├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── fuzz.yml │ ├── lint.yml │ └── publish.yml ├── .gitignore ├── CMakeLists.txt ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── Package.swift ├── README.md ├── binding.gyp ├── bindings ├── c │ ├── tree-sitter-bash.h │ └── tree-sitter-bash.pc.in ├── go │ ├── binding.go │ └── binding_test.go ├── node │ ├── binding.cc │ ├── binding_test.js │ ├── index.d.ts │ └── index.js ├── python │ ├── tests │ │ └── test_binding.py │ └── tree_sitter_bash │ │ ├── __init__.py │ │ ├── __init__.pyi │ │ ├── binding.c │ │ └── py.typed ├── rust │ ├── build.rs │ └── lib.rs └── swift │ ├── TreeSitterBash │ └── bash.h │ └── TreeSitterBashTests │ └── TreeSitterBashTests.swift ├── eslint.config.mjs ├── examples ├── atom.sh ├── clean-old.sh ├── doc-build.sh ├── install.sh ├── release.sh ├── relocate.sh ├── test.sh └── update-authors.sh ├── go.mod ├── go.sum ├── grammar.js ├── package-lock.json ├── package.json ├── pyproject.toml ├── queries └── highlights.scm ├── setup.py ├── src ├── grammar.json ├── node-types.json ├── parser.c ├── scanner.c └── tree_sitter │ ├── alloc.h │ ├── array.h │ └── parser.h ├── test └── corpus │ ├── commands.txt │ ├── crlf.txt │ ├── literals.txt │ ├── programs.txt │ └── statements.txt └── tree-sitter.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | 6 | [*.{json,toml,yml,gyp}] 7 | indent_style = space 8 | indent_size = 2 9 | 10 | [*.js] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.scm] 15 | indent_style = space 16 | indent_size = 2 17 | 18 | [*.{c,cc,h}] 19 | indent_style = space 20 | indent_size = 4 21 | 22 | [*.rs] 23 | indent_style = space 24 | indent_size = 4 25 | 26 | [*.{py,pyi}] 27 | indent_style = space 28 | indent_size = 4 29 | 30 | [*.swift] 31 | indent_style = space 32 | indent_size = 4 33 | 34 | [*.go] 35 | indent_style = tab 36 | indent_size = 8 37 | 38 | [Makefile] 39 | indent_style = tab 40 | indent_size = 8 41 | 42 | [parser.c] 43 | indent_size = 2 44 | 45 | [{alloc,array,parser}.h] 46 | indent_size = 2 47 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | # Generated source files 4 | src/*.json linguist-generated 5 | src/parser.c linguist-generated 6 | src/tree_sitter/* linguist-generated 7 | 8 | # C bindings 9 | bindings/c/* linguist-generated 10 | CMakeLists.txt linguist-generated 11 | Makefile linguist-generated 12 | 13 | # Rust bindings 14 | bindings/rust/* linguist-generated 15 | Cargo.toml linguist-generated 16 | Cargo.lock linguist-generated 17 | 18 | # Node.js bindings 19 | bindings/node/* linguist-generated 20 | binding.gyp linguist-generated 21 | package.json linguist-generated 22 | package-lock.json linguist-generated 23 | 24 | # Python bindings 25 | bindings/python/** linguist-generated 26 | setup.py linguist-generated 27 | pyproject.toml linguist-generated 28 | 29 | # Go bindings 30 | bindings/go/* linguist-generated 31 | go.mod linguist-generated 32 | go.sum linguist-generated 33 | 34 | # Swift bindings 35 | bindings/swift/** linguist-generated 36 | Package.swift linguist-generated 37 | Package.resolved linguist-generated 38 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: tree-sitter 4 | patreon: # Replace with a single Patreon username 5 | open_collective: tree-sitter # Replace with a single Open Collective username 6 | ko_fi: amaanq 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | commit-message: 8 | prefix: "ci" 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | paths: 7 | - grammar.js 8 | - src/** 9 | - test/** 10 | - bindings/** 11 | - binding.gyp 12 | pull_request: 13 | paths: 14 | - grammar.js 15 | - src/** 16 | - test/** 17 | - bindings/** 18 | - binding.gyp 19 | 20 | concurrency: 21 | group: ${{github.workflow}}-${{github.ref}} 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | test: 26 | name: Test parser 27 | runs-on: ${{matrix.os}} 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | os: [ubuntu-latest, windows-latest, macos-14] 32 | steps: 33 | - name: Checkout repository 34 | uses: actions/checkout@v4 35 | - name: Set up tree-sitter 36 | uses: tree-sitter/setup-action/cli@v2 37 | - name: Set up examples 38 | shell: bash 39 | run: |- 40 | git clone https://github.com/Bash-it/bash-it examples/bash-it -q --single-branch --depth=1 41 | git clone https://git.savannah.gnu.org/git/bash.git examples/bash -q --single-branch --depth=1 42 | git clone https://anongit.gentoo.org/git/repo/gentoo.git examples/gentoo -q --single-branch --depth=1 43 | if [[ $RUNNER_OS != Windows ]]; then 44 | git clone https://github.com/oilshell/wild-corpus examples/wild-corpus -q --single-branch --depth=1 45 | fi 46 | - name: Run tests 47 | uses: tree-sitter/parser-test-action@v2 48 | with: 49 | test-rust: true 50 | test-node: true 51 | test-python: true 52 | test-go: true 53 | test-swift: true 54 | - name: Parse examples 55 | uses: tree-sitter/parse-action@v4 56 | with: 57 | files: |- 58 | examples/*.sh 59 | examples/bash-it/**/*.sh 60 | examples/bash-it/**/*.bash 61 | examples/bash/**/*.sh 62 | examples/bash/**/*.tests 63 | examples/gentoo/**/*.sh 64 | examples/gentoo/**/*.eclass 65 | examples/wild-corpus/**/*.sh 66 | # examples/gentoo/**/*.ebuild 67 | !examples/gentoo/app-antivirus/fangfrisch/files/fangfrisch-has-news.sh 68 | !examples/gentoo/eclass/ruby-fakegem.eclass 69 | invalid-files-list: script/known-failures.txt 70 | -------------------------------------------------------------------------------- /.github/workflows/fuzz.yml: -------------------------------------------------------------------------------- 1 | name: Fuzz 2 | 3 | on: 4 | push: 5 | paths: 6 | - src/scanner.c 7 | pull_request: 8 | paths: 9 | - src/scanner.c 10 | workflow_dispatch: 11 | 12 | concurrency: 13 | group: ${{github.workflow}}-${{github.ref}} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | test: 18 | name: Fuzz scanner 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout repository 22 | uses: actions/checkout@v4 23 | - name: Run fuzzer 24 | uses: tree-sitter/fuzz-action@v4 25 | with: 26 | corpus: examples 27 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | paths: 7 | - grammar.js 8 | pull_request: 9 | paths: 10 | - grammar.js 11 | 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout repository 17 | uses: actions/checkout@v4 18 | - name: Set up Node.js 19 | uses: actions/setup-node@v4 20 | with: 21 | cache: npm 22 | node-version: ${{vars.NODE_VERSION}} 23 | - name: Install modules 24 | run: npm ci --legacy-peer-deps 25 | - name: Run ESLint 26 | run: npm run lint 27 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish packages 2 | 3 | on: 4 | push: 5 | tags: ["*"] 6 | 7 | permissions: 8 | contents: write 9 | id-token: write 10 | attestations: write 11 | 12 | jobs: 13 | github: 14 | uses: tree-sitter/workflows/.github/workflows/release.yml@main 15 | with: 16 | generate: true 17 | attestations: true 18 | npm: 19 | uses: tree-sitter/workflows/.github/workflows/package-npm.yml@main 20 | secrets: 21 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 22 | with: 23 | generate: true 24 | crates: 25 | uses: tree-sitter/workflows/.github/workflows/package-crates.yml@main 26 | secrets: 27 | CARGO_REGISTRY_TOKEN: ${{secrets.CARGO_REGISTRY_TOKEN}} 28 | with: 29 | generate: true 30 | pypi: 31 | uses: tree-sitter/workflows/.github/workflows/package-pypi.yml@main 32 | secrets: 33 | PYPI_API_TOKEN: ${{secrets.PYPI_API_TOKEN}} 34 | with: 35 | generate: true 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Rust artifacts 2 | target/ 3 | 4 | # Node artifacts 5 | build/ 6 | prebuilds/ 7 | node_modules/ 8 | 9 | # Swift artifacts 10 | .build/ 11 | 12 | # Go artifacts 13 | _obj/ 14 | 15 | # Python artifacts 16 | .venv/ 17 | dist/ 18 | *.egg-info 19 | *.whl 20 | 21 | # C artifacts 22 | *.a 23 | *.so 24 | *.so.* 25 | *.dylib 26 | *.dll 27 | *.pc 28 | 29 | # Example dirs 30 | /examples/*/ 31 | 32 | # Grammar volatiles 33 | *.wasm 34 | *.obj 35 | *.o 36 | 37 | # Archives 38 | *.tar.gz 39 | *.tgz 40 | *.zip 41 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | project(tree-sitter-bash 4 | VERSION "0.23.3" 5 | DESCRIPTION "Bash grammar for tree-sitter" 6 | HOMEPAGE_URL "https://github.com/tree-sitter/tree-sitter-bash" 7 | LANGUAGES C) 8 | 9 | option(BUILD_SHARED_LIBS "Build using shared libraries" ON) 10 | option(TREE_SITTER_REUSE_ALLOCATOR "Reuse the library allocator" OFF) 11 | 12 | set(TREE_SITTER_ABI_VERSION 14 CACHE STRING "Tree-sitter ABI version") 13 | if(NOT ${TREE_SITTER_ABI_VERSION} MATCHES "^[0-9]+$") 14 | unset(TREE_SITTER_ABI_VERSION CACHE) 15 | message(FATAL_ERROR "TREE_SITTER_ABI_VERSION must be an integer") 16 | endif() 17 | 18 | find_program(TREE_SITTER_CLI tree-sitter DOC "Tree-sitter CLI") 19 | 20 | add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/parser.c" 21 | DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json" 22 | COMMAND "${TREE_SITTER_CLI}" generate src/grammar.json 23 | --abi=${TREE_SITTER_ABI_VERSION} 24 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 25 | COMMENT "Generating parser.c") 26 | 27 | add_library(tree-sitter-bash src/parser.c) 28 | if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/src/scanner.c) 29 | target_sources(tree-sitter-bash PRIVATE src/scanner.c) 30 | endif() 31 | target_include_directories(tree-sitter-bash PRIVATE src) 32 | 33 | target_compile_definitions(tree-sitter-bash PRIVATE 34 | $<$:TREE_SITTER_REUSE_ALLOCATOR> 35 | $<$:TREE_SITTER_DEBUG>) 36 | 37 | set_target_properties(tree-sitter-bash 38 | PROPERTIES 39 | C_STANDARD 11 40 | POSITION_INDEPENDENT_CODE ON 41 | SOVERSION "${TREE_SITTER_ABI_VERSION}.${PROJECT_VERSION_MAJOR}" 42 | DEFINE_SYMBOL "") 43 | 44 | configure_file(bindings/c/tree-sitter-bash.pc.in 45 | "${CMAKE_CURRENT_BINARY_DIR}/tree-sitter-bash.pc" @ONLY) 46 | 47 | include(GNUInstallDirs) 48 | 49 | install(FILES bindings/c/tree-sitter-bash.h 50 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/tree_sitter") 51 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tree-sitter-bash.pc" 52 | DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig") 53 | install(TARGETS tree-sitter-bash 54 | LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}") 55 | 56 | add_custom_target(ts-test "${TREE_SITTER_CLI}" test 57 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 58 | COMMENT "tree-sitter test") 59 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "cc" 16 | version = "1.1.37" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf" 19 | dependencies = [ 20 | "shlex", 21 | ] 22 | 23 | [[package]] 24 | name = "memchr" 25 | version = "2.7.4" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 28 | 29 | [[package]] 30 | name = "regex" 31 | version = "1.11.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 34 | dependencies = [ 35 | "aho-corasick", 36 | "memchr", 37 | "regex-automata", 38 | "regex-syntax", 39 | ] 40 | 41 | [[package]] 42 | name = "regex-automata" 43 | version = "0.4.8" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" 46 | dependencies = [ 47 | "aho-corasick", 48 | "memchr", 49 | "regex-syntax", 50 | ] 51 | 52 | [[package]] 53 | name = "regex-syntax" 54 | version = "0.8.5" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 57 | 58 | [[package]] 59 | name = "shlex" 60 | version = "1.3.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 63 | 64 | [[package]] 65 | name = "streaming-iterator" 66 | version = "0.1.9" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" 69 | 70 | [[package]] 71 | name = "tree-sitter" 72 | version = "0.24.4" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "b67baf55e7e1b6806063b1e51041069c90afff16afcbbccd278d899f9d84bca4" 75 | dependencies = [ 76 | "cc", 77 | "regex", 78 | "regex-syntax", 79 | "streaming-iterator", 80 | "tree-sitter-language", 81 | ] 82 | 83 | [[package]] 84 | name = "tree-sitter-bash" 85 | version = "0.23.3" 86 | dependencies = [ 87 | "cc", 88 | "tree-sitter", 89 | "tree-sitter-language", 90 | ] 91 | 92 | [[package]] 93 | name = "tree-sitter-language" 94 | version = "0.1.2" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "e8ddffe35a0e5eeeadf13ff7350af564c6e73993a24db62caee1822b185c2600" 97 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tree-sitter-bash" 3 | description = "Bash grammar for tree-sitter" 4 | version = "0.23.3" 5 | authors = [ 6 | "Max Brunsfeld ", 7 | "Amaan Qureshi ", 8 | ] 9 | license = "MIT" 10 | readme = "README.md" 11 | keywords = ["incremental", "parsing", "tree-sitter", "bash"] 12 | categories = ["parsing", "text-editors"] 13 | repository = "https://github.com/tree-sitter/tree-sitter-bash" 14 | edition = "2021" 15 | autoexamples = false 16 | 17 | build = "bindings/rust/build.rs" 18 | include = ["LICENSE", "bindings/rust/*", "grammar.js", "queries/*", "src/*", "tree-sitter.json"] 19 | 20 | [lib] 21 | path = "bindings/rust/lib.rs" 22 | 23 | [dependencies] 24 | tree-sitter-language = "0.1" 25 | 26 | [build-dependencies] 27 | cc = "1.1" 28 | 29 | [dev-dependencies] 30 | tree-sitter = "0.24" 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Max Brunsfeld 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(OS),Windows_NT) 2 | $(error Windows is not supported) 3 | endif 4 | 5 | LANGUAGE_NAME := tree-sitter-bash 6 | HOMEPAGE_URL := https://github.com/tree-sitter/tree-sitter-bash 7 | VERSION := 0.23.3 8 | 9 | # repository 10 | SRC_DIR := src 11 | 12 | TS ?= tree-sitter 13 | 14 | # install directory layout 15 | PREFIX ?= /usr/local 16 | INCLUDEDIR ?= $(PREFIX)/include 17 | LIBDIR ?= $(PREFIX)/lib 18 | PCLIBDIR ?= $(LIBDIR)/pkgconfig 19 | 20 | # source/object files 21 | PARSER := $(SRC_DIR)/parser.c 22 | EXTRAS := $(filter-out $(PARSER),$(wildcard $(SRC_DIR)/*.c)) 23 | OBJS := $(patsubst %.c,%.o,$(PARSER) $(EXTRAS)) 24 | 25 | # flags 26 | ARFLAGS ?= rcs 27 | override CFLAGS += -I$(SRC_DIR) -std=c11 -fPIC 28 | 29 | # ABI versioning 30 | SONAME_MAJOR = $(shell sed -n 's/\#define LANGUAGE_VERSION //p' $(PARSER)) 31 | SONAME_MINOR = $(word 1,$(subst ., ,$(VERSION))) 32 | 33 | # OS-specific bits 34 | ifeq ($(shell uname),Darwin) 35 | SOEXT = dylib 36 | SOEXTVER_MAJOR = $(SONAME_MAJOR).$(SOEXT) 37 | SOEXTVER = $(SONAME_MAJOR).$(SONAME_MINOR).$(SOEXT) 38 | LINKSHARED = -dynamiclib -Wl,-install_name,$(LIBDIR)/lib$(LANGUAGE_NAME).$(SOEXTVER),-rpath,@executable_path/../Frameworks 39 | else 40 | SOEXT = so 41 | SOEXTVER_MAJOR = $(SOEXT).$(SONAME_MAJOR) 42 | SOEXTVER = $(SOEXT).$(SONAME_MAJOR).$(SONAME_MINOR) 43 | LINKSHARED = -shared -Wl,-soname,lib$(LANGUAGE_NAME).$(SOEXTVER) 44 | endif 45 | ifneq ($(filter $(shell uname),FreeBSD NetBSD DragonFly),) 46 | PCLIBDIR := $(PREFIX)/libdata/pkgconfig 47 | endif 48 | 49 | all: lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) $(LANGUAGE_NAME).pc 50 | 51 | lib$(LANGUAGE_NAME).a: $(OBJS) 52 | $(AR) $(ARFLAGS) $@ $^ 53 | 54 | lib$(LANGUAGE_NAME).$(SOEXT): $(OBJS) 55 | $(CC) $(LDFLAGS) $(LINKSHARED) $^ $(LDLIBS) -o $@ 56 | ifneq ($(STRIP),) 57 | $(STRIP) $@ 58 | endif 59 | 60 | $(LANGUAGE_NAME).pc: bindings/c/$(LANGUAGE_NAME).pc.in 61 | sed -e 's|@PROJECT_VERSION@|$(VERSION)|' \ 62 | -e 's|@CMAKE_INSTALL_LIBDIR@|$(LIBDIR:$(PREFIX)/%=%)|' \ 63 | -e 's|@CMAKE_INSTALL_INCLUDEDIR@|$(INCLUDEDIR:$(PREFIX)/%=%)|' \ 64 | -e 's|@PROJECT_DESCRIPTION@|$(DESCRIPTION)|' \ 65 | -e 's|@PROJECT_HOMEPAGE_URL@|$(HOMEPAGE_URL)|' \ 66 | -e 's|@CMAKE_INSTALL_PREFIX@|$(PREFIX)|' $< > $@ 67 | 68 | $(PARSER): $(SRC_DIR)/grammar.json 69 | $(TS) generate $^ 70 | 71 | install: all 72 | install -d '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter '$(DESTDIR)$(PCLIBDIR)' '$(DESTDIR)$(LIBDIR)' 73 | install -m644 bindings/c/$(LANGUAGE_NAME).h '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h 74 | install -m644 $(LANGUAGE_NAME).pc '$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc 75 | install -m644 lib$(LANGUAGE_NAME).a '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a 76 | install -m755 lib$(LANGUAGE_NAME).$(SOEXT) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER) 77 | ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) 78 | ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXT) 79 | 80 | uninstall: 81 | $(RM) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a \ 82 | '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER) \ 83 | '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) \ 84 | '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXT) \ 85 | '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h \ 86 | '$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc 87 | 88 | clean: 89 | $(RM) $(OBJS) $(LANGUAGE_NAME).pc lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) 90 | 91 | test: 92 | $(TS) test 93 | 94 | .PHONY: all install uninstall clean test 95 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "TreeSitterBash", 6 | products: [ 7 | .library(name: "TreeSitterBash", targets: ["TreeSitterBash"]), 8 | ], 9 | dependencies: [ 10 | .package(url: "https://github.com/ChimeHQ/SwiftTreeSitter", from: "0.8.0"), 11 | ], 12 | targets: [ 13 | .target( 14 | name: "TreeSitterBash", 15 | dependencies: [], 16 | path: ".", 17 | sources: [ 18 | "src/parser.c", 19 | "src/scanner.c", 20 | ], 21 | resources: [ 22 | .copy("queries") 23 | ], 24 | publicHeadersPath: "bindings/swift", 25 | cSettings: [.headerSearchPath("src")] 26 | ), 27 | .testTarget( 28 | name: "TreeSitterBashTests", 29 | dependencies: [ 30 | "SwiftTreeSitter", 31 | "TreeSitterBash", 32 | ], 33 | path: "bindings/swift/TreeSitterBashTests" 34 | ) 35 | ], 36 | cLanguageStandard: .c11 37 | ) 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tree-sitter-bash 2 | 3 | [![CI][ci]](https://github.com/tree-sitter/tree-sitter-bash/actions/workflows/ci.yml) 4 | [![discord][discord]](https://discord.gg/w7nTvsVJhm) 5 | [![matrix][matrix]](https://matrix.to/#/#tree-sitter-chat:matrix.org) 6 | [![crates][crates]](https://crates.io/crates/tree-sitter-bash) 7 | [![npm][npm]](https://www.npmjs.com/package/tree-sitter-bash) 8 | [![pypi][pypi]](https://pypi.org/project/tree-sitter-bash) 9 | 10 | Bash grammar for [tree-sitter](https://github.com/tree-sitter/tree-sitter). 11 | 12 | ## Development 13 | 14 | Install the dependencies: 15 | 16 | ```sh 17 | npm install 18 | ``` 19 | 20 | Build and run the tests: 21 | 22 | ```sh 23 | npm run build 24 | npm run test 25 | ``` 26 | 27 | Run the build and tests in watch mode: 28 | 29 | ```sh 30 | npm run test:watch 31 | ``` 32 | 33 | ### References 34 | 35 | - [Bash man page](http://man7.org/linux/man-pages/man1/bash.1.html#SHELL_GRAMMAR) 36 | - [Shell command language specification](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html) 37 | - [mvdnan/sh - a shell parser in go](https://github.com/mvdan/sh) 38 | 39 | [ci]: https://img.shields.io/github/actions/workflow/status/tree-sitter/tree-sitter-bash/ci.yml?logo=github&label=CI 40 | [discord]: https://img.shields.io/discord/1063097320771698699?logo=discord&label=discord 41 | [matrix]: https://img.shields.io/matrix/tree-sitter-chat%3Amatrix.org?logo=matrix&label=matrix 42 | [npm]: https://img.shields.io/npm/v/tree-sitter-bash?logo=npm 43 | [crates]: https://img.shields.io/crates/v/tree-sitter-bash?logo=rust 44 | [pypi]: https://img.shields.io/pypi/v/tree-sitter-bash?logo=pypi&logoColor=ffd242 45 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "tree_sitter_bash_binding", 5 | "dependencies": [ 6 | " 2 | 3 | typedef struct TSLanguage TSLanguage; 4 | 5 | extern "C" TSLanguage *tree_sitter_bash(); 6 | 7 | // "tree-sitter", "language" hashed with BLAKE2 8 | const napi_type_tag LANGUAGE_TYPE_TAG = { 9 | 0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16 10 | }; 11 | 12 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 13 | exports["name"] = Napi::String::New(env, "bash"); 14 | auto language = Napi::External::New(env, tree_sitter_bash()); 15 | language.TypeTag(&LANGUAGE_TYPE_TAG); 16 | exports["language"] = language; 17 | return exports; 18 | } 19 | 20 | NODE_API_MODULE(tree_sitter_bash_binding, Init) 21 | -------------------------------------------------------------------------------- /bindings/node/binding_test.js: -------------------------------------------------------------------------------- 1 | const assert = require("node:assert"); 2 | const { test } = require("node:test"); 3 | 4 | const Parser = require("tree-sitter"); 5 | 6 | test("can load grammar", () => { 7 | const parser = new Parser(); 8 | assert.doesNotThrow(() => parser.setLanguage(require("."))); 9 | }); 10 | -------------------------------------------------------------------------------- /bindings/node/index.d.ts: -------------------------------------------------------------------------------- 1 | type BaseNode = { 2 | type: string; 3 | named: boolean; 4 | }; 5 | 6 | type ChildNode = { 7 | multiple: boolean; 8 | required: boolean; 9 | types: BaseNode[]; 10 | }; 11 | 12 | type NodeInfo = 13 | | (BaseNode & { 14 | subtypes: BaseNode[]; 15 | }) 16 | | (BaseNode & { 17 | fields: { [name: string]: ChildNode }; 18 | children: ChildNode[]; 19 | }); 20 | 21 | type Language = { 22 | name: string; 23 | language: unknown; 24 | nodeTypeInfo: NodeInfo[]; 25 | }; 26 | 27 | declare const language: Language; 28 | export = language; 29 | -------------------------------------------------------------------------------- /bindings/node/index.js: -------------------------------------------------------------------------------- 1 | const root = require("path").join(__dirname, "..", ".."); 2 | 3 | module.exports = 4 | typeof process.versions.bun === "string" 5 | // Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time 6 | ? require(`../../prebuilds/${process.platform}-${process.arch}/tree-sitter-bash.node`) 7 | : require("node-gyp-build")(root); 8 | 9 | try { 10 | module.exports.nodeTypeInfo = require("../../src/node-types.json"); 11 | } catch (_) {} 12 | -------------------------------------------------------------------------------- /bindings/python/tests/test_binding.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | import tree_sitter, tree_sitter_bash 4 | 5 | 6 | class TestLanguage(TestCase): 7 | def test_can_load_grammar(self): 8 | try: 9 | tree_sitter.Language(tree_sitter_bash.language()) 10 | except Exception: 11 | self.fail("Error loading Bash grammar") 12 | -------------------------------------------------------------------------------- /bindings/python/tree_sitter_bash/__init__.py: -------------------------------------------------------------------------------- 1 | """Bash grammar for tree-sitter""" 2 | 3 | from importlib.resources import files as _files 4 | 5 | from ._binding import language 6 | 7 | 8 | def _get_query(name, file): 9 | query = _files(f"{__package__}.queries") / file 10 | globals()[name] = query.read_text() 11 | return globals()[name] 12 | 13 | 14 | def __getattr__(name): 15 | if name == "HIGHLIGHTS_QUERY": 16 | return _get_query("HIGHLIGHTS_QUERY", "highlights.scm") 17 | 18 | raise AttributeError(f"module {__name__!r} has no attribute {name!r}") 19 | 20 | 21 | __all__ = [ 22 | "language", 23 | "HIGHLIGHTS_QUERY", 24 | ] 25 | 26 | 27 | def __dir__(): 28 | return sorted(__all__ + [ 29 | "__all__", "__builtins__", "__cached__", "__doc__", "__file__", 30 | "__loader__", "__name__", "__package__", "__path__", "__spec__", 31 | ]) 32 | -------------------------------------------------------------------------------- /bindings/python/tree_sitter_bash/__init__.pyi: -------------------------------------------------------------------------------- 1 | from typing import Final 2 | 3 | HIGHLIGHTS_QUERY: Final[str] 4 | 5 | def language() -> object: ... 6 | -------------------------------------------------------------------------------- /bindings/python/tree_sitter_bash/binding.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef struct TSLanguage TSLanguage; 4 | 5 | TSLanguage *tree_sitter_bash(void); 6 | 7 | static PyObject* _binding_language(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) { 8 | return PyCapsule_New(tree_sitter_bash(), "tree_sitter.Language", NULL); 9 | } 10 | 11 | static PyMethodDef methods[] = { 12 | {"language", _binding_language, METH_NOARGS, 13 | "Get the tree-sitter language for this grammar."}, 14 | {NULL, NULL, 0, NULL} 15 | }; 16 | 17 | static struct PyModuleDef module = { 18 | .m_base = PyModuleDef_HEAD_INIT, 19 | .m_name = "_binding", 20 | .m_doc = NULL, 21 | .m_size = -1, 22 | .m_methods = methods 23 | }; 24 | 25 | PyMODINIT_FUNC PyInit__binding(void) { 26 | return PyModule_Create(&module); 27 | } 28 | -------------------------------------------------------------------------------- /bindings/python/tree_sitter_bash/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tree-sitter/tree-sitter-bash/0c46d792d54c536be5ff7eb18eb95c70fccdb232/bindings/python/tree_sitter_bash/py.typed -------------------------------------------------------------------------------- /bindings/rust/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let src_dir = std::path::Path::new("src"); 3 | 4 | let mut c_config = cc::Build::new(); 5 | c_config 6 | .std("c11") 7 | .include(src_dir) 8 | .flag_if_supported("-Wno-unused-value"); 9 | 10 | #[cfg(target_env = "msvc")] 11 | c_config.flag("-utf-8"); 12 | 13 | let parser_path = src_dir.join("parser.c"); 14 | c_config.file(&parser_path); 15 | println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap()); 16 | 17 | let scanner_path = src_dir.join("scanner.c"); 18 | c_config.file(&scanner_path); 19 | println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap()); 20 | 21 | c_config.compile("tree-sitter-bash"); 22 | } 23 | -------------------------------------------------------------------------------- /bindings/rust/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides Bash language support for the [tree-sitter][] parsing library. 2 | //! 3 | //! Typically, you will use the [LANGUAGE][] constant to add this language to a 4 | //! tree-sitter [Parser][], and then use the parser to parse some code: 5 | //! 6 | //! ``` 7 | //! use tree_sitter::Parser; 8 | //! 9 | //! let code = r#" 10 | //! echo "hello world!" 11 | //! "#; 12 | //! let mut parser = Parser::new(); 13 | //! let language = tree_sitter_bash::LANGUAGE; 14 | //! parser 15 | //! .set_language(&language.into()) 16 | //! .expect("Error loading Bash parser"); 17 | //! let tree = parser.parse(code, None).unwrap(); 18 | //! assert!(!tree.root_node().has_error()); 19 | //! ``` 20 | //! 21 | //! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html 22 | //! [tree-sitter]: https://tree-sitter.github.io/ 23 | 24 | use tree_sitter_language::LanguageFn; 25 | 26 | extern "C" { 27 | fn tree_sitter_bash() -> *const (); 28 | } 29 | 30 | /// The tree-sitter [`LanguageFn`][LanguageFn] for this grammar. 31 | /// 32 | /// [LanguageFn]: https://docs.rs/tree-sitter-language/*/tree_sitter_language/struct.LanguageFn.html 33 | pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_bash) }; 34 | 35 | /// The content of the [`node-types.json`][] file for this grammar. 36 | /// 37 | /// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types 38 | pub const NODE_TYPES: &str = include_str!("../../src/node-types.json"); 39 | 40 | /// The syntax highlighting query for this grammar. 41 | pub const HIGHLIGHT_QUERY: &str = include_str!("../../queries/highlights.scm"); 42 | 43 | #[cfg(test)] 44 | mod tests { 45 | #[test] 46 | fn test_can_load_grammar() { 47 | let mut parser = tree_sitter::Parser::new(); 48 | parser 49 | .set_language(&super::LANGUAGE.into()) 50 | .expect("Error loading Bash parser"); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /bindings/swift/TreeSitterBash/bash.h: -------------------------------------------------------------------------------- 1 | #ifndef TREE_SITTER_BASH_H_ 2 | #define TREE_SITTER_BASH_H_ 3 | 4 | typedef struct TSLanguage TSLanguage; 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | const TSLanguage *tree_sitter_bash(void); 11 | 12 | #ifdef __cplusplus 13 | } 14 | #endif 15 | 16 | #endif // TREE_SITTER_BASH_H_ 17 | -------------------------------------------------------------------------------- /bindings/swift/TreeSitterBashTests/TreeSitterBashTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import SwiftTreeSitter 3 | import TreeSitterBash 4 | 5 | final class TreeSitterBashTests: XCTestCase { 6 | func testCanLoadGrammar() throws { 7 | let parser = Parser() 8 | let language = Language(language: tree_sitter_bash()) 9 | XCTAssertNoThrow(try parser.setLanguage(language), 10 | "Error loading Bash grammar") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import treesitter from 'eslint-config-treesitter'; 2 | 3 | export default [ 4 | ...treesitter, 5 | ]; 6 | -------------------------------------------------------------------------------- /examples/atom.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$(uname)" == 'Darwin' ]; then 4 | OS='Mac' 5 | elif [ "$(expr substr $(uname -s) 1 5)" == 'Linux' ]; then 6 | OS='Linux' 7 | else 8 | echo "Your platform ($(uname -a)) is not supported." 9 | exit 1 10 | fi 11 | 12 | if [ "$(basename $0)" == 'atom-beta' ]; then 13 | BETA_VERSION=true 14 | else 15 | BETA_VERSION= 16 | fi 17 | 18 | export ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT=true 19 | 20 | while getopts ":wtfvh-:" opt; do 21 | case "$opt" in 22 | -) 23 | case "${OPTARG}" in 24 | wait) 25 | WAIT=1 26 | ;; 27 | help|version) 28 | REDIRECT_STDERR=1 29 | EXPECT_OUTPUT=1 30 | ;; 31 | foreground|benchmark|benchmark-test|test) 32 | EXPECT_OUTPUT=1 33 | ;; 34 | esac 35 | ;; 36 | w) 37 | WAIT=1 38 | ;; 39 | h|v) 40 | REDIRECT_STDERR=1 41 | EXPECT_OUTPUT=1 42 | ;; 43 | f|t) 44 | EXPECT_OUTPUT=1 45 | ;; 46 | esac 47 | done 48 | 49 | if [ $REDIRECT_STDERR ]; then 50 | exec 2> /dev/null 51 | fi 52 | 53 | if [ $EXPECT_OUTPUT ]; then 54 | export ELECTRON_ENABLE_LOGGING=1 55 | fi 56 | 57 | if [ $OS == 'Mac' ]; then 58 | if [ -L "$0" ]; then 59 | SCRIPT="$(readlink "$0")" 60 | else 61 | SCRIPT="$0" 62 | fi 63 | ATOM_APP="$(dirname "$(dirname "$(dirname "$(dirname "$SCRIPT")")")")" 64 | if [ "$ATOM_APP" == . ]; then 65 | unset ATOM_APP 66 | else 67 | ATOM_PATH="$(dirname "$ATOM_APP")" 68 | ATOM_APP_NAME="$(basename "$ATOM_APP")" 69 | fi 70 | 71 | if [ -n "$BETA_VERSION" ]; then 72 | ATOM_EXECUTABLE_NAME="Atom Beta" 73 | else 74 | ATOM_EXECUTABLE_NAME="Atom" 75 | fi 76 | 77 | if [ -z "${ATOM_PATH}" ]; then 78 | # If ATOM_PATH isn't set, check /Applications and then ~/Applications for Atom.app 79 | if [ -x "/Applications/$ATOM_APP_NAME" ]; then 80 | ATOM_PATH="/Applications" 81 | elif [ -x "$HOME/Applications/$ATOM_APP_NAME" ]; then 82 | ATOM_PATH="$HOME/Applications" 83 | else 84 | # We haven't found an Atom.app, use spotlight to search for Atom 85 | ATOM_PATH="$(mdfind "kMDItemCFBundleIdentifier == 'com.github.atom'" | grep -v ShipIt | head -1 | xargs -0 dirname)" 86 | 87 | # Exit if Atom can't be found 88 | if [ ! -x "$ATOM_PATH/$ATOM_APP_NAME" ]; then 89 | echo "Cannot locate ${ATOM_APP_NAME}, it is usually located in /Applications. Set the ATOM_PATH environment variable to the directory containing ${ATOM_APP_NAME}." 90 | exit 1 91 | fi 92 | fi 93 | fi 94 | 95 | if [ $EXPECT_OUTPUT ]; then 96 | "$ATOM_PATH/$ATOM_APP_NAME/Contents/MacOS/$ATOM_EXECUTABLE_NAME" --executed-from="$(pwd)" --pid=$$ "$@" 97 | exit $? 98 | else 99 | open -a "$ATOM_PATH/$ATOM_APP_NAME" -n --args --executed-from="$(pwd)" --pid=$$ --path-environment="$PATH" "$@" 100 | fi 101 | elif [ $OS == 'Linux' ]; then 102 | SCRIPT=$(readlink -f "$0") 103 | USR_DIRECTORY=$(readlink -f $(dirname $SCRIPT)/..) 104 | 105 | if [ -n "$BETA_VERSION" ]; then 106 | ATOM_PATH="$USR_DIRECTORY/share/atom-beta/atom" 107 | else 108 | ATOM_PATH="$USR_DIRECTORY/share/atom/atom" 109 | fi 110 | 111 | ATOM_HOME="${ATOM_HOME:-$HOME/.atom}" 112 | mkdir -p "$ATOM_HOME" 113 | 114 | : ${TMPDIR:=/tmp} 115 | 116 | [ -x "$ATOM_PATH" ] || ATOM_PATH="$TMPDIR/atom-build/Atom/atom" 117 | 118 | if [ $EXPECT_OUTPUT ]; then 119 | "$ATOM_PATH" --executed-from="$(pwd)" --pid=$$ "$@" 120 | exit $? 121 | else 122 | ( 123 | nohup "$ATOM_PATH" --executed-from="$(pwd)" --pid=$$ "$@" > "$ATOM_HOME/nohup.out" 2>&1 124 | if [ $? -ne 0 ]; then 125 | cat "$ATOM_HOME/nohup.out" 126 | exit $? 127 | fi 128 | ) & 129 | fi 130 | fi 131 | 132 | # Exits this process when Atom is used as $EDITOR 133 | on_die() { 134 | exit 0 135 | } 136 | trap 'on_die' SIGQUIT SIGTERM 137 | 138 | # If the wait flag is set, don't exit this process until Atom tells it to. 139 | if [ $WAIT ]; then 140 | while true; do 141 | sleep 1 142 | done 143 | fi 144 | -------------------------------------------------------------------------------- /examples/clean-old.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # look for old 0.x cruft, and get rid of it. 4 | # Should already be sitting in the npm folder. 5 | 6 | # This doesn't have to be quite as cross-platform as install.sh. 7 | # There are some bash-isms, because maintaining *two* 8 | # fully-portable posix/bourne sh scripts is too much for 9 | # one project with a sane maintainer. 10 | 11 | # If readlink isn't available, then this is just too tricky. 12 | # However, greadlink is fine, so Solaris can join the party, too. 13 | readlink="readlink" 14 | which $readlink >/dev/null 2>/dev/null 15 | if [ $? -ne 0 ]; then 16 | readlink="greadlink" 17 | which $readlink >/dev/null 2>/dev/null 18 | if [ $? -ne 0 ]; then 19 | echo "Can't find the readlink or greadlink command. Aborting." 20 | exit 1 21 | fi 22 | fi 23 | 24 | if [ "x$npm_config_prefix" != "x" ]; then 25 | PREFIXES=$npm_config_prefix 26 | else 27 | node="$NODE" 28 | if [ "x$node" = "x" ]; then 29 | node=`which node` 30 | fi 31 | if [ "x$node" = "x" ]; then 32 | echo "Can't find node to determine prefix. Aborting." 33 | exit 1 34 | fi 35 | 36 | 37 | PREFIX=`dirname $node` 38 | PREFIX=`dirname $PREFIX` 39 | echo "cleanup prefix=$PREFIX" 40 | PREFIXES=$PREFIX 41 | 42 | altprefix=`"$node" -e process.installPrefix` 43 | if [ "x$altprefix" != "x" ] && [ "x$altprefix" != "x$PREFIX" ]; then 44 | echo "altprefix=$altprefix" 45 | PREFIXES="$PREFIX $altprefix" 46 | fi 47 | fi 48 | 49 | # now prefix is where npm would be rooted by default 50 | # go hunting. 51 | 52 | packages= 53 | for prefix in $PREFIXES; do 54 | packages="$packages 55 | "`ls "$prefix"/lib/node/.npm 2>/dev/null | grep -v .cache` 56 | done 57 | 58 | packages=`echo $packages` 59 | 60 | filelist=() 61 | fid=0 62 | 63 | for prefix in $PREFIXES; do 64 | # remove any links into the .npm dir, or links to 65 | # version-named shims/symlinks. 66 | for folder in share/man bin lib/node; do 67 | find $prefix/$folder -type l | while read file; do 68 | target=`$readlink $file | grep '/\.npm/'` 69 | if [ "x$target" != "x" ]; then 70 | # found one! 71 | filelist[$fid]="$file" 72 | let 'fid++' 73 | # also remove any symlinks to this file. 74 | base=`basename "$file"` 75 | base=`echo "$base" | awk -F@ '{print $1}'` 76 | if [ "x$base" != "x" ]; then 77 | find "`dirname $file`" -type l -name "$base"'*' \ 78 | | while read l; do 79 | target=`$readlink "$l" | grep "$base"` 80 | if [ "x$target" != "x" ]; then 81 | filelist[$fid]="$1" 82 | let 'fid++' 83 | fi 84 | done 85 | fi 86 | fi 87 | done 88 | 89 | # Scour for shim files. These are relics of 0.2 npm installs. 90 | # note: grep -r is not portable. 91 | find $prefix/$folder -type f \ 92 | | xargs grep -sl '// generated by npm' \ 93 | | while read file; do 94 | filelist[$fid]="$file" 95 | let 'fid++' 96 | done 97 | done 98 | 99 | # now remove the package modules, and the .npm folder itself. 100 | if [ "x$packages" != "x" ]; then 101 | for pkg in $packages; do 102 | filelist[$fid]="$prefix/lib/node/$pkg" 103 | let 'fid++' 104 | for i in $prefix/lib/node/$pkg\@*; do 105 | filelist[$fid]="$i" 106 | let 'fid++' 107 | done 108 | done 109 | fi 110 | 111 | for folder in lib/node/.npm lib/npm share/npm; do 112 | if [ -d $prefix/$folder ]; then 113 | filelist[$fid]="$prefix/$folder" 114 | let 'fid++' 115 | fi 116 | done 117 | done 118 | 119 | # now actually clean, but only if there's anything TO clean 120 | if [ "${#filelist[@]}" -gt 0 ]; then 121 | echo "" 122 | echo "This script will find and eliminate any shims, symbolic" 123 | echo "links, and other cruft that was installed by npm 0.x." 124 | echo "" 125 | 126 | if [ "x$packages" != "x" ]; then 127 | echo "The following packages appear to have been installed with" 128 | echo "an old version of npm, and will be removed forcibly:" 129 | for pkg in $packages; do 130 | echo " $pkg" 131 | done 132 | echo "Make a note of these. You may want to install them" 133 | echo "with npm 1.0 when this process is completed." 134 | echo "" 135 | fi 136 | 137 | OK= 138 | if [ "x$1" = "x-y" ]; then 139 | OK="yes" 140 | fi 141 | 142 | while [ "$OK" != "y" ] && [ "$OK" != "yes" ] && [ "$OK" != "no" ]; do 143 | echo "Is this OK?" 144 | echo " enter 'yes' or 'no'" 145 | echo " or 'show' to see a list of files " 146 | read OK 147 | if [ "x$OK" = "xshow" ] || [ "x$OK" = "xs" ]; then 148 | for i in "${filelist[@]}"; do 149 | echo "$i" 150 | done 151 | fi 152 | done 153 | if [ "$OK" = "no" ]; then 154 | echo "Aborting" 155 | exit 1 156 | fi 157 | for i in "${filelist[@]}"; do 158 | rm -rf "$i" 159 | done 160 | fi 161 | 162 | echo "" 163 | echo 'All clean!' 164 | 165 | exit 0 166 | -------------------------------------------------------------------------------- /examples/doc-build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ $DEBUG != "" ]]; then 4 | set -x 5 | fi 6 | set -o errexit 7 | set -o pipefail 8 | 9 | if ! [ -x node_modules/.bin/marked-man ]; then 10 | ps=0 11 | if [ -f .building_marked-man ]; then 12 | pid=$(cat .building_marked-man) 13 | ps=$(ps -p $pid | grep $pid | wc -l) || true 14 | fi 15 | 16 | if [ -f .building_marked-man ] && [ $ps != 0 ]; then 17 | while [ -f .building_marked-man ]; do 18 | sleep 1 19 | done 20 | else 21 | # a race to see which make process will be the one to install marked-man 22 | echo $$ > .building_marked-man 23 | sleep 1 24 | if [ $(cat .building_marked-man) == $$ ]; then 25 | make node_modules/.bin/marked-man 26 | rm .building_marked-man 27 | else 28 | while [ -f .building_marked-man ]; do 29 | sleep 1 30 | done 31 | fi 32 | fi 33 | fi 34 | 35 | if ! [ -x node_modules/.bin/marked ]; then 36 | ps=0 37 | if [ -f .building_marked ]; then 38 | pid=$(cat .building_marked) 39 | ps=$(ps -p $pid | grep $pid | wc -l) || true 40 | fi 41 | 42 | if [ -f .building_marked ] && [ $ps != 0 ]; then 43 | while [ -f .building_marked ]; do 44 | sleep 1 45 | done 46 | else 47 | # a race to see which make process will be the one to install marked 48 | echo $$ > .building_marked 49 | sleep 1 50 | if [ $(cat .building_marked) == $$ ]; then 51 | make node_modules/.bin/marked 52 | rm .building_marked 53 | else 54 | while [ -f .building_marked ]; do 55 | sleep 1 56 | done 57 | fi 58 | fi 59 | fi 60 | 61 | src=$1 62 | dest=$2 63 | name=$(basename ${src%.*}) 64 | date=$(date -u +'%Y-%m-%d %H:%M:%S') 65 | version=$(node cli.js -v) 66 | 67 | mkdir -p $(dirname $dest) 68 | 69 | html_replace_tokens () { 70 | local url=$1 71 | sed "s|@NAME@|$name|g" \ 72 | | sed "s|@DATE@|$date|g" \ 73 | | sed "s|@URL@|$url|g" \ 74 | | sed "s|@VERSION@|$version|g" \ 75 | | perl -p -e 's/]*)>([^\(]*\([0-9]\)) -- (.*?)<\/h1>/

\2<\/h1>

\3<\/p>/g' \ 76 | | perl -p -e 's/npm-npm/npm/g' \ 77 | | perl -p -e 's/([^"-])(npm-)?README(?!\.html)(\(1\))?/\1README<\/a>/g' \ 78 | | perl -p -e 's/<a href="[^"]+README.html">README<\/a><\/title>/<title>README<\/title>/g' \ 79 | | perl -p -e 's/([^"-])([^\(> ]+)(\(1\))/\1<a href="..\/cli\/\2.html">\2\3<\/a>/g' \ 80 | | perl -p -e 's/([^"-])([^\(> ]+)(\(3\))/\1<a href="..\/api\/\2.html">\2\3<\/a>/g' \ 81 | | perl -p -e 's/([^"-])([^\(> ]+)(\(5\))/\1<a href="..\/files\/\2.html">\2\3<\/a>/g' \ 82 | | perl -p -e 's/([^"-])([^\(> ]+)(\(7\))/\1<a href="..\/misc\/\2.html">\2\3<\/a>/g' \ 83 | | perl -p -e 's/\([1357]\)<\/a><\/h1>/<\/a><\/h1>/g' \ 84 | | (if [ $(basename $(dirname $dest)) == "doc" ]; then 85 | perl -p -e 's/ href="\.\.\// href="/g' 86 | else 87 | cat 88 | fi) 89 | } 90 | 91 | man_replace_tokens () { 92 | sed "s|@VERSION@|$version|g" \ 93 | | perl -p -e 's/(npm\\-)?([a-zA-Z\\\.\-]*)\(1\)/npm help \2/g' \ 94 | | perl -p -e 's/(npm\\-)?([a-zA-Z\\\.\-]*)\(([57])\)/npm help \3 \2/g' \ 95 | | perl -p -e 's/(npm\\-)?([a-zA-Z\\\.\-]*)\(3\)/npm apihelp \2/g' \ 96 | | perl -p -e 's/npm\(1\)/npm help npm/g' \ 97 | | perl -p -e 's/npm\(3\)/npm apihelp npm/g' 98 | } 99 | 100 | case $dest in 101 | *.[1357]) 102 | ./node_modules/.bin/marked-man --roff $src \ 103 | | man_replace_tokens > $dest 104 | exit $? 105 | ;; 106 | *.html) 107 | url=${dest/html\//} 108 | (cat html/dochead.html && \ 109 | cat $src | ./node_modules/.bin/marked && 110 | cat html/docfoot.html)\ 111 | | html_replace_tokens $url \ 112 | > $dest 113 | exit $? 114 | ;; 115 | *) 116 | echo "Invalid destination type: $dest" >&2 117 | exit 1 118 | ;; 119 | esac 120 | -------------------------------------------------------------------------------- /examples/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # A word about this shell script: 4 | # 5 | # It must work everywhere, including on systems that lack 6 | # a /bin/bash, map 'sh' to ksh, ksh97, bash, ash, or zsh, 7 | # and potentially have either a posix shell or bourne 8 | # shell living at /bin/sh. 9 | # 10 | # See this helpful document on writing portable shell scripts: 11 | # http://www.gnu.org/s/hello/manual/autoconf/Portable-Shell.html 12 | # 13 | # The only shell it won't ever work on is cmd.exe. 14 | 15 | if [ "x$0" = "xsh" ]; then 16 | # run as curl | sh 17 | # on some systems, you can just do cat>npm-install.sh 18 | # which is a bit cuter. But on others, &1 is already closed, 19 | # so catting to another script file won't do anything. 20 | # Follow Location: headers, and fail on errors 21 | curl -f -L -s https://www.npmjs.org/install.sh > npm-install-$$.sh 22 | ret=$? 23 | if [ $ret -eq 0 ]; then 24 | (exit 0) 25 | else 26 | rm npm-install-$$.sh 27 | echo "Failed to download script" >&2 28 | exit $ret 29 | fi 30 | sh npm-install-$$.sh 31 | ret=$? 32 | rm npm-install-$$.sh 33 | exit $ret 34 | fi 35 | 36 | # See what "npm_config_*" things there are in the env, 37 | # and make them permanent. 38 | # If this fails, it's not such a big deal. 39 | configures="`env | grep 'npm_config_' | sed -e 's|^npm_config_||g'`" 40 | 41 | npm_config_loglevel="error" 42 | if [ "x$npm_debug" = "x" ]; then 43 | (exit 0) 44 | else 45 | echo "Running in debug mode." 46 | echo "Note that this requires bash or zsh." 47 | set -o xtrace 48 | set -o pipefail 49 | npm_config_loglevel="verbose" 50 | fi 51 | export npm_config_loglevel 52 | 53 | # make sure that node exists 54 | node=`which node 2>&1` 55 | ret=$? 56 | if [ $ret -eq 0 ] && [ -x "$node" ]; then 57 | (exit 0) 58 | else 59 | echo "npm cannot be installed without node.js." >&2 60 | echo "Install node first, and then try again." >&2 61 | echo "" >&2 62 | echo "Maybe node is installed, but not in the PATH?" >&2 63 | echo "Note that running as sudo can change envs." >&2 64 | echo "" 65 | echo "PATH=$PATH" >&2 66 | exit $ret 67 | fi 68 | 69 | # set the temp dir 70 | TMP="${TMPDIR}" 71 | if [ "x$TMP" = "x" ]; then 72 | TMP="/tmp" 73 | fi 74 | TMP="${TMP}/npm.$$" 75 | rm -rf "$TMP" || true 76 | mkdir "$TMP" 77 | if [ $? -ne 0 ]; then 78 | echo "failed to mkdir $TMP" >&2 79 | exit 1 80 | fi 81 | 82 | BACK="$PWD" 83 | 84 | ret=0 85 | tar="${TAR}" 86 | if [ -z "$tar" ]; then 87 | tar="${npm_config_tar}" 88 | fi 89 | if [ -z "$tar" ]; then 90 | tar=`which tar 2>&1` 91 | ret=$? 92 | fi 93 | 94 | if [ $ret -eq 0 ] && [ -x "$tar" ]; then 95 | echo "tar=$tar" 96 | echo "version:" 97 | $tar --version 98 | ret=$? 99 | fi 100 | 101 | if [ $ret -eq 0 ]; then 102 | (exit 0) 103 | else 104 | echo "No suitable tar program found." 105 | exit 1 106 | fi 107 | 108 | 109 | 110 | # Try to find a suitable make 111 | # If the MAKE environment var is set, use that. 112 | # otherwise, try to find gmake, and then make. 113 | # If no make is found, then just execute the necessary commands. 114 | 115 | # XXX For some reason, make is building all the docs every time. This 116 | # is an annoying source of bugs. Figure out why this happens. 117 | MAKE=NOMAKE 118 | 119 | if [ "x$MAKE" = "x" ]; then 120 | make=`which gmake 2>&1` 121 | if [ $? -eq 0 ] && [ -x "$make" ]; then 122 | (exit 0) 123 | else 124 | make=`which make 2>&1` 125 | if [ $? -eq 0 ] && [ -x "$make" ]; then 126 | (exit 0) 127 | else 128 | make=NOMAKE 129 | fi 130 | fi 131 | else 132 | make="$MAKE" 133 | fi 134 | 135 | if [ -x "$make" ]; then 136 | (exit 0) 137 | else 138 | # echo "Installing without make. This may fail." >&2 139 | make=NOMAKE 140 | fi 141 | 142 | # If there's no bash, then don't even try to clean 143 | if [ -x "/bin/bash" ]; then 144 | (exit 0) 145 | else 146 | clean="no" 147 | fi 148 | 149 | node_version=`"$node" --version 2>&1` 150 | ret=$? 151 | if [ $ret -ne 0 ]; then 152 | echo "You need node to run this program." >&2 153 | echo "node --version reports: $node_version" >&2 154 | echo "with exit code = $ret" >&2 155 | echo "Please install node before continuing." >&2 156 | exit $ret 157 | fi 158 | 159 | t="${npm_install}" 160 | if [ -z "$t" ]; then 161 | # switch based on node version. 162 | # note that we can only use strict sh-compatible patterns here. 163 | case $node_version in 164 | 0.[01234567].* | v0.[01234567].*) 165 | echo "You are using an outdated and unsupported version of" >&2 166 | echo "node ($node_version). Please update node and try again." >&2 167 | exit 99 168 | ;; 169 | *) 170 | echo "install npm@latest" 171 | t="latest" 172 | ;; 173 | esac 174 | fi 175 | 176 | # need to echo "" after, because Posix sed doesn't treat EOF 177 | # as an implied end of line. 178 | url=`(curl -SsL https://registry.npmjs.org/npm/$t; echo "") \ 179 | | sed -e 's/^.*tarball":"//' \ 180 | | sed -e 's/".*$//'` 181 | 182 | ret=$? 183 | if [ "x$url" = "x" ]; then 184 | ret=125 185 | # try without the -e arg to sed. 186 | url=`(curl -SsL https://registry.npmjs.org/npm/$t; echo "") \ 187 | | sed 's/^.*tarball":"//' \ 188 | | sed 's/".*$//'` 189 | ret=$? 190 | if [ "x$url" = "x" ]; then 191 | ret=125 192 | fi 193 | fi 194 | if [ $ret -ne 0 ]; then 195 | echo "Failed to get tarball url for npm/$t" >&2 196 | exit $ret 197 | fi 198 | 199 | 200 | echo "fetching: $url" >&2 201 | 202 | cd "$TMP" \ 203 | && curl -SsL "$url" \ 204 | | $tar -xzf - \ 205 | && cd "$TMP"/* \ 206 | && (ver=`"$node" bin/read-package-json.js package.json version` 207 | isnpm10=0 208 | if [ $ret -eq 0 ]; then 209 | if [ -d node_modules ]; then 210 | if "$node" node_modules/semver/bin/semver -v "$ver" -r "1" 211 | then 212 | isnpm10=1 213 | fi 214 | else 215 | if "$node" bin/semver -v "$ver" -r ">=1.0"; then 216 | isnpm10=1 217 | fi 218 | fi 219 | fi 220 | 221 | ret=0 222 | if [ $isnpm10 -eq 1 ] && [ -f "scripts/clean-old.sh" ]; then 223 | if [ "x$skipclean" = "x" ]; then 224 | (exit 0) 225 | else 226 | clean=no 227 | fi 228 | if [ "x$clean" = "xno" ] \ 229 | || [ "x$clean" = "xn" ]; then 230 | echo "Skipping 0.x cruft clean" >&2 231 | ret=0 232 | elif [ "x$clean" = "xy" ] || [ "x$clean" = "xyes" ]; then 233 | NODE="$node" /bin/bash "scripts/clean-old.sh" "-y" 234 | ret=$? 235 | else 236 | NODE="$node" /bin/bash "scripts/clean-old.sh" </dev/tty 237 | ret=$? 238 | fi 239 | fi 240 | 241 | if [ $ret -ne 0 ]; then 242 | echo "Aborted 0.x cleanup. Exiting." >&2 243 | exit $ret 244 | fi) \ 245 | && (if [ "x$configures" = "x" ]; then 246 | (exit 0) 247 | else 248 | echo "./configure $configures" 249 | echo "$configures" > npmrc 250 | fi) \ 251 | && (if [ "$make" = "NOMAKE" ]; then 252 | (exit 0) 253 | elif "$make" uninstall install; then 254 | (exit 0) 255 | else 256 | make="NOMAKE" 257 | fi 258 | if [ "$make" = "NOMAKE" ]; then 259 | "$node" cli.js rm npm -gf 260 | "$node" cli.js install -gf 261 | fi) \ 262 | && cd "$BACK" \ 263 | && rm -rf "$TMP" \ 264 | && echo "It worked" 265 | 266 | ret=$? 267 | if [ $ret -ne 0 ]; then 268 | echo "It failed" >&2 269 | fi 270 | exit $ret 271 | -------------------------------------------------------------------------------- /examples/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # script for creating a zip and tarball for inclusion in node 4 | 5 | unset CDPATH 6 | 7 | set -e 8 | 9 | rm -rf release *.tgz || true 10 | mkdir release 11 | node ./cli.js pack --loglevel error >/dev/null 12 | mv *.tgz release 13 | cd release 14 | tar xzf *.tgz 15 | 16 | mkdir node_modules 17 | mv package node_modules/npm 18 | 19 | # make the zip for windows users 20 | cp node_modules/npm/bin/*.cmd . 21 | zipname=npm-$(node ../cli.js -v).zip 22 | zip -q -9 -r -X "$zipname" *.cmd node_modules 23 | 24 | # make the tar for node's deps 25 | cd node_modules 26 | tarname=npm-$(node ../../cli.js -v).tgz 27 | tar czf "$tarname" npm 28 | 29 | cd .. 30 | mv "node_modules/$tarname" . 31 | 32 | rm -rf *.cmd 33 | rm -rf node_modules 34 | 35 | echo "release/$tarname" 36 | echo "release/$zipname" 37 | -------------------------------------------------------------------------------- /examples/relocate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Change the cli shebang to point at the specified node 4 | # Useful for when the program is moved around after install. 5 | # Also used by the default 'make install' in node to point 6 | # npm at the newly installed node, rather than the first one 7 | # in the PATH, which would be the default otherwise. 8 | 9 | # bash /path/to/npm/scripts/relocate.sh $nodepath 10 | # If $nodepath is blank, then it'll use /usr/bin/env 11 | 12 | dir="$(dirname "$(dirname "$0")")" 13 | cli="$dir"/bin/npm-cli.js 14 | tmp="$cli".tmp 15 | 16 | node="$1" 17 | if [ "x$node" = "x" ]; then 18 | node="/usr/bin/env node" 19 | fi 20 | node="#!$node" 21 | 22 | sed -e 1d "$cli" > "$tmp" 23 | echo "$node" > "$cli" 24 | cat "$tmp" >> "$cli" 25 | rm "$tmp" 26 | chmod ogu+x $cli 27 | -------------------------------------------------------------------------------- /examples/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | function usage { 6 | cat <<-EOF 7 | USAGE 8 | 9 | $0 [-dgGhv] [-f focus-string] [-s seed] 10 | 11 | OPTIONS 12 | 13 | -h print this message 14 | 15 | -b run make under scan-build static analyzer 16 | 17 | -d run tests in a debugger (either lldb or gdb) 18 | 19 | -g run tests with valgrind's memcheck tool 20 | 21 | -G run tests with valgrind's memcheck tool, including a full leak check 22 | 23 | -v run tests with verbose output 24 | 25 | -f run only tests whose description contain the given string 26 | 27 | -s set the seed used to control random behavior 28 | 29 | -z pipe tests' stderr to \`dot(1)\` to render an SVG log 30 | 31 | 32 | EOF 33 | } 34 | 35 | profile= 36 | leak_check=no 37 | mode=normal 38 | verbose= 39 | args=() 40 | target=tests 41 | export BUILDTYPE=Test 42 | cmd="out/${BUILDTYPE}/${target}" 43 | run_scan_build= 44 | 45 | if [ "$(uname -s)" == "Darwin" ]; then 46 | export LINK="clang++ -fsanitize=address" 47 | fi 48 | 49 | while getopts "bdf:s:gGhpvS" option; do 50 | case ${option} in 51 | h) 52 | usage 53 | exit 54 | ;; 55 | d) 56 | mode=debug 57 | ;; 58 | g) 59 | mode=valgrind 60 | ;; 61 | G) 62 | mode=valgrind 63 | leak_check=full 64 | ;; 65 | p) 66 | profile=true 67 | ;; 68 | f) 69 | args+=("--only=${OPTARG}") 70 | ;; 71 | v) 72 | verbose=true 73 | ;; 74 | s) 75 | export TREE_SITTER_SEED=${OPTARG} 76 | ;; 77 | S) 78 | mode=SVG 79 | ;; 80 | b) 81 | run_scan_build=true 82 | ;; 83 | esac 84 | done 85 | 86 | if [[ -n $verbose ]]; then 87 | args+=("--reporter=spec") 88 | else 89 | args+=("--reporter=singleline") 90 | fi 91 | 92 | if [[ -n "$run_scan_build" ]]; then 93 | . script/util/scan-build.sh 94 | scan_build make -j2 $target 95 | else 96 | make -j2 $target 97 | fi 98 | args=${args:-""} 99 | 100 | if [[ -n $profile ]]; then 101 | export CPUPROFILE=/tmp/${target}-$(date '+%s').prof 102 | fi 103 | 104 | case ${mode} in 105 | valgrind) 106 | valgrind \ 107 | --suppressions=./script/util/valgrind.supp \ 108 | --dsymutil=yes \ 109 | --leak-check=${leak_check} \ 110 | $cmd "${args[@]}" 2>&1 | \ 111 | grep --color -E '\w+_tests?.cc:\d+|$' 112 | ;; 113 | 114 | debug) 115 | if which -s lldb; then 116 | lldb $cmd -- "${args[@]}" 117 | elif which -s gdb; then 118 | gdb $cmd -- "${args[@]}" 119 | else 120 | echo "No debugger found" 121 | exit 1 122 | fi 123 | ;; 124 | 125 | SVG) 126 | echo "<!DOCTYPE html><style>svg { width: 100%; margin-bottom: 20px; }</style>" > index.html 127 | $cmd "${args[@]}" 2> >(grep -v 'Assertion failed' | dot -Tsvg >> index.html) 128 | echo "Wrote index.html" 129 | ;; 130 | 131 | normal) 132 | time $cmd "${args[@]}" 133 | ;; 134 | esac 135 | 136 | if [[ -n $profile ]]; then 137 | pprof $cmd $CPUPROFILE 138 | fi 139 | -------------------------------------------------------------------------------- /examples/update-authors.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git log --reverse --format='%aN <%aE>' | perl -wnE ' 4 | BEGIN { 5 | say "# Authors sorted by whether or not they\x27re me"; 6 | } 7 | 8 | print $seen{$_} = $_ unless $seen{$_} 9 | ' > AUTHORS 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tree-sitter/tree-sitter-bash 2 | 3 | go 1.22 4 | 5 | require github.com/tree-sitter/go-tree-sitter v0.24.0 6 | 7 | require github.com/mattn/go-pointer v0.0.1 // indirect 8 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= 4 | github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 8 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 9 | github.com/tree-sitter/go-tree-sitter v0.24.0 h1:kRZb6aBNfcI/u0Qh8XEt3zjNVnmxTisDBN+kXK0xRYQ= 10 | github.com/tree-sitter/go-tree-sitter v0.24.0/go.mod h1:x681iFVoLMEwOSIHA1chaLkXlroXEN7WY+VHGFaoDbk= 11 | github.com/tree-sitter/tree-sitter-c v0.21.5-0.20240818205408-927da1f210eb h1:A8425heRM8mylnv4H58FPUiH+aYivyitre0PzxrfmWs= 12 | github.com/tree-sitter/tree-sitter-c v0.21.5-0.20240818205408-927da1f210eb/go.mod h1:dOF6gtQiF9UwNh995T5OphYmtIypkjsp3ap7r9AN/iA= 13 | github.com/tree-sitter/tree-sitter-cpp v0.22.4-0.20240818224355-b1a4e2b25148 h1:AfFPZwtwGN01BW1jDdqBVqscTwetvMpydqYZz57RSlc= 14 | github.com/tree-sitter/tree-sitter-cpp v0.22.4-0.20240818224355-b1a4e2b25148/go.mod h1:Bh6U3viD57rFXRYIQ+kmiYtr+1Bx0AceypDLJJSyi9s= 15 | github.com/tree-sitter/tree-sitter-embedded-template v0.21.1-0.20240819044651-ffbf64942c33 h1:TwqSV3qLp3tKSqirGLRHnjFk9Tc2oy57LIl+FQ4GjI4= 16 | github.com/tree-sitter/tree-sitter-embedded-template v0.21.1-0.20240819044651-ffbf64942c33/go.mod h1:CvCKCt3v04Ufos1zZnNCelBDeCGRpPucaN8QczoUsN4= 17 | github.com/tree-sitter/tree-sitter-go v0.21.3-0.20240818010209-8c0f0e7a6012 h1:Xvxck3tE5FW7F7bTS97iNM2ADMyCMJztVqn5HYKdJGo= 18 | github.com/tree-sitter/tree-sitter-go v0.21.3-0.20240818010209-8c0f0e7a6012/go.mod h1:T40D0O1cPvUU/+AmiXVXy1cncYQT6wem4Z0g4SfAYvY= 19 | github.com/tree-sitter/tree-sitter-html v0.20.5-0.20240818004741-d11201a263d0 h1:c46K6uh5Dz00zJeU9BfjXdb8I+E4RkUdfnWJpQADXFo= 20 | github.com/tree-sitter/tree-sitter-html v0.20.5-0.20240818004741-d11201a263d0/go.mod h1:hcNt/kOJHcIcuMvouE7LJcYdeFUFbVpBJ6d4wmOA+tU= 21 | github.com/tree-sitter/tree-sitter-java v0.21.1-0.20240824015150-576d8097e495 h1:jrt4qbJVEFs4H93/ITxygHc6u0TGqAkkate7TQ4wFSA= 22 | github.com/tree-sitter/tree-sitter-java v0.21.1-0.20240824015150-576d8097e495/go.mod h1:oyaR7fLnRV0hT9z6qwE9GkaeTom/hTDwK3H2idcOJFc= 23 | github.com/tree-sitter/tree-sitter-javascript v0.21.5-0.20240818005344-15887341e5b5 h1:om4X9AVg3asL8gxNJDcz4e/Wp+VpQj1PY3uJXKr6EOg= 24 | github.com/tree-sitter/tree-sitter-javascript v0.21.5-0.20240818005344-15887341e5b5/go.mod h1:nNqgPoV/h9uYWk6kYEFdEAhNVOacpfpRW5SFmdaP4tU= 25 | github.com/tree-sitter/tree-sitter-json v0.21.1-0.20240818005659-bdd69eb8c8a5 h1:pfV3G3k7NCKqKk8THBmyuh2zA33lgYHS3GVrzRR8ry4= 26 | github.com/tree-sitter/tree-sitter-json v0.21.1-0.20240818005659-bdd69eb8c8a5/go.mod h1:GbMKRjLfk0H+PI7nLi1Sx5lHf5wCpLz9al8tQYSxpEk= 27 | github.com/tree-sitter/tree-sitter-php v0.22.9-0.20240819002312-a552625b56c1 h1:ZXZMDwE+IhUtGug4Brv6NjJWUU3rfkZBKpemf6RY8/g= 28 | github.com/tree-sitter/tree-sitter-php v0.22.9-0.20240819002312-a552625b56c1/go.mod h1:UKCLuYnJ312Mei+3cyTmGOHzn0YAnaPRECgJmHtzrqs= 29 | github.com/tree-sitter/tree-sitter-python v0.21.1-0.20240818005537-55a9b8a4fbfb h1:EXEM82lFM7JjJb6qiKZXkpIDaCcbV2obNn82ghwj9lw= 30 | github.com/tree-sitter/tree-sitter-python v0.21.1-0.20240818005537-55a9b8a4fbfb/go.mod h1:lXCF1nGG5Dr4J3BTS0ObN4xJCCICiSu/b+Xe/VqMV7g= 31 | github.com/tree-sitter/tree-sitter-ruby v0.21.1-0.20240818211811-7dbc1e2d0e2d h1:fcYCvoXdcP1uRQYXqJHRy6Hec+uKScQdKVtMwK9JeCI= 32 | github.com/tree-sitter/tree-sitter-ruby v0.21.1-0.20240818211811-7dbc1e2d0e2d/go.mod h1:T1nShQ4v5AJtozZ8YyAS4uzUtDAJj/iv4YfwXSbUHzg= 33 | github.com/tree-sitter/tree-sitter-rust v0.21.3-0.20240818005432-2b43eafe6447 h1:o9alBu1J/WjrcTKEthYtXmdkDc5OVXD+PqlvnEZ0Lzc= 34 | github.com/tree-sitter/tree-sitter-rust v0.21.3-0.20240818005432-2b43eafe6447/go.mod h1:1Oh95COkkTn6Ezp0vcMbvfhRP5gLeqqljR0BYnBzWvc= 35 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 36 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 37 | -------------------------------------------------------------------------------- /grammar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Bash grammar for tree-sitter 3 | * @author Max Brunsfeld <maxbrunsfeld@gmail.com> 4 | * @author Amaan Qureshi <amaanq12@gmail.com> 5 | * @license MIT 6 | */ 7 | 8 | /// <reference types="tree-sitter-cli/dsl" /> 9 | // @ts-check 10 | 11 | const SPECIAL_CHARACTERS = [ 12 | '\'', '"', 13 | '<', '>', 14 | '{', '}', 15 | '\\[', '\\]', 16 | '(', ')', 17 | '`', '$', 18 | '|', '&', ';', 19 | '\\', 20 | '\\s', 21 | ]; 22 | 23 | const PREC = { 24 | UPDATE: 0, 25 | ASSIGN: 1, 26 | TERNARY: 2, 27 | LOGICAL_OR: 3, 28 | LOGICAL_AND: 4, 29 | BITWISE_OR: 5, 30 | BITWISE_XOR: 6, 31 | BITWISE_AND: 7, 32 | EQUALITY: 8, 33 | COMPARE: 9, 34 | TEST: 10, 35 | UNARY: 11, 36 | SHIFT: 12, 37 | ADD: 13, 38 | MULTIPLY: 14, 39 | EXPONENT: 15, 40 | NEGATE: 16, 41 | PREFIX: 17, 42 | POSTFIX: 18, 43 | }; 44 | 45 | module.exports = grammar({ 46 | name: 'bash', 47 | 48 | conflicts: $ => [ 49 | [$._expression, $.command_name], 50 | [$.command, $.variable_assignments], 51 | [$.redirected_statement, $.command], 52 | [$.redirected_statement, $.command_substitution], 53 | [$.function_definition, $.command_name], 54 | [$.pipeline], 55 | ], 56 | 57 | inline: $ => [ 58 | $._statement, 59 | $._terminator, 60 | $._literal, 61 | $._terminated_statement, 62 | $._primary_expression, 63 | $._simple_variable_name, 64 | $._multiline_variable_name, 65 | $._special_variable_name, 66 | $._c_word, 67 | $._statement_not_subshell, 68 | $._redirect, 69 | ], 70 | 71 | externals: $ => [ 72 | $.heredoc_start, 73 | $.simple_heredoc_body, 74 | $._heredoc_body_beginning, 75 | $.heredoc_content, 76 | $.heredoc_end, 77 | $.file_descriptor, 78 | $._empty_value, 79 | $._concat, 80 | $.variable_name, // Variable name followed by an operator like '=' or '+=' 81 | $.test_operator, 82 | $.regex, 83 | $._regex_no_slash, 84 | $._regex_no_space, 85 | $._expansion_word, 86 | $.extglob_pattern, 87 | $._bare_dollar, 88 | $._brace_start, 89 | $._immediate_double_hash, 90 | $._external_expansion_sym_hash, 91 | $._external_expansion_sym_bang, 92 | $._external_expansion_sym_equal, 93 | '}', 94 | ']', 95 | '<<', 96 | '<<-', 97 | /\n/, 98 | '(', 99 | 'esac', 100 | $.__error_recovery, 101 | ], 102 | 103 | extras: $ => [ 104 | $.comment, 105 | /\s/, 106 | /\\\r?\n/, 107 | /\\( |\t|\v|\f)/, 108 | ], 109 | 110 | supertypes: $ => [ 111 | $._statement, 112 | $._expression, 113 | $._primary_expression, 114 | ], 115 | 116 | word: $ => $.word, 117 | 118 | rules: { 119 | program: $ => optional($._statements), 120 | 121 | _statements: $ => prec(1, seq( 122 | repeat(seq( 123 | $._statement, 124 | $._terminator, 125 | )), 126 | $._statement, 127 | optional($._terminator), 128 | )), 129 | 130 | _terminated_statement: $ => repeat1(seq( 131 | $._statement, 132 | $._terminator, 133 | )), 134 | 135 | // Statements 136 | 137 | _statement: $ => choice( 138 | $._statement_not_subshell, 139 | $.subshell, 140 | ), 141 | 142 | _statement_not_subshell: $ => choice( 143 | $.redirected_statement, 144 | $.variable_assignment, 145 | $.variable_assignments, 146 | $.command, 147 | $.declaration_command, 148 | $.unset_command, 149 | $.test_command, 150 | $.negated_command, 151 | $.for_statement, 152 | $.c_style_for_statement, 153 | $.while_statement, 154 | $.if_statement, 155 | $.case_statement, 156 | $.pipeline, 157 | $.list, 158 | $.compound_statement, 159 | $.function_definition, 160 | ), 161 | 162 | _statement_not_pipeline: $ => prec(1, choice( 163 | $.redirected_statement, 164 | $.variable_assignment, 165 | $.variable_assignments, 166 | $.command, 167 | $.declaration_command, 168 | $.unset_command, 169 | $.test_command, 170 | $.negated_command, 171 | $.for_statement, 172 | $.c_style_for_statement, 173 | $.while_statement, 174 | $.if_statement, 175 | $.case_statement, 176 | $.list, 177 | $.compound_statement, 178 | $.function_definition, 179 | $.subshell, 180 | )), 181 | 182 | redirected_statement: $ => prec.dynamic(-1, prec.right(-1, choice( 183 | seq( 184 | field('body', $._statement), 185 | field('redirect', choice( 186 | repeat1(choice( 187 | $.file_redirect, 188 | $.heredoc_redirect, 189 | )), 190 | )), 191 | ), 192 | seq( 193 | field('body', choice($.if_statement, $.while_statement)), 194 | $.herestring_redirect, 195 | ), 196 | field('redirect', repeat1($._redirect)), 197 | $.herestring_redirect, 198 | ))), 199 | 200 | for_statement: $ => seq( 201 | choice('for', 'select'), 202 | field('variable', $._simple_variable_name), 203 | optional(seq( 204 | 'in', 205 | field('value', repeat1($._literal)), 206 | )), 207 | $._terminator, 208 | field('body', $.do_group), 209 | ), 210 | 211 | c_style_for_statement: $ => seq( 212 | 'for', 213 | '((', 214 | choice($._for_body), 215 | '))', 216 | optional(';'), 217 | field('body', choice( 218 | $.do_group, 219 | $.compound_statement, 220 | )), 221 | ), 222 | _for_body: $ => seq( 223 | field('initializer', commaSep($._c_expression)), 224 | $._c_terminator, 225 | field('condition', commaSep($._c_expression)), 226 | $._c_terminator, 227 | field('update', commaSep($._c_expression)), 228 | ), 229 | 230 | _c_expression: $ => choice( 231 | $._c_expression_not_assignment, 232 | alias($._c_variable_assignment, $.variable_assignment), 233 | ), 234 | _c_expression_not_assignment: $ => choice( 235 | $._c_word, 236 | $.simple_expansion, 237 | $.expansion, 238 | $.number, 239 | $.string, 240 | alias($._c_unary_expression, $.unary_expression), 241 | alias($._c_binary_expression, $.binary_expression), 242 | alias($._c_postfix_expression, $.postfix_expression), 243 | alias($._c_parenthesized_expression, $.parenthesized_expression), 244 | $.command_substitution, 245 | ), 246 | 247 | _c_variable_assignment: $ => seq( 248 | field('name', alias($._c_word, $.variable_name)), 249 | '=', 250 | field('value', $._c_expression), 251 | ), 252 | _c_unary_expression: $ => prec(PREC.PREFIX, seq( 253 | field('operator', choice('++', '--')), 254 | $._c_expression_not_assignment, 255 | )), 256 | _c_binary_expression: $ => { 257 | const table = [ 258 | [choice('+=', '-=', '*=', '/=', '%=', '**=', '<<=', '>>=', '&=', '^=', '|='), PREC.UPDATE], 259 | [choice('||', '-o'), PREC.LOGICAL_OR], 260 | [choice('&&', '-a'), PREC.LOGICAL_AND], 261 | ['|', PREC.BITWISE_OR], 262 | ['^', PREC.BITWISE_XOR], 263 | ['&', PREC.BITWISE_AND], 264 | [choice('==', '!='), PREC.EQUALITY], 265 | [choice('<', '>', '<=', '>='), PREC.COMPARE], 266 | [choice('<<', '>>'), PREC.SHIFT], 267 | [choice('+', '-'), PREC.ADD], 268 | [choice('*', '/', '%'), PREC.MULTIPLY], 269 | ['**', PREC.EXPONENT], 270 | ]; 271 | 272 | return choice(...table.map(([operator, precedence]) => { 273 | // @ts-ignore 274 | return prec[operator === '**' ? 'right' : 'left'](precedence, seq( 275 | field('left', $._c_expression_not_assignment), 276 | // @ts-ignore 277 | field('operator', operator), 278 | field('right', $._c_expression_not_assignment), 279 | )); 280 | })); 281 | }, 282 | _c_postfix_expression: $ => prec(PREC.POSTFIX, seq( 283 | $._c_expression_not_assignment, 284 | field('operator', choice('++', '--')), 285 | )), 286 | _c_parenthesized_expression: $ => seq( 287 | '(', 288 | commaSep1($._c_expression), 289 | ')', 290 | ), 291 | _c_word: $ => alias(/[a-zA-Z_][a-zA-Z0-9_]*/, $.word), 292 | 293 | while_statement: $ => seq( 294 | choice('while', 'until'), 295 | field('condition', $._terminated_statement), 296 | field('body', $.do_group), 297 | ), 298 | 299 | do_group: $ => seq( 300 | 'do', 301 | optional($._terminated_statement), 302 | 'done', 303 | ), 304 | 305 | if_statement: $ => seq( 306 | 'if', 307 | field('condition', $._terminated_statement), 308 | 'then', 309 | optional($._terminated_statement), 310 | repeat($.elif_clause), 311 | optional($.else_clause), 312 | 'fi', 313 | ), 314 | 315 | elif_clause: $ => seq( 316 | 'elif', 317 | $._terminated_statement, 318 | 'then', 319 | optional($._terminated_statement), 320 | ), 321 | 322 | else_clause: $ => seq( 323 | 'else', 324 | optional($._terminated_statement), 325 | ), 326 | 327 | case_statement: $ => seq( 328 | 'case', 329 | field('value', $._literal), 330 | optional($._terminator), 331 | 'in', 332 | optional($._terminator), 333 | optional(seq( 334 | repeat($.case_item), 335 | alias($.last_case_item, $.case_item), 336 | )), 337 | 'esac', 338 | ), 339 | 340 | case_item: $ => seq( 341 | choice( 342 | seq( 343 | optional('('), 344 | field('value', choice($._literal, $._extglob_blob)), 345 | repeat(seq('|', field('value', choice($._literal, $._extglob_blob)))), 346 | ')', 347 | ), 348 | ), 349 | optional($._statements), 350 | prec(1, choice( 351 | field('termination', ';;'), 352 | field('fallthrough', choice(';&', ';;&')), 353 | )), 354 | ), 355 | 356 | last_case_item: $ => seq( 357 | optional('('), 358 | field('value', choice($._literal, $._extglob_blob)), 359 | repeat(seq('|', field('value', choice($._literal, $._extglob_blob)))), 360 | ')', 361 | optional($._statements), 362 | optional(prec(1, ';;')), 363 | ), 364 | 365 | function_definition: $ => prec.right(seq( 366 | choice( 367 | seq( 368 | 'function', 369 | field('name', $.word), 370 | optional(seq('(', ')')), 371 | ), 372 | seq( 373 | field('name', $.word), 374 | '(', ')', 375 | ), 376 | ), 377 | field( 378 | 'body', 379 | choice( 380 | $.compound_statement, 381 | $.subshell, 382 | $.test_command, 383 | $.if_statement, 384 | ), 385 | ), 386 | field('redirect', optional($._redirect)), 387 | )), 388 | 389 | compound_statement: $ => seq( 390 | '{', 391 | optional($._terminated_statement), 392 | token(prec(-1, '}')), 393 | ), 394 | 395 | subshell: $ => seq( 396 | '(', 397 | $._statements, 398 | ')', 399 | ), 400 | 401 | pipeline: $ => prec.right(seq( 402 | $._statement_not_pipeline, 403 | repeat1(seq( 404 | choice('|', '|&'), 405 | $._statement_not_pipeline, 406 | )), 407 | )), 408 | 409 | list: $ => prec.left(-1, seq( 410 | $._statement, 411 | choice('&&', '||'), 412 | $._statement, 413 | )), 414 | 415 | // Commands 416 | 417 | negated_command: $ => seq( 418 | '!', 419 | choice( 420 | prec(2, $.command), 421 | prec(1, $.variable_assignment), 422 | $.test_command, 423 | $.subshell, 424 | ), 425 | ), 426 | 427 | test_command: $ => seq( 428 | choice( 429 | seq('[', optional(choice($._expression, $.redirected_statement)), ']'), 430 | seq( 431 | '[[', 432 | choice( 433 | $._expression, 434 | alias($._test_command_binary_expression, $.binary_expression), 435 | ), 436 | ']]', 437 | ), 438 | seq('((', optional($._expression), '))'), 439 | ), 440 | ), 441 | 442 | _test_command_binary_expression: $ => prec(PREC.ASSIGN, 443 | seq( 444 | field('left', $._expression), 445 | field('operator', '='), 446 | field('right', alias($._regex_no_space, $.regex)), 447 | ), 448 | ), 449 | 450 | declaration_command: $ => prec.left(seq( 451 | choice('declare', 'typeset', 'export', 'readonly', 'local'), 452 | repeat(choice( 453 | $._literal, 454 | $._simple_variable_name, 455 | $.variable_assignment, 456 | )), 457 | )), 458 | 459 | unset_command: $ => prec.left(seq( 460 | choice('unset', 'unsetenv'), 461 | repeat(choice( 462 | $._literal, 463 | $._simple_variable_name, 464 | )), 465 | )), 466 | 467 | command: $ => prec.left(seq( 468 | repeat(choice( 469 | $.variable_assignment, 470 | field('redirect', $._redirect), 471 | )), 472 | field('name', $.command_name), 473 | choice( 474 | repeat(choice( 475 | field('argument', $._literal), 476 | field('argument', alias($._bare_dollar, '$')), 477 | field('argument', seq( 478 | choice('=~', '=='), 479 | choice($._literal, $.regex), 480 | )), 481 | field('redirect', $.herestring_redirect), 482 | )), 483 | $.subshell, 484 | ), 485 | )), 486 | 487 | command_name: $ => $._literal, 488 | 489 | variable_assignment: $ => seq( 490 | field('name', choice( 491 | $.variable_name, 492 | $.subscript, 493 | )), 494 | choice( 495 | '=', 496 | '+=', 497 | ), 498 | field('value', choice( 499 | $._literal, 500 | $.array, 501 | $._empty_value, 502 | alias($._comment_word, $.word), 503 | )), 504 | ), 505 | 506 | variable_assignments: $ => seq($.variable_assignment, repeat1($.variable_assignment)), 507 | 508 | subscript: $ => seq( 509 | field('name', $.variable_name), 510 | '[', 511 | field('index', choice($._literal, $.binary_expression, $.unary_expression, $.parenthesized_expression)), 512 | optional($._concat), 513 | ']', 514 | optional($._concat), 515 | ), 516 | 517 | file_redirect: $ => prec.left(seq( 518 | field('descriptor', optional($.file_descriptor)), 519 | choice( 520 | seq( 521 | choice('<', '>', '>>', '&>', '&>>', '<&', '>&', '>|'), 522 | field('destination', repeat1($._literal)), 523 | ), 524 | seq( 525 | choice('<&-', '>&-'), // close file descriptor 526 | optional(field('destination', $._literal)), 527 | ), 528 | ), 529 | )), 530 | 531 | heredoc_redirect: $ => seq( 532 | field('descriptor', optional($.file_descriptor)), 533 | choice('<<', '<<-'), 534 | $.heredoc_start, 535 | optional(choice( 536 | alias($._heredoc_pipeline, $.pipeline), 537 | seq( 538 | field('redirect', repeat1($._redirect)), 539 | optional($._heredoc_expression), 540 | ), 541 | $._heredoc_expression, 542 | $._heredoc_command, 543 | )), 544 | /\n/, 545 | choice($._heredoc_body, $._simple_heredoc_body), 546 | ), 547 | 548 | _heredoc_pipeline: $ => seq( 549 | choice('|', '|&'), 550 | $._statement, 551 | ), 552 | 553 | _heredoc_expression: $ => seq( 554 | field('operator', choice('||', '&&')), 555 | field('right', $._statement), 556 | ), 557 | 558 | _heredoc_command: $ => repeat1(field('argument', $._literal)), 559 | 560 | _heredoc_body: $ => seq( 561 | $.heredoc_body, 562 | $.heredoc_end, 563 | ), 564 | 565 | heredoc_body: $ => seq( 566 | $._heredoc_body_beginning, 567 | repeat(choice( 568 | $.expansion, 569 | $.simple_expansion, 570 | $.command_substitution, 571 | $.heredoc_content, 572 | )), 573 | ), 574 | 575 | _simple_heredoc_body: $ => seq( 576 | alias($.simple_heredoc_body, $.heredoc_body), 577 | $.heredoc_end, 578 | ), 579 | 580 | herestring_redirect: $ => prec.left(seq( 581 | field('descriptor', optional($.file_descriptor)), 582 | '<<<', 583 | $._literal, 584 | )), 585 | 586 | _redirect: $ => choice($.file_redirect, $.herestring_redirect), 587 | 588 | // Expressions 589 | 590 | _expression: $ => choice( 591 | $._literal, 592 | $.unary_expression, 593 | $.ternary_expression, 594 | $.binary_expression, 595 | $.postfix_expression, 596 | $.parenthesized_expression, 597 | ), 598 | 599 | // https://tldp.org/LDP/abs/html/opprecedence.html 600 | binary_expression: $ => { 601 | const table = [ 602 | [choice('+=', '-=', '*=', '/=', '%=', '**=', '<<=', '>>=', '&=', '^=', '|='), PREC.UPDATE], 603 | [choice('=', '=~'), PREC.ASSIGN], 604 | ['||', PREC.LOGICAL_OR], 605 | ['&&', PREC.LOGICAL_AND], 606 | ['|', PREC.BITWISE_OR], 607 | ['^', PREC.BITWISE_XOR], 608 | ['&', PREC.BITWISE_AND], 609 | [choice('==', '!='), PREC.EQUALITY], 610 | [choice('<', '>', '<=', '>='), PREC.COMPARE], 611 | [$.test_operator, PREC.TEST], 612 | [choice('<<', '>>'), PREC.SHIFT], 613 | [choice('+', '-'), PREC.ADD], 614 | [choice('*', '/', '%'), PREC.MULTIPLY], 615 | ['**', PREC.EXPONENT], 616 | ]; 617 | 618 | return choice( 619 | choice(...table.map(([operator, precedence]) => { 620 | // @ts-ignore 621 | return prec[operator === '**' ? 'right' : 'left'](precedence, seq( 622 | field('left', $._expression), 623 | // @ts-ignore 624 | field('operator', operator), 625 | field('right', $._expression), 626 | )); 627 | })), 628 | prec(PREC.ASSIGN, seq( 629 | field('left', $._expression), 630 | field('operator', '=~'), 631 | field('right', alias($._regex_no_space, $.regex)), 632 | )), 633 | prec(PREC.EQUALITY, seq( 634 | field('left', $._expression), 635 | field('operator', choice('==', '!=')), 636 | field('right', $._extglob_blob), 637 | )), 638 | ); 639 | }, 640 | 641 | ternary_expression: $ => prec.left(PREC.TERNARY, seq( 642 | field('condition', $._expression), 643 | '?', 644 | field('consequence', $._expression), 645 | ':', 646 | field('alternative', $._expression), 647 | )), 648 | 649 | unary_expression: $ => choice( 650 | prec(PREC.PREFIX, seq( 651 | field('operator', tokenLiterals(1, '++', '--')), 652 | $._expression, 653 | )), 654 | prec(PREC.UNARY, seq( 655 | field('operator', tokenLiterals(1, '-', '+', '~')), 656 | $._expression, 657 | )), 658 | prec.right(PREC.UNARY, seq( 659 | field('operator', '!'), 660 | $._expression, 661 | )), 662 | prec.right(PREC.TEST, seq( 663 | field('operator', $.test_operator), 664 | $._expression, 665 | )), 666 | ), 667 | 668 | postfix_expression: $ => prec(PREC.POSTFIX, seq( 669 | $._expression, 670 | field('operator', choice('++', '--')), 671 | )), 672 | 673 | parenthesized_expression: $ => seq( 674 | '(', 675 | $._expression, 676 | ')', 677 | ), 678 | 679 | // Literals 680 | 681 | _literal: $ => choice( 682 | $.concatenation, 683 | $._primary_expression, 684 | alias(prec(-2, repeat1($._special_character)), $.word), 685 | ), 686 | 687 | _primary_expression: $ => choice( 688 | $.word, 689 | alias($.test_operator, $.word), 690 | $.string, 691 | $.raw_string, 692 | $.translated_string, 693 | $.ansi_c_string, 694 | $.number, 695 | $.expansion, 696 | $.simple_expansion, 697 | $.command_substitution, 698 | $.process_substitution, 699 | $.arithmetic_expansion, 700 | $.brace_expression, 701 | ), 702 | 703 | arithmetic_expansion: $ => choice( 704 | seq(choice('$((', '(('), commaSep1($._arithmetic_expression), '))'), 705 | seq('$[', $._arithmetic_expression, ']'), 706 | ), 707 | 708 | brace_expression: $ => seq( 709 | alias($._brace_start, '{'), 710 | alias(token.immediate(/\d+/), $.number), 711 | token.immediate('..'), 712 | alias(token.immediate(/\d+/), $.number), 713 | token.immediate('}'), 714 | ), 715 | 716 | _arithmetic_expression: $ => prec(1, choice( 717 | $._arithmetic_literal, 718 | alias($._arithmetic_unary_expression, $.unary_expression), 719 | alias($._arithmetic_ternary_expression, $.ternary_expression), 720 | alias($._arithmetic_binary_expression, $.binary_expression), 721 | alias($._arithmetic_postfix_expression, $.postfix_expression), 722 | alias($._arithmetic_parenthesized_expression, $.parenthesized_expression), 723 | $.command_substitution, 724 | )), 725 | 726 | _arithmetic_literal: $ => prec(1, choice( 727 | $.number, 728 | $.subscript, 729 | $.simple_expansion, 730 | $.expansion, 731 | $._simple_variable_name, 732 | $.variable_name, 733 | $.string, 734 | )), 735 | 736 | _arithmetic_binary_expression: $ => { 737 | const table = [ 738 | [choice('+=', '-=', '*=', '/=', '%=', '**=', '<<=', '>>=', '&=', '^=', '|='), PREC.UPDATE], 739 | [choice('=', '=~'), PREC.ASSIGN], 740 | ['||', PREC.LOGICAL_OR], 741 | ['&&', PREC.LOGICAL_AND], 742 | ['|', PREC.BITWISE_OR], 743 | ['^', PREC.BITWISE_XOR], 744 | ['&', PREC.BITWISE_AND], 745 | [choice('==', '!='), PREC.EQUALITY], 746 | [choice('<', '>', '<=', '>='), PREC.COMPARE], 747 | [choice('<<', '>>'), PREC.SHIFT], 748 | [choice('+', '-'), PREC.ADD], 749 | [choice('*', '/', '%'), PREC.MULTIPLY], 750 | ['**', PREC.EXPONENT], 751 | ]; 752 | 753 | return choice(...table.map(([operator, precedence]) => { 754 | // @ts-ignore 755 | return prec.left(precedence, seq( 756 | field('left', $._arithmetic_expression), 757 | // @ts-ignore 758 | field('operator', operator), 759 | field('right', $._arithmetic_expression), 760 | )); 761 | })); 762 | }, 763 | 764 | _arithmetic_ternary_expression: $ => prec.left(PREC.TERNARY, seq( 765 | field('condition', $._arithmetic_expression), 766 | '?', 767 | field('consequence', $._arithmetic_expression), 768 | ':', 769 | field('alternative', $._arithmetic_expression), 770 | )), 771 | 772 | _arithmetic_unary_expression: $ => choice( 773 | prec(PREC.PREFIX, seq( 774 | field('operator', tokenLiterals(1, '++', '--')), 775 | $._arithmetic_expression, 776 | )), 777 | prec(PREC.UNARY, seq( 778 | field('operator', tokenLiterals(1, '-', '+', '~')), 779 | $._arithmetic_expression, 780 | )), 781 | prec.right(PREC.UNARY, seq( 782 | field('operator', '!'), 783 | $._arithmetic_expression, 784 | )), 785 | ), 786 | 787 | _arithmetic_postfix_expression: $ => prec(PREC.POSTFIX, seq( 788 | $._arithmetic_expression, 789 | field('operator', choice('++', '--')), 790 | )), 791 | 792 | _arithmetic_parenthesized_expression: $ => seq( 793 | '(', 794 | $._arithmetic_expression, 795 | ')', 796 | ), 797 | 798 | 799 | concatenation: $ => prec(-1, seq( 800 | choice( 801 | $._primary_expression, 802 | alias($._special_character, $.word), 803 | ), 804 | repeat1(seq( 805 | choice($._concat, alias(/`\s*`/, '``')), 806 | choice( 807 | $._primary_expression, 808 | alias($._special_character, $.word), 809 | alias($._comment_word, $.word), 810 | alias($._bare_dollar, '$'), 811 | ), 812 | )), 813 | optional(seq($._concat, '$')), 814 | )), 815 | 816 | _special_character: _ => token(prec(-1, choice('{', '}', '[', ']'))), 817 | 818 | string: $ => seq( 819 | '"', 820 | repeat(seq( 821 | choice( 822 | seq(optional('$'), $.string_content), 823 | $.expansion, 824 | $.simple_expansion, 825 | $.command_substitution, 826 | $.arithmetic_expansion, 827 | ), 828 | optional($._concat), 829 | )), 830 | optional('$'), 831 | '"', 832 | ), 833 | 834 | string_content: _ => token(prec(-1, /([^"`$\\\r\n]|\\(.|\r?\n))+/)), 835 | 836 | translated_string: $ => seq('$', $.string), 837 | 838 | array: $ => seq( 839 | '(', 840 | repeat($._literal), 841 | ')', 842 | ), 843 | 844 | raw_string: _ => /'[^']*'/, 845 | 846 | ansi_c_string: _ => /\$'([^']|\\')*'/, 847 | 848 | number: $ => choice( 849 | /-?(0x)?[0-9]+(#[0-9A-Za-z@_]+)?/, 850 | // the base can be an expansion or command substitution 851 | seq(/-?(0x)?[0-9]+#/, choice($.expansion, $.command_substitution)), 852 | ), 853 | 854 | simple_expansion: $ => seq( 855 | '$', 856 | choice( 857 | $._simple_variable_name, 858 | $._multiline_variable_name, 859 | $._special_variable_name, 860 | $.variable_name, 861 | alias('!', $.special_variable_name), 862 | alias('#', $.special_variable_name), 863 | ), 864 | ), 865 | 866 | string_expansion: $ => seq('$', $.string), 867 | 868 | expansion: $ => seq( 869 | '${', 870 | optional($._expansion_body), 871 | '}', 872 | ), 873 | _expansion_body: $ => choice( 874 | // ${!##} ${!#} 875 | repeat1(field( 876 | 'operator', 877 | choice( 878 | alias($._external_expansion_sym_hash, '#'), 879 | alias($._external_expansion_sym_bang, '!'), 880 | alias($._external_expansion_sym_equal, '='), 881 | ), 882 | )), 883 | seq( 884 | optional(field('operator', token.immediate('!'))), 885 | choice($.variable_name, $._simple_variable_name, $._special_variable_name, $.subscript), 886 | choice( 887 | $._expansion_expression, 888 | $._expansion_regex, 889 | $._expansion_regex_replacement, 890 | $._expansion_regex_removal, 891 | $._expansion_max_length, 892 | $._expansion_operator, 893 | ), 894 | ), 895 | seq( 896 | field('operator', token.immediate('!')), 897 | choice($._simple_variable_name, $.variable_name), 898 | optional(field('operator', choice( 899 | token.immediate('@'), 900 | token.immediate('*'), 901 | ))), 902 | ), 903 | seq( 904 | optional(field('operator', immediateLiterals('#', '!', '='))), 905 | choice( 906 | $.subscript, 907 | $._simple_variable_name, 908 | $._special_variable_name, 909 | $.command_substitution, 910 | ), 911 | repeat(field( 912 | 'operator', 913 | choice( 914 | alias($._external_expansion_sym_hash, '#'), 915 | alias($._external_expansion_sym_bang, '!'), 916 | alias($._external_expansion_sym_equal, '='), 917 | ), 918 | )), 919 | ), 920 | ), 921 | 922 | _expansion_expression: $ => prec(1, seq( 923 | field('operator', immediateLiterals('=', ':=', '-', ':-', '+', ':+', '?', ':?')), 924 | optional(seq( 925 | choice( 926 | alias($._concatenation_in_expansion, $.concatenation), 927 | $.command_substitution, 928 | $.word, 929 | $.expansion, 930 | $.simple_expansion, 931 | $.array, 932 | $.string, 933 | $.raw_string, 934 | $.ansi_c_string, 935 | alias($._expansion_word, $.word), 936 | ), 937 | )), 938 | )), 939 | 940 | _expansion_regex: $ => seq( 941 | field('operator', choice('#', alias($._immediate_double_hash, '##'), '%', '%%')), 942 | repeat(choice( 943 | $.regex, 944 | alias(')', $.regex), 945 | $.string, 946 | $.raw_string, 947 | alias(/\s+/, $.regex), 948 | )), 949 | ), 950 | 951 | _expansion_regex_replacement: $ => seq( 952 | field('operator', choice('/', '//', '/#', '/%')), 953 | optional(choice( 954 | alias($._regex_no_slash, $.regex), 955 | $.string, 956 | $.command_substitution, 957 | seq($.string, alias($._regex_no_slash, $.regex)), 958 | )), 959 | // This can be elided 960 | optional(seq( 961 | field('operator', '/'), 962 | optional(seq( 963 | choice( 964 | $._primary_expression, 965 | alias(prec(-2, repeat1($._special_character)), $.word), 966 | seq($.command_substitution, alias($._expansion_word, $.word)), 967 | alias($._expansion_word, $.word), 968 | alias($._concatenation_in_expansion, $.concatenation), 969 | $.array, 970 | ), 971 | field('operator', optional('/')), 972 | )), 973 | )), 974 | ), 975 | 976 | _expansion_regex_removal: $ => seq( 977 | field('operator', choice(',', ',,', '^', '^^')), 978 | optional($.regex), 979 | ), 980 | 981 | _expansion_max_length: $ => seq( 982 | field('operator', ':'), 983 | optional(choice( 984 | $._simple_variable_name, 985 | $.number, 986 | $.arithmetic_expansion, 987 | $.expansion, 988 | $.parenthesized_expression, 989 | $.command_substitution, 990 | alias($._expansion_max_length_binary_expression, $.binary_expression), 991 | /\n/, 992 | )), 993 | optional(seq( 994 | field('operator', ':'), 995 | optional($.simple_expansion), 996 | optional(choice( 997 | $._simple_variable_name, 998 | $.number, 999 | $.arithmetic_expansion, 1000 | $.expansion, 1001 | $.parenthesized_expression, 1002 | $.command_substitution, 1003 | alias($._expansion_max_length_binary_expression, $.binary_expression), 1004 | /\n/, 1005 | )), 1006 | )), 1007 | ), 1008 | 1009 | _expansion_max_length_expression: $ => choice( 1010 | $._simple_variable_name, 1011 | $.number, 1012 | $.expansion, 1013 | alias($._expansion_max_length_binary_expression, $.binary_expression), 1014 | ), 1015 | _expansion_max_length_binary_expression: $ => { 1016 | const table = [ 1017 | [choice('+', '-'), PREC.ADD], 1018 | [choice('*', '/', '%'), PREC.MULTIPLY], 1019 | ]; 1020 | 1021 | return choice(...table.map(([operator, precedence]) => { 1022 | // @ts-ignore 1023 | return prec.left(precedence, seq( 1024 | $._expansion_max_length_expression, 1025 | // @ts-ignore 1026 | field('operator', operator), 1027 | $._expansion_max_length_expression, 1028 | )); 1029 | })); 1030 | }, 1031 | 1032 | _expansion_operator: _ => seq( 1033 | field('operator', token.immediate('@')), 1034 | field('operator', immediateLiterals('U', 'u', 'L', 'Q', 'E', 'P', 'A', 'K', 'a', 'k')), 1035 | ), 1036 | 1037 | _concatenation_in_expansion: $ => prec(-2, seq( 1038 | choice( 1039 | $.word, 1040 | $.variable_name, 1041 | $.simple_expansion, 1042 | $.expansion, 1043 | $.string, 1044 | $.raw_string, 1045 | $.ansi_c_string, 1046 | $.command_substitution, 1047 | alias($._expansion_word, $.word), 1048 | $.array, 1049 | $.process_substitution, 1050 | ), 1051 | repeat1(seq( 1052 | choice($._concat, alias(/`\s*`/, '``')), 1053 | choice( 1054 | $.word, 1055 | $.variable_name, 1056 | $.simple_expansion, 1057 | $.expansion, 1058 | $.string, 1059 | $.raw_string, 1060 | $.ansi_c_string, 1061 | $.command_substitution, 1062 | alias($._expansion_word, $.word), 1063 | $.array, 1064 | $.process_substitution, 1065 | ), 1066 | )), 1067 | )), 1068 | 1069 | command_substitution: $ => choice( 1070 | seq('$(', $._statements, ')'), 1071 | seq('$(', field('redirect', $.file_redirect), ')'), 1072 | prec(1, seq('`', $._statements, '`')), 1073 | seq('$`', $._statements, '`'), 1074 | ), 1075 | 1076 | process_substitution: $ => seq( 1077 | choice('<(', '>('), 1078 | $._statements, 1079 | ')', 1080 | ), 1081 | 1082 | _extglob_blob: $ => choice( 1083 | $.extglob_pattern, 1084 | seq( 1085 | $.extglob_pattern, 1086 | choice($.string, $.expansion, $.command_substitution), 1087 | optional($.extglob_pattern), 1088 | ), 1089 | ), 1090 | 1091 | comment: _ => token(prec(-10, /#.*/)), 1092 | 1093 | _comment_word: _ => token(prec(-8, seq( 1094 | choice( 1095 | noneOf(...SPECIAL_CHARACTERS), 1096 | seq('\\', noneOf('\\s')), 1097 | ), 1098 | repeat(choice( 1099 | noneOf(...SPECIAL_CHARACTERS), 1100 | seq('\\', noneOf('\\s')), 1101 | '\\ ', 1102 | )), 1103 | ))), 1104 | 1105 | _simple_variable_name: $ => alias(/\w+/, $.variable_name), 1106 | _multiline_variable_name: $ => alias( 1107 | token(prec(-1, /(\w|\\\r?\n)+/)), 1108 | $.variable_name, 1109 | ), 1110 | 1111 | _special_variable_name: $ => alias(choice('*', '@', '?', '!', '#', '-', '$', '0', '_'), $.special_variable_name), 1112 | 1113 | word: _ => token(seq( 1114 | choice( 1115 | noneOf('#', ...SPECIAL_CHARACTERS), 1116 | seq('\\', noneOf('\\s')), 1117 | ), 1118 | repeat(choice( 1119 | noneOf(...SPECIAL_CHARACTERS), 1120 | seq('\\', noneOf('\\s')), 1121 | '\\ ', 1122 | )), 1123 | )), 1124 | 1125 | _c_terminator: _ => choice(';', /\n/, '&'), 1126 | _terminator: _ => choice(';', ';;', /\n/, '&'), 1127 | }, 1128 | }); 1129 | 1130 | /** 1131 | * Returns a regular expression that matches any character except the ones 1132 | * provided. 1133 | * 1134 | * @param {...string} characters 1135 | * 1136 | * @returns {RegExp} 1137 | */ 1138 | function noneOf(...characters) { 1139 | const negatedString = characters.map(c => c == '\\' ? '\\\\' : c).join(''); 1140 | return new RegExp('[^' + negatedString + ']'); 1141 | } 1142 | 1143 | /** 1144 | * Creates a rule to optionally match one or more of the rules separated by a comma 1145 | * 1146 | * @param {RuleOrLiteral} rule 1147 | * 1148 | * @returns {ChoiceRule} 1149 | */ 1150 | function commaSep(rule) { 1151 | return optional(commaSep1(rule)); 1152 | } 1153 | 1154 | /** 1155 | * Creates a rule to match one or more of the rules separated by a comma 1156 | * 1157 | * @param {RuleOrLiteral} rule 1158 | * 1159 | * @returns {SeqRule} 1160 | */ 1161 | function commaSep1(rule) { 1162 | return seq(rule, repeat(seq(',', rule))); 1163 | } 1164 | 1165 | /** 1166 | * 1167 | * Turns a list of rules into a choice of immediate rule 1168 | * 1169 | * @param {(RegExp | string)[]} literals 1170 | * 1171 | * @returns {ChoiceRule} 1172 | */ 1173 | function immediateLiterals(...literals) { 1174 | return choice(...literals.map(l => token.immediate(l))); 1175 | } 1176 | 1177 | /** 1178 | * 1179 | * Turns a list of rules into a choice of aliased token rules 1180 | * 1181 | * @param {number} precedence 1182 | * 1183 | * @param {(RegExp | string)[]} literals 1184 | * 1185 | * @returns {ChoiceRule} 1186 | */ 1187 | function tokenLiterals(precedence, ...literals) { 1188 | return choice(...literals.map(l => token(prec(precedence, l)))); 1189 | } 1190 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tree-sitter-bash", 3 | "version": "0.23.3", 4 | "description": "Bash grammar for tree-sitter", 5 | "repository": "https://github.com/tree-sitter/tree-sitter-bash", 6 | "license": "MIT", 7 | "author": { 8 | "name": "Max Brunsfeld", 9 | "email": "maxbrunsfeld@gmail.com" 10 | }, 11 | "maintainers": [ 12 | { 13 | "name": "Amaan Qureshi", 14 | "email": "amaanq12@gmail.com" 15 | } 16 | ], 17 | "main": "bindings/node", 18 | "types": "bindings/node", 19 | "keywords": [ 20 | "incremental", 21 | "parsing", 22 | "tree-sitter", 23 | "bash" 24 | ], 25 | "files": [ 26 | "grammar.js", 27 | "tree-sitter.json", 28 | "binding.gyp", 29 | "prebuilds/**", 30 | "bindings/node/*", 31 | "queries/*", 32 | "src/**", 33 | "*.wasm" 34 | ], 35 | "dependencies": { 36 | "node-addon-api": "^8.2.1", 37 | "node-gyp-build": "^4.8.2" 38 | }, 39 | "devDependencies": { 40 | "eslint": "^9.12.0", 41 | "eslint-config-treesitter": "^1.0.2", 42 | "tree-sitter-cli": "^0.24.3", 43 | "prebuildify": "^6.0.1" 44 | }, 45 | "peerDependencies": { 46 | "tree-sitter": "^0.21.1" 47 | }, 48 | "peerDependenciesMeta": { 49 | "tree-sitter": { 50 | "optional": true 51 | } 52 | }, 53 | "scripts": { 54 | "install": "node-gyp-build", 55 | "lint": "eslint grammar.js", 56 | "prestart": "tree-sitter build --wasm", 57 | "start": "tree-sitter playground", 58 | "test": "node --test bindings/node/*_test.js" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=42", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "tree-sitter-bash" 7 | description = "Bash grammar for tree-sitter" 8 | version = "0.23.3" 9 | keywords = ["incremental", "parsing", "tree-sitter", "bash"] 10 | classifiers = [ 11 | "Intended Audience :: Developers", 12 | "License :: OSI Approved :: MIT License", 13 | "Topic :: Software Development :: Compilers", 14 | "Topic :: Text Processing :: Linguistic", 15 | "Typing :: Typed", 16 | ] 17 | authors = [ 18 | { name = "Max Brunsfeld", email = "maxbrunsfeld@gmail.com" }, 19 | { name = "Amaan Qureshi", email = "amaanq12@gmail.com" }, 20 | ] 21 | requires-python = ">=3.9" 22 | license.text = "MIT" 23 | readme = "README.md" 24 | 25 | [project.urls] 26 | Homepage = "https://github.com/tree-sitter/tree-sitter-bash" 27 | 28 | [project.optional-dependencies] 29 | core = ["tree-sitter~=0.22"] 30 | 31 | [tool.cibuildwheel] 32 | build = "cp39-*" 33 | build-frontend = "build" 34 | -------------------------------------------------------------------------------- /queries/highlights.scm: -------------------------------------------------------------------------------- 1 | [ 2 | (string) 3 | (raw_string) 4 | (heredoc_body) 5 | (heredoc_start) 6 | ] @string 7 | 8 | (command_name) @function 9 | 10 | (variable_name) @property 11 | 12 | [ 13 | "case" 14 | "do" 15 | "done" 16 | "elif" 17 | "else" 18 | "esac" 19 | "export" 20 | "fi" 21 | "for" 22 | "function" 23 | "if" 24 | "in" 25 | "select" 26 | "then" 27 | "unset" 28 | "until" 29 | "while" 30 | ] @keyword 31 | 32 | (comment) @comment 33 | 34 | (function_definition name: (word) @function) 35 | 36 | (file_descriptor) @number 37 | 38 | [ 39 | (command_substitution) 40 | (process_substitution) 41 | (expansion) 42 | ]@embedded 43 | 44 | [ 45 | "$" 46 | "&&" 47 | ">" 48 | ">>" 49 | "<" 50 | "|" 51 | ] @operator 52 | 53 | ( 54 | (command (_) @constant) 55 | (#match? @constant "^-") 56 | ) 57 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from os.path import isdir, join 2 | from platform import system 3 | 4 | from setuptools import Extension, find_packages, setup 5 | from setuptools.command.build import build 6 | from wheel.bdist_wheel import bdist_wheel 7 | 8 | 9 | class Build(build): 10 | def run(self): 11 | if isdir("queries"): 12 | dest = join(self.build_lib, "tree_sitter_bash", "queries") 13 | self.copy_tree("queries", dest) 14 | super().run() 15 | 16 | 17 | class BdistWheel(bdist_wheel): 18 | def get_tag(self): 19 | python, abi, platform = super().get_tag() 20 | if python.startswith("cp"): 21 | python, abi = "cp39", "abi3" 22 | return python, abi, platform 23 | 24 | 25 | setup( 26 | packages=find_packages("bindings/python"), 27 | package_dir={"": "bindings/python"}, 28 | package_data={ 29 | "tree_sitter_bash": ["*.pyi", "py.typed"], 30 | "tree_sitter_bash.queries": ["*.scm"], 31 | }, 32 | ext_package="tree_sitter_bash", 33 | ext_modules=[ 34 | Extension( 35 | name="_binding", 36 | sources=[ 37 | "bindings/python/tree_sitter_bash/binding.c", 38 | "src/parser.c", 39 | "src/scanner.c", 40 | ], 41 | extra_compile_args=[ 42 | "-std=c11", 43 | "-fvisibility=hidden", 44 | ] if system() != "Windows" else [ 45 | "/std:c11", 46 | "/utf-8", 47 | ], 48 | define_macros=[ 49 | ("Py_LIMITED_API", "0x03090000"), 50 | ("PY_SSIZE_T_CLEAN", None), 51 | ("TREE_SITTER_HIDE_SYMBOLS", None), 52 | ], 53 | include_dirs=["src"], 54 | py_limited_api=True, 55 | ) 56 | ], 57 | cmdclass={ 58 | "build": Build, 59 | "bdist_wheel": BdistWheel 60 | }, 61 | zip_safe=False 62 | ) 63 | -------------------------------------------------------------------------------- /src/tree_sitter/alloc.h: -------------------------------------------------------------------------------- 1 | #ifndef TREE_SITTER_ALLOC_H_ 2 | #define TREE_SITTER_ALLOC_H_ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include <stdbool.h> 9 | #include <stdio.h> 10 | #include <stdlib.h> 11 | 12 | // Allow clients to override allocation functions 13 | #ifdef TREE_SITTER_REUSE_ALLOCATOR 14 | 15 | extern void *(*ts_current_malloc)(size_t size); 16 | extern void *(*ts_current_calloc)(size_t count, size_t size); 17 | extern void *(*ts_current_realloc)(void *ptr, size_t size); 18 | extern void (*ts_current_free)(void *ptr); 19 | 20 | #ifndef ts_malloc 21 | #define ts_malloc ts_current_malloc 22 | #endif 23 | #ifndef ts_calloc 24 | #define ts_calloc ts_current_calloc 25 | #endif 26 | #ifndef ts_realloc 27 | #define ts_realloc ts_current_realloc 28 | #endif 29 | #ifndef ts_free 30 | #define ts_free ts_current_free 31 | #endif 32 | 33 | #else 34 | 35 | #ifndef ts_malloc 36 | #define ts_malloc malloc 37 | #endif 38 | #ifndef ts_calloc 39 | #define ts_calloc calloc 40 | #endif 41 | #ifndef ts_realloc 42 | #define ts_realloc realloc 43 | #endif 44 | #ifndef ts_free 45 | #define ts_free free 46 | #endif 47 | 48 | #endif 49 | 50 | #ifdef __cplusplus 51 | } 52 | #endif 53 | 54 | #endif // TREE_SITTER_ALLOC_H_ 55 | -------------------------------------------------------------------------------- /src/tree_sitter/array.h: -------------------------------------------------------------------------------- 1 | #ifndef TREE_SITTER_ARRAY_H_ 2 | #define TREE_SITTER_ARRAY_H_ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "./alloc.h" 9 | 10 | #include <assert.h> 11 | #include <stdbool.h> 12 | #include <stdint.h> 13 | #include <stdlib.h> 14 | #include <string.h> 15 | 16 | #ifdef _MSC_VER 17 | #pragma warning(disable : 4101) 18 | #elif defined(__GNUC__) || defined(__clang__) 19 | #pragma GCC diagnostic push 20 | #pragma GCC diagnostic ignored "-Wunused-variable" 21 | #endif 22 | 23 | #define Array(T) \ 24 | struct { \ 25 | T *contents; \ 26 | uint32_t size; \ 27 | uint32_t capacity; \ 28 | } 29 | 30 | /// Initialize an array. 31 | #define array_init(self) \ 32 | ((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL) 33 | 34 | /// Create an empty array. 35 | #define array_new() \ 36 | { NULL, 0, 0 } 37 | 38 | /// Get a pointer to the element at a given `index` in the array. 39 | #define array_get(self, _index) \ 40 | (assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index]) 41 | 42 | /// Get a pointer to the first element in the array. 43 | #define array_front(self) array_get(self, 0) 44 | 45 | /// Get a pointer to the last element in the array. 46 | #define array_back(self) array_get(self, (self)->size - 1) 47 | 48 | /// Clear the array, setting its size to zero. Note that this does not free any 49 | /// memory allocated for the array's contents. 50 | #define array_clear(self) ((self)->size = 0) 51 | 52 | /// Reserve `new_capacity` elements of space in the array. If `new_capacity` is 53 | /// less than the array's current capacity, this function has no effect. 54 | #define array_reserve(self, new_capacity) \ 55 | _array__reserve((Array *)(self), array_elem_size(self), new_capacity) 56 | 57 | /// Free any memory allocated for this array. Note that this does not free any 58 | /// memory allocated for the array's contents. 59 | #define array_delete(self) _array__delete((Array *)(self)) 60 | 61 | /// Push a new `element` onto the end of the array. 62 | #define array_push(self, element) \ 63 | (_array__grow((Array *)(self), 1, array_elem_size(self)), \ 64 | (self)->contents[(self)->size++] = (element)) 65 | 66 | /// Increase the array's size by `count` elements. 67 | /// New elements are zero-initialized. 68 | #define array_grow_by(self, count) \ 69 | do { \ 70 | if ((count) == 0) break; \ 71 | _array__grow((Array *)(self), count, array_elem_size(self)); \ 72 | memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \ 73 | (self)->size += (count); \ 74 | } while (0) 75 | 76 | /// Append all elements from one array to the end of another. 77 | #define array_push_all(self, other) \ 78 | array_extend((self), (other)->size, (other)->contents) 79 | 80 | /// Append `count` elements to the end of the array, reading their values from the 81 | /// `contents` pointer. 82 | #define array_extend(self, count, contents) \ 83 | _array__splice( \ 84 | (Array *)(self), array_elem_size(self), (self)->size, \ 85 | 0, count, contents \ 86 | ) 87 | 88 | /// Remove `old_count` elements from the array starting at the given `index`. At 89 | /// the same index, insert `new_count` new elements, reading their values from the 90 | /// `new_contents` pointer. 91 | #define array_splice(self, _index, old_count, new_count, new_contents) \ 92 | _array__splice( \ 93 | (Array *)(self), array_elem_size(self), _index, \ 94 | old_count, new_count, new_contents \ 95 | ) 96 | 97 | /// Insert one `element` into the array at the given `index`. 98 | #define array_insert(self, _index, element) \ 99 | _array__splice((Array *)(self), array_elem_size(self), _index, 0, 1, &(element)) 100 | 101 | /// Remove one element from the array at the given `index`. 102 | #define array_erase(self, _index) \ 103 | _array__erase((Array *)(self), array_elem_size(self), _index) 104 | 105 | /// Pop the last element off the array, returning the element by value. 106 | #define array_pop(self) ((self)->contents[--(self)->size]) 107 | 108 | /// Assign the contents of one array to another, reallocating if necessary. 109 | #define array_assign(self, other) \ 110 | _array__assign((Array *)(self), (const Array *)(other), array_elem_size(self)) 111 | 112 | /// Swap one array with another 113 | #define array_swap(self, other) \ 114 | _array__swap((Array *)(self), (Array *)(other)) 115 | 116 | /// Get the size of the array contents 117 | #define array_elem_size(self) (sizeof *(self)->contents) 118 | 119 | /// Search a sorted array for a given `needle` value, using the given `compare` 120 | /// callback to determine the order. 121 | /// 122 | /// If an existing element is found to be equal to `needle`, then the `index` 123 | /// out-parameter is set to the existing value's index, and the `exists` 124 | /// out-parameter is set to true. Otherwise, `index` is set to an index where 125 | /// `needle` should be inserted in order to preserve the sorting, and `exists` 126 | /// is set to false. 127 | #define array_search_sorted_with(self, compare, needle, _index, _exists) \ 128 | _array__search_sorted(self, 0, compare, , needle, _index, _exists) 129 | 130 | /// Search a sorted array for a given `needle` value, using integer comparisons 131 | /// of a given struct field (specified with a leading dot) to determine the order. 132 | /// 133 | /// See also `array_search_sorted_with`. 134 | #define array_search_sorted_by(self, field, needle, _index, _exists) \ 135 | _array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists) 136 | 137 | /// Insert a given `value` into a sorted array, using the given `compare` 138 | /// callback to determine the order. 139 | #define array_insert_sorted_with(self, compare, value) \ 140 | do { \ 141 | unsigned _index, _exists; \ 142 | array_search_sorted_with(self, compare, &(value), &_index, &_exists); \ 143 | if (!_exists) array_insert(self, _index, value); \ 144 | } while (0) 145 | 146 | /// Insert a given `value` into a sorted array, using integer comparisons of 147 | /// a given struct field (specified with a leading dot) to determine the order. 148 | /// 149 | /// See also `array_search_sorted_by`. 150 | #define array_insert_sorted_by(self, field, value) \ 151 | do { \ 152 | unsigned _index, _exists; \ 153 | array_search_sorted_by(self, field, (value) field, &_index, &_exists); \ 154 | if (!_exists) array_insert(self, _index, value); \ 155 | } while (0) 156 | 157 | // Private 158 | 159 | typedef Array(void) Array; 160 | 161 | /// This is not what you're looking for, see `array_delete`. 162 | static inline void _array__delete(Array *self) { 163 | if (self->contents) { 164 | ts_free(self->contents); 165 | self->contents = NULL; 166 | self->size = 0; 167 | self->capacity = 0; 168 | } 169 | } 170 | 171 | /// This is not what you're looking for, see `array_erase`. 172 | static inline void _array__erase(Array *self, size_t element_size, 173 | uint32_t index) { 174 | assert(index < self->size); 175 | char *contents = (char *)self->contents; 176 | memmove(contents + index * element_size, contents + (index + 1) * element_size, 177 | (self->size - index - 1) * element_size); 178 | self->size--; 179 | } 180 | 181 | /// This is not what you're looking for, see `array_reserve`. 182 | static inline void _array__reserve(Array *self, size_t element_size, uint32_t new_capacity) { 183 | if (new_capacity > self->capacity) { 184 | if (self->contents) { 185 | self->contents = ts_realloc(self->contents, new_capacity * element_size); 186 | } else { 187 | self->contents = ts_malloc(new_capacity * element_size); 188 | } 189 | self->capacity = new_capacity; 190 | } 191 | } 192 | 193 | /// This is not what you're looking for, see `array_assign`. 194 | static inline void _array__assign(Array *self, const Array *other, size_t element_size) { 195 | _array__reserve(self, element_size, other->size); 196 | self->size = other->size; 197 | memcpy(self->contents, other->contents, self->size * element_size); 198 | } 199 | 200 | /// This is not what you're looking for, see `array_swap`. 201 | static inline void _array__swap(Array *self, Array *other) { 202 | Array swap = *other; 203 | *other = *self; 204 | *self = swap; 205 | } 206 | 207 | /// This is not what you're looking for, see `array_push` or `array_grow_by`. 208 | static inline void _array__grow(Array *self, uint32_t count, size_t element_size) { 209 | uint32_t new_size = self->size + count; 210 | if (new_size > self->capacity) { 211 | uint32_t new_capacity = self->capacity * 2; 212 | if (new_capacity < 8) new_capacity = 8; 213 | if (new_capacity < new_size) new_capacity = new_size; 214 | _array__reserve(self, element_size, new_capacity); 215 | } 216 | } 217 | 218 | /// This is not what you're looking for, see `array_splice`. 219 | static inline void _array__splice(Array *self, size_t element_size, 220 | uint32_t index, uint32_t old_count, 221 | uint32_t new_count, const void *elements) { 222 | uint32_t new_size = self->size + new_count - old_count; 223 | uint32_t old_end = index + old_count; 224 | uint32_t new_end = index + new_count; 225 | assert(old_end <= self->size); 226 | 227 | _array__reserve(self, element_size, new_size); 228 | 229 | char *contents = (char *)self->contents; 230 | if (self->size > old_end) { 231 | memmove( 232 | contents + new_end * element_size, 233 | contents + old_end * element_size, 234 | (self->size - old_end) * element_size 235 | ); 236 | } 237 | if (new_count > 0) { 238 | if (elements) { 239 | memcpy( 240 | (contents + index * element_size), 241 | elements, 242 | new_count * element_size 243 | ); 244 | } else { 245 | memset( 246 | (contents + index * element_size), 247 | 0, 248 | new_count * element_size 249 | ); 250 | } 251 | } 252 | self->size += new_count - old_count; 253 | } 254 | 255 | /// A binary search routine, based on Rust's `std::slice::binary_search_by`. 256 | /// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`. 257 | #define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \ 258 | do { \ 259 | *(_index) = start; \ 260 | *(_exists) = false; \ 261 | uint32_t size = (self)->size - *(_index); \ 262 | if (size == 0) break; \ 263 | int comparison; \ 264 | while (size > 1) { \ 265 | uint32_t half_size = size / 2; \ 266 | uint32_t mid_index = *(_index) + half_size; \ 267 | comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \ 268 | if (comparison <= 0) *(_index) = mid_index; \ 269 | size -= half_size; \ 270 | } \ 271 | comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \ 272 | if (comparison == 0) *(_exists) = true; \ 273 | else if (comparison < 0) *(_index) += 1; \ 274 | } while (0) 275 | 276 | /// Helper macro for the `_sorted_by` routines below. This takes the left (existing) 277 | /// parameter by reference in order to work with the generic sorting function above. 278 | #define _compare_int(a, b) ((int)*(a) - (int)(b)) 279 | 280 | #ifdef _MSC_VER 281 | #pragma warning(default : 4101) 282 | #elif defined(__GNUC__) || defined(__clang__) 283 | #pragma GCC diagnostic pop 284 | #endif 285 | 286 | #ifdef __cplusplus 287 | } 288 | #endif 289 | 290 | #endif // TREE_SITTER_ARRAY_H_ 291 | -------------------------------------------------------------------------------- /src/tree_sitter/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef TREE_SITTER_PARSER_H_ 2 | #define TREE_SITTER_PARSER_H_ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include <stdbool.h> 9 | #include <stdint.h> 10 | #include <stdlib.h> 11 | 12 | #define ts_builtin_sym_error ((TSSymbol)-1) 13 | #define ts_builtin_sym_end 0 14 | #define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024 15 | 16 | #ifndef TREE_SITTER_API_H_ 17 | typedef uint16_t TSStateId; 18 | typedef uint16_t TSSymbol; 19 | typedef uint16_t TSFieldId; 20 | typedef struct TSLanguage TSLanguage; 21 | #endif 22 | 23 | typedef struct { 24 | TSFieldId field_id; 25 | uint8_t child_index; 26 | bool inherited; 27 | } TSFieldMapEntry; 28 | 29 | typedef struct { 30 | uint16_t index; 31 | uint16_t length; 32 | } TSFieldMapSlice; 33 | 34 | typedef struct { 35 | bool visible; 36 | bool named; 37 | bool supertype; 38 | } TSSymbolMetadata; 39 | 40 | typedef struct TSLexer TSLexer; 41 | 42 | struct TSLexer { 43 | int32_t lookahead; 44 | TSSymbol result_symbol; 45 | void (*advance)(TSLexer *, bool); 46 | void (*mark_end)(TSLexer *); 47 | uint32_t (*get_column)(TSLexer *); 48 | bool (*is_at_included_range_start)(const TSLexer *); 49 | bool (*eof)(const TSLexer *); 50 | void (*log)(const TSLexer *, const char *, ...); 51 | }; 52 | 53 | typedef enum { 54 | TSParseActionTypeShift, 55 | TSParseActionTypeReduce, 56 | TSParseActionTypeAccept, 57 | TSParseActionTypeRecover, 58 | } TSParseActionType; 59 | 60 | typedef union { 61 | struct { 62 | uint8_t type; 63 | TSStateId state; 64 | bool extra; 65 | bool repetition; 66 | } shift; 67 | struct { 68 | uint8_t type; 69 | uint8_t child_count; 70 | TSSymbol symbol; 71 | int16_t dynamic_precedence; 72 | uint16_t production_id; 73 | } reduce; 74 | uint8_t type; 75 | } TSParseAction; 76 | 77 | typedef struct { 78 | uint16_t lex_state; 79 | uint16_t external_lex_state; 80 | } TSLexMode; 81 | 82 | typedef union { 83 | TSParseAction action; 84 | struct { 85 | uint8_t count; 86 | bool reusable; 87 | } entry; 88 | } TSParseActionEntry; 89 | 90 | typedef struct { 91 | int32_t start; 92 | int32_t end; 93 | } TSCharacterRange; 94 | 95 | struct TSLanguage { 96 | uint32_t version; 97 | uint32_t symbol_count; 98 | uint32_t alias_count; 99 | uint32_t token_count; 100 | uint32_t external_token_count; 101 | uint32_t state_count; 102 | uint32_t large_state_count; 103 | uint32_t production_id_count; 104 | uint32_t field_count; 105 | uint16_t max_alias_sequence_length; 106 | const uint16_t *parse_table; 107 | const uint16_t *small_parse_table; 108 | const uint32_t *small_parse_table_map; 109 | const TSParseActionEntry *parse_actions; 110 | const char * const *symbol_names; 111 | const char * const *field_names; 112 | const TSFieldMapSlice *field_map_slices; 113 | const TSFieldMapEntry *field_map_entries; 114 | const TSSymbolMetadata *symbol_metadata; 115 | const TSSymbol *public_symbol_map; 116 | const uint16_t *alias_map; 117 | const TSSymbol *alias_sequences; 118 | const TSLexMode *lex_modes; 119 | bool (*lex_fn)(TSLexer *, TSStateId); 120 | bool (*keyword_lex_fn)(TSLexer *, TSStateId); 121 | TSSymbol keyword_capture_token; 122 | struct { 123 | const bool *states; 124 | const TSSymbol *symbol_map; 125 | void *(*create)(void); 126 | void (*destroy)(void *); 127 | bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist); 128 | unsigned (*serialize)(void *, char *); 129 | void (*deserialize)(void *, const char *, unsigned); 130 | } external_scanner; 131 | const TSStateId *primary_state_ids; 132 | }; 133 | 134 | static inline bool set_contains(TSCharacterRange *ranges, uint32_t len, int32_t lookahead) { 135 | uint32_t index = 0; 136 | uint32_t size = len - index; 137 | while (size > 1) { 138 | uint32_t half_size = size / 2; 139 | uint32_t mid_index = index + half_size; 140 | TSCharacterRange *range = &ranges[mid_index]; 141 | if (lookahead >= range->start && lookahead <= range->end) { 142 | return true; 143 | } else if (lookahead > range->end) { 144 | index = mid_index; 145 | } 146 | size -= half_size; 147 | } 148 | TSCharacterRange *range = &ranges[index]; 149 | return (lookahead >= range->start && lookahead <= range->end); 150 | } 151 | 152 | /* 153 | * Lexer Macros 154 | */ 155 | 156 | #ifdef _MSC_VER 157 | #define UNUSED __pragma(warning(suppress : 4101)) 158 | #else 159 | #define UNUSED __attribute__((unused)) 160 | #endif 161 | 162 | #define START_LEXER() \ 163 | bool result = false; \ 164 | bool skip = false; \ 165 | UNUSED \ 166 | bool eof = false; \ 167 | int32_t lookahead; \ 168 | goto start; \ 169 | next_state: \ 170 | lexer->advance(lexer, skip); \ 171 | start: \ 172 | skip = false; \ 173 | lookahead = lexer->lookahead; 174 | 175 | #define ADVANCE(state_value) \ 176 | { \ 177 | state = state_value; \ 178 | goto next_state; \ 179 | } 180 | 181 | #define ADVANCE_MAP(...) \ 182 | { \ 183 | static const uint16_t map[] = { __VA_ARGS__ }; \ 184 | for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) { \ 185 | if (map[i] == lookahead) { \ 186 | state = map[i + 1]; \ 187 | goto next_state; \ 188 | } \ 189 | } \ 190 | } 191 | 192 | #define SKIP(state_value) \ 193 | { \ 194 | skip = true; \ 195 | state = state_value; \ 196 | goto next_state; \ 197 | } 198 | 199 | #define ACCEPT_TOKEN(symbol_value) \ 200 | result = true; \ 201 | lexer->result_symbol = symbol_value; \ 202 | lexer->mark_end(lexer); 203 | 204 | #define END_STATE() return result; 205 | 206 | /* 207 | * Parse Table Macros 208 | */ 209 | 210 | #define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT) 211 | 212 | #define STATE(id) id 213 | 214 | #define ACTIONS(id) id 215 | 216 | #define SHIFT(state_value) \ 217 | {{ \ 218 | .shift = { \ 219 | .type = TSParseActionTypeShift, \ 220 | .state = (state_value) \ 221 | } \ 222 | }} 223 | 224 | #define SHIFT_REPEAT(state_value) \ 225 | {{ \ 226 | .shift = { \ 227 | .type = TSParseActionTypeShift, \ 228 | .state = (state_value), \ 229 | .repetition = true \ 230 | } \ 231 | }} 232 | 233 | #define SHIFT_EXTRA() \ 234 | {{ \ 235 | .shift = { \ 236 | .type = TSParseActionTypeShift, \ 237 | .extra = true \ 238 | } \ 239 | }} 240 | 241 | #define REDUCE(symbol_name, children, precedence, prod_id) \ 242 | {{ \ 243 | .reduce = { \ 244 | .type = TSParseActionTypeReduce, \ 245 | .symbol = symbol_name, \ 246 | .child_count = children, \ 247 | .dynamic_precedence = precedence, \ 248 | .production_id = prod_id \ 249 | }, \ 250 | }} 251 | 252 | #define RECOVER() \ 253 | {{ \ 254 | .type = TSParseActionTypeRecover \ 255 | }} 256 | 257 | #define ACCEPT_INPUT() \ 258 | {{ \ 259 | .type = TSParseActionTypeAccept \ 260 | }} 261 | 262 | #ifdef __cplusplus 263 | } 264 | #endif 265 | 266 | #endif // TREE_SITTER_PARSER_H_ 267 | -------------------------------------------------------------------------------- /test/corpus/commands.txt: -------------------------------------------------------------------------------- 1 | =============================== 2 | Commands 3 | =============================== 4 | 5 | whoami 6 | 7 | --- 8 | 9 | (program 10 | (command (command_name (word)))) 11 | 12 | =============================== 13 | Commands with arguments 14 | =============================== 15 | 16 | cat file1.txt 17 | git diff --word-diff=color -- file1.txt file2.txt 18 | echo $sing\ 19 | levar 20 | 21 | --- 22 | 23 | (program 24 | (command (command_name (word)) (word)) 25 | (command (command_name (word)) (word) (word) (word) (word) (word)) 26 | (command (command_name (word)) (simple_expansion (variable_name)) (word))) 27 | 28 | =============================== 29 | Quoted command names 30 | =============================== 31 | 32 | "$a/$b" c 33 | 34 | --- 35 | 36 | (program 37 | (command 38 | (command_name (string (simple_expansion (variable_name)) (string_content) (simple_expansion (variable_name)))) 39 | (word))) 40 | 41 | =============================== 42 | Commands with numeric arguments 43 | =============================== 44 | 45 | exit 1 46 | 47 | --- 48 | 49 | (program 50 | (command (command_name (word)) (number))) 51 | 52 | =================================== 53 | Commands with environment variables 54 | =================================== 55 | 56 | VAR1=1 ./script/test 57 | VAR1=a VAR2="ok" git diff --word-diff=color 58 | 59 | --- 60 | 61 | (program 62 | (command 63 | (variable_assignment (variable_name) (number)) 64 | (command_name (word))) 65 | (command 66 | (variable_assignment (variable_name) (word)) 67 | (variable_assignment (variable_name) (string (string_content))) 68 | (command_name (word)) 69 | (word) 70 | (word))) 71 | 72 | =================================== 73 | Empty environment variables 74 | =================================== 75 | 76 | VAR1= 77 | VAR2= echo 78 | 79 | --- 80 | 81 | (program 82 | (variable_assignment (variable_name)) 83 | (command (variable_assignment (variable_name)) (command_name (word)))) 84 | 85 | =============================== 86 | File redirects 87 | =============================== 88 | 89 | whoami > /dev/null 90 | cat a b > /dev/null 91 | 2>&1 whoami 92 | echo "foobar" >&2 93 | [ ! command -v go &>/dev/null ] && return 94 | 95 | if [ ]; then 96 | >aa >bb 97 | fi 98 | 99 | exec {VIRTWL[0]} {VIRTWL[1]} <&- >&- 100 | exec {VIRTWL[0]}<&- {VIRTWL[1]}>&- 101 | 102 | grep 2>/dev/null -q "^/usr/bin/scponly$" /etc/shells 103 | 104 | x <x a b c 105 | 106 | --- 107 | 108 | (program 109 | (redirected_statement 110 | (command (command_name (word))) 111 | (file_redirect (word))) 112 | (redirected_statement 113 | (command (command_name (word)) (word) (word)) 114 | (file_redirect (word))) 115 | (command 116 | (file_redirect (file_descriptor) (number)) 117 | (command_name (word))) 118 | (redirected_statement 119 | (command (command_name (word)) (string (string_content))) 120 | (file_redirect (number))) 121 | (list 122 | (test_command 123 | (redirected_statement 124 | (negated_command 125 | (command (command_name (word)) (word) (word))) 126 | (file_redirect (word)))) 127 | (command (command_name (word)))) 128 | (if_statement 129 | (test_command) 130 | (redirected_statement 131 | (file_redirect (word)) 132 | (file_redirect (word)))) 133 | (redirected_statement 134 | (command 135 | (command_name (word)) 136 | (concatenation (word) (word) (word) (number) (word) (word)) 137 | (concatenation (word) (word) (word) (number) (word) (word))) 138 | (file_redirect) 139 | (file_redirect)) 140 | (redirected_statement 141 | (command (command_name (word)) 142 | (concatenation (word) (word) (word) (number) (word) (word))) 143 | (file_redirect 144 | (concatenation 145 | (word) 146 | (word) 147 | (word) 148 | (number) 149 | (word) 150 | (word))) 151 | (file_redirect)) 152 | (redirected_statement 153 | (command (command_name (word))) 154 | (file_redirect (file_descriptor) (word) 155 | (word) 156 | (string 157 | (string_content)) 158 | (word))) 159 | (redirected_statement 160 | (command (command_name (word))) 161 | (file_redirect (word) 162 | (word) 163 | (word) 164 | (word)))) 165 | 166 | =============================== 167 | File redirects (noclobber override) 168 | =============================== 169 | 170 | whoami >| /dev/null 171 | cat a b >| /dev/null 172 | 173 | --- 174 | 175 | (program 176 | (redirected_statement 177 | (command (command_name (word))) 178 | (file_redirect (word))) 179 | (redirected_statement 180 | (command (command_name (word)) (word) (word)) 181 | (file_redirect (word)))) 182 | 183 | =============================== 184 | Heredoc redirects 185 | =============================== 186 | 187 | node <<JS 188 | console.log("hi") 189 | JS 190 | 191 | bash -c <<JS 192 | echo hi 193 | JS 194 | 195 | newins <<-EOF - org.freedesktop.Notifications.service 196 | [D-BUS Service] 197 | Name=org.freedesktop.Notifications 198 | Exec=/usr/libexec/notification-daemon 199 | EOF 200 | 201 | --- 202 | 203 | (program 204 | (redirected_statement 205 | (command (command_name (word))) 206 | (heredoc_redirect 207 | (heredoc_start) 208 | (heredoc_body) 209 | (heredoc_end))) 210 | (redirected_statement 211 | (command (command_name (word)) (word)) 212 | (heredoc_redirect 213 | (heredoc_start) 214 | (heredoc_body) 215 | (heredoc_end))) 216 | (redirected_statement 217 | (command (command_name (word))) 218 | (heredoc_redirect 219 | (heredoc_start) 220 | (word) 221 | (word) 222 | (heredoc_body) 223 | (heredoc_end)))) 224 | 225 | =============================== 226 | Heredocs with variables 227 | =============================== 228 | 229 | node <<JS 230 | a $B ${C} 231 | JS 232 | 233 | exit 234 | 235 | --- 236 | 237 | (program 238 | (redirected_statement 239 | (command 240 | (command_name 241 | (word))) 242 | (heredoc_redirect 243 | (heredoc_start) 244 | (heredoc_body 245 | (simple_expansion 246 | (variable_name)) 247 | (heredoc_content) 248 | (expansion 249 | (variable_name)) 250 | (heredoc_content)) 251 | (heredoc_end))) 252 | (command 253 | (command_name 254 | (word)))) 255 | 256 | ================================= 257 | Heredocs with file redirects 258 | ================================= 259 | 260 | cat <<EOF > $tmpfile 261 | a $B ${C} 262 | EOF 263 | 264 | wc -l $tmpfile 265 | 266 | --- 267 | 268 | (program 269 | (redirected_statement 270 | (command 271 | (command_name 272 | (word))) 273 | (heredoc_redirect 274 | (heredoc_start) 275 | (file_redirect 276 | (simple_expansion 277 | (variable_name))) 278 | (heredoc_body 279 | (simple_expansion 280 | (variable_name)) 281 | (heredoc_content) 282 | (expansion 283 | (variable_name)) 284 | (heredoc_content)) 285 | (heredoc_end))) 286 | (command 287 | (command_name 288 | (word)) 289 | (word) 290 | (simple_expansion 291 | (variable_name)))) 292 | 293 | ================================= 294 | Heredocs with many file redirects 295 | ================================= 296 | 297 | FOO=bar echo <<EOF 2> err.txt > hello.txt 298 | hello 299 | EOF 300 | 301 | --- 302 | 303 | (program 304 | (redirected_statement 305 | body: (command 306 | (variable_assignment 307 | name: (variable_name) 308 | value: (word)) 309 | name: (command_name 310 | (word))) 311 | redirect: (heredoc_redirect 312 | (heredoc_start) 313 | redirect: (file_redirect 314 | descriptor: (file_descriptor) 315 | destination: (word)) 316 | redirect: (file_redirect 317 | destination: (word)) 318 | (heredoc_body) 319 | (heredoc_end)))) 320 | 321 | ================================= 322 | Heredocs with pipes 323 | ================================= 324 | 325 | one <<EOF | grep two 326 | three 327 | EOF 328 | 329 | --- 330 | 331 | (program 332 | (redirected_statement 333 | (command 334 | (command_name 335 | (word))) 336 | (heredoc_redirect 337 | (heredoc_start) 338 | (pipeline 339 | (command 340 | (command_name 341 | (word)) 342 | (word))) 343 | (heredoc_body) 344 | (heredoc_end)))) 345 | 346 | ====================================== 347 | Heredocs with escaped expansions 348 | ====================================== 349 | 350 | cat << EOF 351 | DEV_NAME=\$(lsblk) 352 | EOF 353 | 354 | --- 355 | 356 | (program (redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (heredoc_body) (heredoc_end)))) 357 | 358 | ====================================== 359 | Quoted Heredocs 360 | ====================================== 361 | 362 | cat << 'EOF' 363 | a=$b 364 | EOF 365 | 366 | cat << "EOF" 367 | a=$b 368 | EOF 369 | 370 | cat <<"END OF FILE" 371 | hello, 372 | world 373 | END OF FILE 374 | 375 | cat << \EOF 376 | EOF 377 | 378 | --- 379 | 380 | (program 381 | (redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (heredoc_body) (heredoc_end))) 382 | (redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (heredoc_body) (heredoc_end))) 383 | (redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (heredoc_body) (heredoc_end))) 384 | (redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (heredoc_body) (heredoc_end)))) 385 | 386 | ========================================== 387 | Heredocs with indented closing delimiters 388 | ========================================== 389 | 390 | usage() { 391 | cat <<-EOF 392 | Usage: ${0##*/} FOO BAR 393 | EOF 394 | } 395 | 396 | --- 397 | 398 | (program 399 | (function_definition 400 | (word) 401 | (compound_statement 402 | (redirected_statement 403 | (command (command_name (word))) 404 | (heredoc_redirect 405 | (heredoc_start) 406 | (heredoc_body (expansion (special_variable_name) (regex)) (heredoc_content)) 407 | (heredoc_end)))))) 408 | 409 | ========================================== 410 | Heredocs with empty bodies 411 | ========================================== 412 | 413 | node <<JS 414 | JS 415 | 416 | node << 'SJ' 417 | SJ 418 | 419 | usage() { 420 | cat <<-EOF 421 | EOF 422 | } 423 | 424 | node << 'EOF' > temp 425 | EOF 426 | 427 | --- 428 | 429 | (program 430 | (redirected_statement 431 | body: (command 432 | name: (command_name 433 | (word))) 434 | redirect: (heredoc_redirect 435 | (heredoc_start) 436 | (heredoc_body) 437 | (heredoc_end))) 438 | (redirected_statement 439 | body: (command 440 | name: (command_name 441 | (word))) 442 | redirect: (heredoc_redirect 443 | (heredoc_start) 444 | (heredoc_body) 445 | (heredoc_end))) 446 | (function_definition 447 | name: (word) 448 | body: (compound_statement 449 | (redirected_statement 450 | body: (command 451 | name: (command_name 452 | (word))) 453 | redirect: (heredoc_redirect 454 | (heredoc_start) 455 | (heredoc_body) 456 | (heredoc_end))))) 457 | (redirected_statement 458 | body: (command 459 | name: (command_name 460 | (word))) 461 | redirect: (heredoc_redirect 462 | (heredoc_start) 463 | redirect: (file_redirect 464 | destination: (word)) 465 | (heredoc_body) 466 | (heredoc_end)))) 467 | 468 | ========================================== 469 | Heredocs with weird characters 470 | ========================================== 471 | 472 | node <<_DELIMITER_WITH_UNDERSCORES_ 473 | Hello. 474 | _DELIMITER_WITH_UNDERSCORES_ 475 | 476 | node <<'```' 477 | Hello. 478 | ``` 479 | 480 | node <<!HEREDOC! 481 | Hello. 482 | !HEREDOC! 483 | 484 | node <<\' 485 | Hello. 486 | ' 487 | 488 | node <<\\ 489 | Hello. 490 | \ 491 | 492 | --- 493 | 494 | (program 495 | (redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (heredoc_body) (heredoc_end))) 496 | (redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (heredoc_body) (heredoc_end))) 497 | (redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (heredoc_body) (heredoc_end))) 498 | (redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (heredoc_body) (heredoc_end))) 499 | (redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (heredoc_body) (heredoc_end)))) 500 | 501 | ========================================== 502 | Heredocs with a rhs statement 503 | ========================================== 504 | 505 | cat <<-_EOF_ || die "cat EOF failed" 506 | #!/bin/sh 507 | echo hello 508 | _EOF_ 509 | 510 | --- 511 | 512 | (program 513 | (redirected_statement 514 | (command (command_name (word))) 515 | (heredoc_redirect 516 | (heredoc_start) 517 | (command (command_name (word)) (string (string_content))) 518 | (heredoc_body) 519 | (heredoc_end)))) 520 | 521 | ========================================== 522 | Heredocs with a $ that is not an expansion 523 | ========================================== 524 | 525 | cat <<EOF 526 | # check out this regex '^EOF$' 527 | EOF 528 | 529 | --- 530 | 531 | (program 532 | (redirected_statement 533 | (command (command_name (word))) 534 | (heredoc_redirect 535 | (heredoc_start) 536 | (heredoc_body) 537 | (heredoc_end)))) 538 | 539 | ========================================== 540 | Nested Heredocs 541 | ========================================== 542 | 543 | cat <<OUTER 544 | Outer Heredoc Start 545 | $(cat <<INNER 546 | Inner Heredoc Content 547 | $(cat <<INNERMOST 548 | Innermost Heredoc Content 549 | INNERMOST 550 | ) 551 | INNER) 552 | Outer Heredoc End 553 | OUTER 554 | 555 | --- 556 | 557 | (program 558 | (redirected_statement 559 | (command (command_name (word))) 560 | (heredoc_redirect 561 | (heredoc_start) 562 | (heredoc_body 563 | (command_substitution 564 | (redirected_statement 565 | (command (command_name (word))) 566 | (heredoc_redirect 567 | (heredoc_start) 568 | (heredoc_body 569 | (command_substitution 570 | (redirected_statement 571 | (command (command_name (word))) 572 | (heredoc_redirect 573 | (heredoc_start) 574 | (heredoc_body) 575 | (heredoc_end)))) 576 | (heredoc_content)) 577 | (heredoc_end)))) 578 | (heredoc_content)) 579 | (heredoc_end)))) 580 | 581 | ========================================== 582 | Herestrings 583 | ========================================== 584 | 585 | node <<< foo 586 | 587 | while read -u 3 entry; do 588 | echo $entry 589 | done 3<<<"$ENTRIES" 590 | 591 | $(tc-getCC) -Werror -Wl,-l:libobjc.so.${ver} -x objective-c \ 592 | - <<<$'int main() {}' -o /dev/null 2> /dev/null; 593 | 594 | <<<string cmd arg 595 | 596 | cmd arg <<<string 597 | 598 | cmd <<<string arg 599 | 600 | <<<string 601 | 602 | --- 603 | 604 | (program 605 | (command (command_name (word)) (herestring_redirect (word))) 606 | (redirected_statement 607 | (while_statement 608 | (command (command_name (word)) (word) (number) (word)) 609 | (do_group 610 | (command (command_name (word)) (simple_expansion (variable_name))))) 611 | (herestring_redirect 612 | (file_descriptor) 613 | (string (simple_expansion (variable_name))))) 614 | (redirected_statement 615 | (command 616 | (command_name (command_substitution (command (command_name (word))))) 617 | (word) 618 | (concatenation (word) (expansion (variable_name))) 619 | (word) 620 | (word) 621 | (word) 622 | (herestring_redirect (ansi_c_string)) 623 | (word) 624 | (word)) 625 | (file_redirect (file_descriptor) (word))) 626 | (command (herestring_redirect (word)) (command_name (word)) (word)) 627 | (command (command_name (word)) (word) (herestring_redirect (word))) 628 | (command (command_name (word)) (herestring_redirect (word)) (word)) 629 | (redirected_statement (herestring_redirect (word)))) 630 | 631 | ========================================== 632 | Subscripts 633 | ========================================== 634 | 635 | echo ${a[1 + 2]} 636 | 637 | echo ${b[1234 % 2]} 638 | 639 | ${words[++counter]} 640 | 641 | ${array[(($number+1))]} 642 | 643 | ${array[((number+1))]} 644 | 645 | --- 646 | 647 | (program 648 | (command 649 | (command_name (word)) 650 | (expansion 651 | (subscript (variable_name) (binary_expression (number) (number))))) 652 | (command 653 | (command_name (word)) 654 | (expansion 655 | (subscript (variable_name) (binary_expression (number) (number))))) 656 | (command 657 | (command_name 658 | (expansion 659 | (subscript (variable_name) (unary_expression (word)))))) 660 | (command 661 | (command_name 662 | (expansion 663 | (subscript 664 | (variable_name) 665 | (arithmetic_expansion (binary_expression (simple_expansion (variable_name)) (number))))))) 666 | (command 667 | (command_name 668 | (expansion 669 | (subscript 670 | (variable_name) 671 | (arithmetic_expansion (binary_expression (variable_name) (number)))))))) 672 | 673 | ========================================== 674 | Bare $ 675 | ========================================== 676 | 677 | echo $ 678 | echo "${module}"$ 679 | echo $$ 680 | 681 | --- 682 | 683 | (program 684 | (command (command_name (word))) 685 | (command 686 | (command_name (word)) 687 | (concatenation (string (expansion (variable_name))))) 688 | (command 689 | (command_name (word)) 690 | (simple_expansion (special_variable_name)))) 691 | 692 | ========================================== 693 | Arithmetic with command substitution 694 | ========================================== 695 | 696 | $(( $( ver_cut 2 ) - 1 )) 697 | 698 | --- 699 | 700 | (program 701 | (command 702 | (command_name 703 | (arithmetic_expansion 704 | (binary_expression 705 | (command_substitution 706 | (command 707 | (command_name 708 | (word)) 709 | (number))) 710 | (number)))))) 711 | 712 | 713 | ========================================== 714 | Ralative path without dots 715 | ========================================== 716 | 717 | bin/ls /usr/bin 718 | 719 | --- 720 | 721 | (program 722 | (command 723 | (command_name 724 | (word)) 725 | (word))) 726 | -------------------------------------------------------------------------------- /test/corpus/crlf.txt: -------------------------------------------------------------------------------- 1 | ================================ 2 | Variables with CRLF line endings 3 | ================================ 4 | 5 | A=one 6 | 7 | B=two 8 | 9 | --- 10 | 11 | (program 12 | (variable_assignment (variable_name) (word)) 13 | (variable_assignment (variable_name) (word))) 14 | -------------------------------------------------------------------------------- /test/corpus/literals.txt: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | Literal words 3 | ================================================================================ 4 | 5 | echo a 6 | echo a b 7 | 8 | -------------------------------------------------------------------------------- 9 | 10 | (program 11 | (command 12 | (command_name 13 | (word)) 14 | (word)) 15 | (command 16 | (command_name 17 | (word)) 18 | (word) 19 | (word))) 20 | 21 | ================================================================================ 22 | Words with special characters 23 | ================================================================================ 24 | 25 | echo {o[k]} 26 | echo }}} 27 | echo ]]] === 28 | 29 | -------------------------------------------------------------------------------- 30 | 31 | (program 32 | (command 33 | (command_name 34 | (word)) 35 | (concatenation 36 | (word) 37 | (word) 38 | (word) 39 | (word) 40 | (word) 41 | (word))) 42 | (command 43 | (command_name 44 | (word)) 45 | (concatenation 46 | (word) 47 | (word) 48 | (word))) 49 | (command 50 | (command_name 51 | (word)) 52 | (concatenation 53 | (word) 54 | (word) 55 | (word)) 56 | (word))) 57 | 58 | ================================================================================ 59 | Simple variable expansions 60 | ================================================================================ 61 | 62 | echo $abc 63 | 64 | -------------------------------------------------------------------------------- 65 | 66 | (program 67 | (command 68 | (command_name 69 | (word)) 70 | (simple_expansion 71 | (variable_name)))) 72 | 73 | ================================================================================ 74 | Special variable expansions 75 | ================================================================================ 76 | 77 | echo $# $* $@ $! 78 | 79 | -------------------------------------------------------------------------------- 80 | 81 | (program 82 | (command 83 | (command_name 84 | (word)) 85 | (simple_expansion 86 | (special_variable_name)) 87 | (simple_expansion 88 | (special_variable_name)) 89 | (simple_expansion 90 | (special_variable_name)) 91 | (simple_expansion 92 | (special_variable_name)))) 93 | 94 | ================================================================================ 95 | Variable expansions 96 | ================================================================================ 97 | 98 | echo ${} 99 | echo ${#} 100 | echo ${var1#*#} 101 | echo ${!abc} 102 | echo ${abc} 103 | echo ${abc:-def} 104 | echo ${abc:+ghi} 105 | echo ${abc:- } 106 | echo ${abc: 107 | } 108 | echo ${abc,?} 109 | echo ${abc^^b} 110 | echo ${abc@U} 111 | echo ${abc:- -quiet} 112 | 113 | -------------------------------------------------------------------------------- 114 | 115 | (program 116 | (command (command_name (word)) (expansion)) 117 | (command (command_name (word)) (expansion)) 118 | (command (command_name (word)) (expansion (variable_name) (regex))) 119 | (command (command_name (word)) (expansion (variable_name))) 120 | (command (command_name (word)) (expansion (variable_name))) 121 | (command (command_name (word)) (expansion (variable_name) (word))) 122 | (command (command_name (word)) (expansion (variable_name) (word))) 123 | (command (command_name (word)) (expansion (variable_name) (word))) 124 | (command (command_name (word)) (expansion (variable_name))) 125 | (command (command_name (word)) (expansion (variable_name) (regex))) 126 | (command (command_name (word)) (expansion (variable_name) (regex))) 127 | (command (command_name (word)) (expansion (variable_name))) 128 | (command (command_name (word)) (expansion (variable_name) (word)))) 129 | 130 | ================================================================================ 131 | Variable expansions with operators 132 | ================================================================================ 133 | 134 | A="${B[0]# }" 135 | C="${D/#* -E /}" 136 | F="${G%% *}" 137 | H="${I#*;}" 138 | J="${K##*;}" 139 | L="${M%;*}" 140 | N="${O%%;*}" 141 | P="${Q%|*}" 142 | R="${S%()}" 143 | T="${U%(}" 144 | V="${W%)}" 145 | X="${Y%<}" 146 | Z="${A#*<B>}" 147 | C="${D%</E>*}" 148 | F="${#!}" 149 | G=${H,,[I]} 150 | J=${K^^[L]} 151 | L="${M/'N'*/O}" 152 | 153 | -------------------------------------------------------------------------------- 154 | 155 | (program 156 | (variable_assignment 157 | (variable_name) 158 | (string 159 | (expansion 160 | (subscript 161 | (variable_name) 162 | (number)) 163 | (regex)))) 164 | (variable_assignment 165 | (variable_name) 166 | (string 167 | (expansion 168 | (variable_name) 169 | (regex)))) 170 | (variable_assignment 171 | (variable_name) 172 | (string 173 | (expansion 174 | (variable_name) 175 | (regex)))) 176 | (variable_assignment 177 | (variable_name) 178 | (string 179 | (expansion 180 | (variable_name) 181 | (regex)))) 182 | (variable_assignment 183 | (variable_name) 184 | (string 185 | (expansion 186 | (variable_name) 187 | (regex)))) 188 | (variable_assignment 189 | (variable_name) 190 | (string 191 | (expansion 192 | (variable_name) 193 | (regex)))) 194 | (variable_assignment 195 | (variable_name) 196 | (string 197 | (expansion 198 | (variable_name) 199 | (regex)))) 200 | (variable_assignment 201 | (variable_name) 202 | (string 203 | (expansion 204 | (variable_name) 205 | (regex)))) 206 | (variable_assignment 207 | (variable_name) 208 | (string 209 | (expansion 210 | (variable_name) 211 | (regex)))) 212 | (variable_assignment 213 | (variable_name) 214 | (string 215 | (expansion 216 | (variable_name) 217 | (regex)))) 218 | (variable_assignment 219 | (variable_name) 220 | (string 221 | (expansion 222 | (variable_name) 223 | (regex)))) 224 | (variable_assignment 225 | (variable_name) 226 | (string 227 | (expansion 228 | (variable_name) 229 | (regex)))) 230 | (variable_assignment 231 | (variable_name) 232 | (string 233 | (expansion 234 | (variable_name) 235 | (regex)))) 236 | (variable_assignment 237 | (variable_name) 238 | (string 239 | (expansion 240 | (variable_name) 241 | (regex)))) 242 | (variable_assignment 243 | (variable_name) 244 | (string 245 | (expansion))) 246 | (variable_assignment 247 | (variable_name) 248 | (expansion 249 | (variable_name) 250 | (regex))) 251 | (variable_assignment 252 | (variable_name) 253 | (expansion 254 | (variable_name) 255 | (regex))) 256 | (variable_assignment 257 | (variable_name) 258 | (string 259 | (expansion 260 | (variable_name) 261 | (regex) 262 | (word))))) 263 | 264 | ================================================================================ 265 | More Variable expansions with operators 266 | ================================================================================ 267 | 268 | ${parameter-default} 269 | ${parameter:-default} 270 | ${parameter=default} 271 | ${parameter:=default} 272 | ${parameter+alt_value} 273 | ${parameter:+alt_value} 274 | ${parameter?err_msg} 275 | ${parameter:?err_msg} 276 | ${var%Pattern} 277 | ${var%%Pattern} 278 | ${var:pos} 279 | ${var:pos:len} 280 | ${MATRIX:$(($RANDOM%${#MATRIX})):1} 281 | ${PKG_CONFIG_LIBDIR:-${ESYSROOT}/usr/$(get_libdir)/pkgconfig} 282 | ${ver_str::${#ver_str}-${#not_match}} 283 | ${value#\{sd.cicd.} 284 | 285 | -------------------------------------------------------------------------------- 286 | 287 | (program 288 | (command 289 | (command_name 290 | (expansion 291 | (variable_name) 292 | (word)))) 293 | (command 294 | (command_name 295 | (expansion 296 | (variable_name) 297 | (word)))) 298 | (command 299 | (command_name 300 | (expansion 301 | (variable_name) 302 | (word)))) 303 | (command 304 | (command_name 305 | (expansion 306 | (variable_name) 307 | (word)))) 308 | (command 309 | (command_name 310 | (expansion 311 | (variable_name) 312 | (word)))) 313 | (command 314 | (command_name 315 | (expansion 316 | (variable_name) 317 | (word)))) 318 | (command 319 | (command_name 320 | (expansion 321 | (variable_name) 322 | (word)))) 323 | (command 324 | (command_name 325 | (expansion 326 | (variable_name) 327 | (word)))) 328 | (command 329 | (command_name 330 | (expansion 331 | (variable_name) 332 | (regex)))) 333 | (command 334 | (command_name 335 | (expansion 336 | (variable_name) 337 | (regex)))) 338 | (command 339 | (command_name 340 | (expansion 341 | (variable_name) 342 | (variable_name)))) 343 | (command 344 | (command_name 345 | (expansion 346 | (variable_name) 347 | (variable_name) 348 | (variable_name)))) 349 | (command 350 | (command_name 351 | (expansion 352 | (variable_name) 353 | (arithmetic_expansion 354 | (binary_expression 355 | (simple_expansion 356 | (variable_name)) 357 | (expansion 358 | (variable_name)))) 359 | (number)))) 360 | (command 361 | (command_name 362 | (expansion 363 | (variable_name) 364 | (concatenation 365 | (expansion (variable_name)) 366 | (word) 367 | (command_substitution 368 | (command (command_name (word)))) 369 | (word))))) 370 | (command 371 | (command_name 372 | (expansion 373 | (variable_name) 374 | (binary_expression 375 | (expansion (variable_name)) 376 | (expansion (variable_name)))))) 377 | (command 378 | (command_name 379 | (expansion 380 | (variable_name) 381 | (regex))))) 382 | 383 | ================================================================================ 384 | Variable expansions in strings 385 | ================================================================================ 386 | 387 | A="${A:-$B/c}" 388 | A="${b=$c/$d}" 389 | MY_PV="${PV/_pre/$'\x7e'pre}" 390 | 391 | -------------------------------------------------------------------------------- 392 | 393 | (program 394 | (variable_assignment 395 | (variable_name) 396 | (string 397 | (expansion 398 | (variable_name) 399 | (concatenation 400 | (simple_expansion 401 | (variable_name)) 402 | (word))))) 403 | (variable_assignment 404 | (variable_name) 405 | (string 406 | (expansion 407 | (variable_name) 408 | (concatenation 409 | (simple_expansion 410 | (variable_name)) 411 | (word) 412 | (simple_expansion 413 | (variable_name)))))) 414 | (variable_assignment 415 | (variable_name) 416 | (string 417 | (expansion 418 | (variable_name) 419 | (regex) 420 | (concatenation (ansi_c_string) (word)))))) 421 | 422 | ================================================================================ 423 | Variable expansions with regexes 424 | ================================================================================ 425 | 426 | A=${B//:;;/$'\n'} 427 | 428 | # escaped space 429 | C=${D/;\ *;|} 430 | MOFILES=${LINGUAS// /.po }.po 431 | MY_P="${PN/aspell/aspell"${ASPELL_VERSION}"}" 432 | pyc=${pyc//*\/} 433 | ${pv/\.} 434 | ${new_test_cp//"${old_ver_cp}"/} 435 | ${tests_to_run//"${classes}"\/} 436 | ${allarchives// /\\|} 437 | 438 | -------------------------------------------------------------------------------- 439 | 440 | (program 441 | (variable_assignment 442 | (variable_name) 443 | (expansion 444 | (variable_name) 445 | (regex) 446 | (ansi_c_string))) 447 | (comment) 448 | (variable_assignment 449 | (variable_name) 450 | (expansion (variable_name) (regex))) 451 | (variable_assignment 452 | (variable_name) 453 | (concatenation 454 | (expansion (variable_name) (regex) (word)) 455 | (word))) 456 | (variable_assignment 457 | (variable_name) 458 | (string 459 | (expansion 460 | (variable_name) 461 | (regex) 462 | (concatenation 463 | (word) 464 | (string (expansion (variable_name))))))) 465 | (variable_assignment 466 | (variable_name) 467 | (expansion (variable_name) (regex))) 468 | (command (command_name (expansion (variable_name) (regex)))) 469 | (command 470 | (command_name 471 | (expansion (variable_name) (string (expansion (variable_name)))))) 472 | (command 473 | (command_name 474 | (expansion (variable_name) (string (expansion (variable_name))) (regex)))) 475 | (command (command_name (expansion (variable_name) (regex) (word))))) 476 | 477 | ================================================================================ 478 | Other variable expansion operators 479 | ================================================================================ 480 | 481 | cat ${BAR} ${ABC=def} ${GHI:?jkl} 482 | [ "$a" != "${a#[Bc]}" ] 483 | 484 | -------------------------------------------------------------------------------- 485 | 486 | (program 487 | (command 488 | (command_name 489 | (word)) 490 | (expansion 491 | (variable_name)) 492 | (expansion 493 | (variable_name) 494 | (word)) 495 | (expansion 496 | (variable_name) 497 | (word))) 498 | (test_command 499 | (binary_expression 500 | (string 501 | (simple_expansion 502 | (variable_name))) 503 | (string 504 | (expansion 505 | (variable_name) 506 | (regex)))))) 507 | 508 | ================================================================================ 509 | Variable Expansions: Length 510 | ================================================================================ 511 | 512 | ${parameter:-1} 513 | 514 | ${parameter: -1} 515 | 516 | ${parameter:(-1)} 517 | 518 | ${matrix:$(($random%${#matrix})):1} 519 | 520 | "${_component_to_single:${len}:2}" 521 | 522 | "${PN::-1}" 523 | 524 | ${trarr:$(ver_cut 2):1} 525 | 526 | ${comp[@]:start:end*2-start} 527 | 528 | -------------------------------------------------------------------------------- 529 | 530 | (program 531 | (command (command_name (expansion (variable_name) (word)))) 532 | (command (command_name (expansion (variable_name) (number)))) 533 | (command 534 | (command_name 535 | (expansion (variable_name) (parenthesized_expression (unary_expression (number)))))) 536 | (command 537 | (command_name 538 | (expansion 539 | (variable_name) 540 | (arithmetic_expansion 541 | (binary_expression (simple_expansion (variable_name)) (expansion (variable_name)))) 542 | (number)))) 543 | (command (command_name (string (expansion (variable_name) (expansion (variable_name)) (number))))) 544 | (command (command_name (string (expansion (variable_name) (number))))) 545 | (command 546 | (command_name 547 | (expansion (variable_name) (command_substitution (command (command_name (word)) (number))) (number)))) 548 | (command 549 | (command_name 550 | (expansion 551 | (subscript (variable_name) (word)) 552 | (variable_name) 553 | (binary_expression (binary_expression (variable_name) (number)) (variable_name)))))) 554 | 555 | ================================================================================ 556 | Variable Expansions with operators 557 | ================================================================================ 558 | 559 | ${parameter-default} 560 | ${parameter- default} 561 | ${!varprefix*} 562 | ${!varprefix@} 563 | ${parameter@U} 564 | 565 | -------------------------------------------------------------------------------- 566 | 567 | (program 568 | (command (command_name (expansion (variable_name) (word)))) 569 | (command (command_name (expansion (variable_name) (word)))) 570 | (command (command_name (expansion (variable_name)))) 571 | (command (command_name (expansion (variable_name)))) 572 | (command (command_name (expansion (variable_name))))) 573 | 574 | ================================================================================ 575 | Variable Expansions: Bizarre Cases 576 | ================================================================================ 577 | 578 | ${!#} 579 | ${!# } 580 | ${!##} 581 | ${!## } 582 | ${!##/} 583 | # here be dragons 584 | echo "${kw}? ( ${cond:+${cond}? (} ${baseuri}-${ver}-${kw}.${suff} ${cond:+) })" 585 | 586 | -------------------------------------------------------------------------------- 587 | 588 | (program 589 | (command (command_name (expansion))) 590 | (command (command_name (expansion))) 591 | (command (command_name (expansion))) 592 | (command (command_name (expansion))) 593 | (command (command_name (expansion (special_variable_name) (regex)))) 594 | (comment) 595 | (command 596 | (command_name (word)) 597 | (string 598 | (expansion (variable_name)) 599 | (string_content) 600 | (expansion 601 | (variable_name) 602 | (concatenation (expansion (variable_name)) (word))) 603 | (expansion (variable_name)) 604 | (string_content) 605 | (expansion (variable_name)) 606 | (string_content) 607 | (expansion (variable_name)) 608 | (string_content) 609 | (expansion (variable_name)) 610 | (expansion (variable_name) (word)) 611 | (string_content)))) 612 | 613 | ================================================================================ 614 | Variable Expansions: Weird Cases 615 | ================================================================================ 616 | 617 | ${completions[*]} 618 | ${=1} 619 | ${2?} 620 | ${p_key#*=} 621 | ${abc:- } 622 | ${B[0]# } 623 | ${to_enables[0]##*/} 624 | exec "${0#-}" --rcfile "${BASH_IT_BASHRC:-${HOME?}/.bashrc}" 625 | recho "TDEFAULTS = ${selvecs:+-DSELECT_VECS=\"$selvecs\"}" 626 | local msg="${2:-command '$1' does not exist}" 627 | ${cdir:+#} 628 | ${dict_langs:+;} 629 | ${UTIL_LINUX_LIBC[@]/%/? ( sys-apps/util-linux )} 630 | ${id}${2+ ${2}} 631 | ${BRANDING_GCC_PKGVERSION/(/(Gentoo ${PVR}${extvers}, } # look at that parenthesis! 632 | some-command ${foo:+--arg <(printf '%s\n' "$foo")} 633 | 634 | -------------------------------------------------------------------------------- 635 | 636 | (program 637 | (command (command_name (expansion (subscript (variable_name) (word))))) 638 | (command (command_name (expansion (variable_name)))) 639 | (command (command_name (expansion (variable_name)))) 640 | (command (command_name (expansion (variable_name) (regex)))) 641 | (command (command_name (expansion (variable_name) (word)))) 642 | (command (command_name (expansion (subscript (variable_name) (number)) (regex)))) 643 | (command (command_name (expansion (subscript (variable_name) (number)) (regex)))) 644 | (command 645 | (command_name (word)) 646 | (string (expansion (special_variable_name) (regex))) 647 | (word) 648 | (string (expansion (variable_name) (concatenation (expansion (variable_name)) (word))))) 649 | (command 650 | (command_name (word)) 651 | (string 652 | (string_content) 653 | (expansion (variable_name) (concatenation (word) (simple_expansion (variable_name)) (word))))) 654 | (declaration_command 655 | (variable_assignment 656 | (variable_name) 657 | (string (expansion (variable_name) (concatenation (word) (raw_string) (word)))))) 658 | (command (command_name (expansion (variable_name) (word)))) 659 | (command (command_name (expansion (variable_name) (word)))) 660 | (command (command_name (expansion (subscript (variable_name) (word)) (word)))) 661 | (command 662 | (command_name 663 | (concatenation (expansion (variable_name)) (expansion (variable_name) (expansion (variable_name)))))) 664 | (command 665 | (command_name 666 | (expansion 667 | (variable_name) 668 | (regex) 669 | (concatenation (word) (expansion (variable_name)) (expansion (variable_name)) (word))))) 670 | (comment) 671 | (command 672 | (command_name (word)) 673 | (expansion 674 | (variable_name) 675 | (concatenation 676 | (word) 677 | (process_substitution 678 | (command 679 | (command_name (word)) 680 | (raw_string) 681 | (string (simple_expansion (variable_name))))))))) 682 | 683 | ================================================================================ 684 | Variable Expansions: Regex 685 | ================================================================================ 686 | 687 | A=${B//:;;/$'\n'} 688 | C="${D/#* -E /}" 689 | BASH_IT_GIT_URL="${BASH_IT_GIT_URL/git@/https://}" 690 | 10#${command_start##*.} 691 | echo ${LIB_DEPEND//\[static-libs(+)]} 692 | ${ALL_LLVM_TARGETS[@]/%/(-)?} 693 | filterdiff -p1 ${paths[@]/#/-i } 694 | ${cflags//-O? /$(get-flag O) } 695 | curf="${f%'-roff2html'*}.html" 696 | reff="${f/'-roff2html'*/'-ref'}.html" 697 | 698 | -------------------------------------------------------------------------------- 699 | 700 | (program 701 | (variable_assignment (variable_name) (expansion (variable_name) (regex) (ansi_c_string))) 702 | (variable_assignment (variable_name) (string (expansion (variable_name) (regex)))) 703 | (variable_assignment (variable_name) (string (expansion (variable_name) (regex) (word)))) 704 | (command (command_name (number (expansion (variable_name) (regex))))) 705 | (command (command_name (word)) (expansion (variable_name) (regex))) 706 | (command (command_name (expansion (subscript (variable_name) (word)) (word)))) 707 | (command (command_name (word)) (word) (expansion (subscript (variable_name) (word)) (word))) 708 | (command 709 | (command_name 710 | (expansion (variable_name) (regex) (command_substitution (command (command_name (word)) (word))) (word)))) 711 | (variable_assignment 712 | (variable_name) 713 | (string (expansion (variable_name) (raw_string) (regex)) (string_content))) 714 | (variable_assignment 715 | (variable_name) 716 | (string (expansion (variable_name) (regex) (raw_string)) (string_content)))) 717 | 718 | ================================================================================ 719 | Words ending with '$' 720 | ================================================================================ 721 | 722 | grep ^${var}$ 723 | 724 | -------------------------------------------------------------------------------- 725 | 726 | (program 727 | (command 728 | (command_name 729 | (word)) 730 | (concatenation 731 | (word) 732 | (expansion 733 | (variable_name))))) 734 | 735 | ================================================================================ 736 | Command substitutions 737 | ================================================================================ 738 | 739 | echo `echo hi` 740 | echo `echo hi; echo there` 741 | echo $(echo $(echo hi)) 742 | echo $(< some-file) 743 | 744 | # both of these are concatenations! 745 | echo `echo otherword`word 746 | echo word`echo otherword` 747 | 748 | -------------------------------------------------------------------------------- 749 | 750 | (program 751 | (command 752 | (command_name 753 | (word)) 754 | (command_substitution 755 | (command 756 | (command_name 757 | (word)) 758 | (word)))) 759 | (command 760 | (command_name 761 | (word)) 762 | (command_substitution 763 | (command 764 | (command_name 765 | (word)) 766 | (word)) 767 | (command 768 | (command_name 769 | (word)) 770 | (word)))) 771 | (command 772 | (command_name 773 | (word)) 774 | (command_substitution 775 | (command 776 | (command_name 777 | (word)) 778 | (command_substitution 779 | (command 780 | (command_name 781 | (word)) 782 | (word)))))) 783 | (command 784 | (command_name 785 | (word)) 786 | (command_substitution 787 | (file_redirect 788 | (word)))) 789 | (comment) 790 | (command 791 | (command_name 792 | (word)) 793 | (concatenation 794 | (command_substitution 795 | (command 796 | (command_name 797 | (word)) 798 | (word))) 799 | (word))) 800 | (command 801 | (command_name 802 | (word)) 803 | (concatenation 804 | (word) 805 | (command_substitution 806 | (command 807 | (command_name 808 | (word)) 809 | (word)))))) 810 | 811 | ================================================================================ 812 | Process substitutions 813 | ================================================================================ 814 | 815 | wc -c <(echo abc && echo def) 816 | wc -c <(echo abc; echo def) 817 | echo abc > >(wc -c) 818 | 819 | -------------------------------------------------------------------------------- 820 | 821 | (program 822 | (command 823 | (command_name 824 | (word)) 825 | (word) 826 | (process_substitution 827 | (list 828 | (command 829 | (command_name 830 | (word)) 831 | (word)) 832 | (command 833 | (command_name 834 | (word)) 835 | (word))))) 836 | (command 837 | (command_name 838 | (word)) 839 | (word) 840 | (process_substitution 841 | (command 842 | (command_name 843 | (word)) 844 | (word)) 845 | (command 846 | (command_name 847 | (word)) 848 | (word)))) 849 | (redirected_statement 850 | (command 851 | (command_name 852 | (word)) 853 | (word)) 854 | (file_redirect 855 | (process_substitution 856 | (command 857 | (command_name 858 | (word)) 859 | (word)))))) 860 | 861 | ================================================================================ 862 | Single quoted strings 863 | ================================================================================ 864 | 865 | echo 'a b' 'c d' 866 | 867 | -------------------------------------------------------------------------------- 868 | 869 | (program 870 | (command 871 | (command_name 872 | (word)) 873 | (raw_string) 874 | (raw_string))) 875 | 876 | ================================================================================ 877 | Double quoted strings 878 | ================================================================================ 879 | 880 | echo "a" "b" 881 | echo "a ${b} c" "d $e" 882 | 883 | -------------------------------------------------------------------------------- 884 | 885 | (program 886 | (command 887 | (command_name 888 | (word)) 889 | (string (string_content)) 890 | (string (string_content))) 891 | (command 892 | (command_name 893 | (word)) 894 | (string 895 | (string_content) 896 | (expansion 897 | (variable_name)) 898 | (string_content)) 899 | (string 900 | (string_content) 901 | (simple_expansion 902 | (variable_name))))) 903 | 904 | ================================================================================ 905 | Strings containing command substitutions 906 | ================================================================================ 907 | 908 | find "`dirname $file`" -name "$base"'*' 909 | 910 | -------------------------------------------------------------------------------- 911 | 912 | (program 913 | (command 914 | (command_name 915 | (word)) 916 | (string 917 | (command_substitution 918 | (command 919 | (command_name 920 | (word)) 921 | (simple_expansion 922 | (variable_name))))) 923 | (word) 924 | (concatenation 925 | (string 926 | (simple_expansion 927 | (variable_name))) 928 | (raw_string)))) 929 | 930 | ================================================================================ 931 | Strings containing escape sequence 932 | ================================================================================ 933 | 934 | echo "\"The great escape\`\${var}" 935 | 936 | -------------------------------------------------------------------------------- 937 | 938 | (program 939 | (command 940 | (command_name 941 | (word)) 942 | (string (string_content)))) 943 | 944 | ================================================================================ 945 | Strings containing special characters 946 | ================================================================================ 947 | 948 | echo "s/$/'/" 949 | echo "#" 950 | echo "s$" 951 | 952 | -------------------------------------------------------------------------------- 953 | 954 | (program 955 | (command 956 | (command_name 957 | (word)) 958 | (string (string_content) (string_content))) 959 | (command 960 | (command_name 961 | (word)) 962 | (string (string_content))) 963 | (command 964 | (command_name 965 | (word)) 966 | (string (string_content)))) 967 | 968 | ================================================================================ 969 | Strings with ANSI-C quoting 970 | ================================================================================ 971 | 972 | echo $'Here\'s Johnny!\r\n' 973 | 974 | -------------------------------------------------------------------------------- 975 | 976 | (program 977 | (command 978 | (command_name 979 | (word)) 980 | (ansi_c_string))) 981 | 982 | ================================================================================ 983 | Arrays and array expansions 984 | ================================================================================ 985 | 986 | a=() 987 | b=(1 2 3) 988 | 989 | echo ${a[@]} 990 | echo ${#b[@]} 991 | 992 | a[$i]=50 993 | a+=(foo "bar" $(baz)) 994 | 995 | printf " %-9s" "${seq0:-(default)}" 996 | 997 | -------------------------------------------------------------------------------- 998 | 999 | (program 1000 | (variable_assignment 1001 | (variable_name) 1002 | (array)) 1003 | (variable_assignment 1004 | (variable_name) 1005 | (array 1006 | (number) 1007 | (number) 1008 | (number))) 1009 | (command 1010 | (command_name 1011 | (word)) 1012 | (expansion 1013 | (subscript 1014 | (variable_name) 1015 | (word)))) 1016 | (command 1017 | (command_name 1018 | (word)) 1019 | (expansion 1020 | (subscript 1021 | (variable_name) 1022 | (word)))) 1023 | (variable_assignment 1024 | (subscript 1025 | (variable_name) 1026 | (simple_expansion 1027 | (variable_name))) 1028 | (number)) 1029 | (variable_assignment 1030 | (variable_name) 1031 | (array 1032 | (word) 1033 | (string (string_content)) 1034 | (command_substitution 1035 | (command 1036 | (command_name 1037 | (word)))))) 1038 | (command 1039 | (command_name 1040 | (word)) 1041 | (string (string_content)) 1042 | (string 1043 | (expansion 1044 | (variable_name) 1045 | (array 1046 | (word)))))) 1047 | 1048 | ================================================================================ 1049 | Escaped characters in strings 1050 | ================================================================================ 1051 | 1052 | echo -ne "\033k$1\033\\" > /dev/stderr 1053 | 1054 | -------------------------------------------------------------------------------- 1055 | 1056 | (program 1057 | (redirected_statement 1058 | (command 1059 | (command_name 1060 | (word)) 1061 | (word) 1062 | (string 1063 | (string_content) 1064 | (simple_expansion 1065 | (variable_name)) 1066 | (string_content))) 1067 | (file_redirect 1068 | (word)))) 1069 | 1070 | ================================================================================ 1071 | Words containing bare '#' 1072 | ================================================================================ 1073 | 1074 | curl -# localhost #comment without space 1075 | nix build nixpkgs#hello -v # comment with space 1076 | 1077 | -------------------------------------------------------------------------------- 1078 | 1079 | (program 1080 | (command 1081 | (command_name 1082 | (word)) 1083 | (word) 1084 | (word)) 1085 | (comment) 1086 | (command 1087 | (command_name 1088 | (word)) 1089 | (word) 1090 | (word) 1091 | (word)) 1092 | (comment)) 1093 | 1094 | ================================================================================ 1095 | Words containing # that are not comments 1096 | ================================================================================ 1097 | 1098 | echo 'word'#not-comment # a legit comment 1099 | echo $(uname -a)#not-comment # a legit comment 1100 | echo `uname -a`#not-comment # a legit comment 1101 | echo $hey#not-comment # a legit comment 1102 | var=#not-comment # a legit comment 1103 | echo "'$var'" # -> '#not-comment' 1104 | 1105 | -------------------------------------------------------------------------------- 1106 | 1107 | (program 1108 | (command 1109 | (command_name 1110 | (word)) 1111 | (concatenation 1112 | (raw_string) 1113 | (word))) 1114 | (comment) 1115 | (command 1116 | (command_name 1117 | (word)) 1118 | (concatenation 1119 | (command_substitution 1120 | (command 1121 | (command_name 1122 | (word)) 1123 | (word))) 1124 | (word))) 1125 | (comment) 1126 | (command 1127 | (command_name 1128 | (word)) 1129 | (concatenation 1130 | (command_substitution 1131 | (command 1132 | (command_name 1133 | (word)) 1134 | (word))) 1135 | (word))) 1136 | (comment) 1137 | (command 1138 | (command_name 1139 | (word)) 1140 | (concatenation 1141 | (simple_expansion 1142 | (variable_name)) 1143 | (word))) 1144 | (comment) 1145 | (variable_assignment 1146 | (variable_name) 1147 | (word)) 1148 | (comment) 1149 | (command 1150 | (command_name 1151 | (word)) 1152 | (string 1153 | (string_content) 1154 | (simple_expansion 1155 | (variable_name)) 1156 | (string_content))) 1157 | (comment)) 1158 | 1159 | ================================================================================ 1160 | Variable assignments immediately followed by a terminator 1161 | ================================================================================ 1162 | 1163 | loop=; variables=& here=;; 1164 | 1165 | -------------------------------------------------------------------------------- 1166 | 1167 | (program 1168 | (variable_assignment 1169 | (variable_name)) 1170 | (variable_assignment 1171 | (variable_name)) 1172 | (variable_assignment 1173 | (variable_name))) 1174 | 1175 | ================================================================================ 1176 | Multiple variable assignments 1177 | ================================================================================ 1178 | 1179 | component_type="${1}" item_name="${2?}" 1180 | 1181 | -------------------------------------------------------------------------------- 1182 | 1183 | (program 1184 | (variable_assignments 1185 | (variable_assignment 1186 | (variable_name) 1187 | (string 1188 | (expansion 1189 | (variable_name)))) 1190 | (variable_assignment 1191 | (variable_name) 1192 | (string 1193 | (expansion 1194 | (variable_name)))))) 1195 | 1196 | ================================================================================ 1197 | Arithmetic expansions 1198 | ================================================================================ 1199 | 1200 | echo $((1 + 2 - 3 * 4 / 5)) 1201 | a=$((6 % 7 ** 8 << 9 >> 10 & 11 | 12 ^ 13)) 1202 | $(((${1:-${SECONDS}} % 12) + 144)) 1203 | ((foo=0)) 1204 | echo $((bar=1)) 1205 | echo $((-1, 1)) 1206 | echo $((! -a || ~ +b || ++c || --d)) 1207 | echo $((foo-- || bar++)) 1208 | (("${MULTIBUILD_VARIANTS}" > 1)) 1209 | $(("$(stat --printf '%05a' "${save_file}")" & 07177)) 1210 | soft_errors_count=$[soft_errors_count + 1] 1211 | 1212 | -------------------------------------------------------------------------------- 1213 | 1214 | (program 1215 | (command 1216 | (command_name (word)) 1217 | (arithmetic_expansion 1218 | (binary_expression 1219 | (binary_expression (number) (number)) 1220 | (binary_expression 1221 | (binary_expression (number) (number)) 1222 | (number))))) 1223 | (variable_assignment 1224 | (variable_name) 1225 | (arithmetic_expansion 1226 | (binary_expression 1227 | (binary_expression 1228 | (binary_expression 1229 | (binary_expression 1230 | (binary_expression 1231 | (number) 1232 | (binary_expression (number) (number))) 1233 | (number)) 1234 | (number)) 1235 | (number)) 1236 | (binary_expression (number) (number))))) 1237 | (command 1238 | (command_name 1239 | (arithmetic_expansion 1240 | (binary_expression 1241 | (parenthesized_expression 1242 | (binary_expression 1243 | (expansion 1244 | (variable_name) 1245 | (expansion (variable_name))) 1246 | (number))) 1247 | (number))))) 1248 | (command 1249 | (command_name 1250 | (arithmetic_expansion 1251 | (binary_expression (variable_name) (number))))) 1252 | (command 1253 | (command_name (word)) 1254 | (arithmetic_expansion 1255 | (binary_expression (variable_name) (number)))) 1256 | (command 1257 | (command_name (word)) 1258 | (arithmetic_expansion (unary_expression (number)) (number))) 1259 | (command 1260 | (command_name (word)) 1261 | (arithmetic_expansion 1262 | (binary_expression 1263 | (binary_expression 1264 | (binary_expression 1265 | (unary_expression (unary_expression (variable_name))) 1266 | (unary_expression (unary_expression (variable_name)))) 1267 | (unary_expression (variable_name))) 1268 | (unary_expression (variable_name))))) 1269 | (command 1270 | (command_name (word)) 1271 | (arithmetic_expansion 1272 | (binary_expression 1273 | (postfix_expression 1274 | (variable_name)) 1275 | (postfix_expression 1276 | (variable_name))))) 1277 | (command 1278 | (command_name 1279 | (arithmetic_expansion 1280 | (binary_expression 1281 | (string 1282 | (expansion 1283 | (variable_name))) 1284 | (number))))) 1285 | (command 1286 | (command_name 1287 | (arithmetic_expansion 1288 | (binary_expression 1289 | (string 1290 | (command_substitution 1291 | (command 1292 | (command_name 1293 | (word)) 1294 | (word) 1295 | (raw_string) 1296 | (string 1297 | (expansion 1298 | (variable_name)))))) 1299 | (number))))) 1300 | (variable_assignment 1301 | (variable_name) 1302 | (arithmetic_expansion 1303 | (binary_expression 1304 | (variable_name) 1305 | (number))))) 1306 | 1307 | ================================================================================ 1308 | Concatenation with double backticks 1309 | ================================================================================ 1310 | 1311 | main() { 1312 | local foo="asd"` 1313 | `"fgh" 1314 | } 1315 | 1316 | --- 1317 | 1318 | (program 1319 | (function_definition 1320 | (word) 1321 | (compound_statement 1322 | (declaration_command 1323 | (variable_assignment 1324 | (variable_name) 1325 | (concatenation 1326 | (string (string_content)) 1327 | (string (string_content)))))))) 1328 | 1329 | ================================================================================ 1330 | Brace expressions and lookalikes 1331 | ================================================================================ 1332 | 1333 | echo {1..2} 1334 | echo {0..5} 1335 | echo {0..2 # not a brace expression 1336 | echo }{0..2} 1337 | echo {0..n} # not a brace expression 1338 | echo {0..n..2} # not a brace expression 1339 | echo {0..2}{1..2} 1340 | 1341 | --- 1342 | 1343 | (program 1344 | (command 1345 | (command_name (word)) 1346 | (brace_expression (number) (number))) 1347 | (command 1348 | (command_name (word)) 1349 | (brace_expression (number) (number))) 1350 | (command 1351 | (command_name (word)) 1352 | (concatenation (word) (word))) 1353 | (comment) 1354 | (command 1355 | (command_name (word)) 1356 | (concatenation 1357 | (word) 1358 | (brace_expression (number) (number)))) 1359 | (command 1360 | (command_name (word)) 1361 | (concatenation (word) (word) (word))) 1362 | (comment) 1363 | (command 1364 | (command_name (word)) 1365 | (concatenation (word) (word) (word))) 1366 | (comment) 1367 | (command 1368 | (command_name (word)) 1369 | (concatenation 1370 | (brace_expression (number) (number)) 1371 | (brace_expression (number) (number))))) 1372 | -------------------------------------------------------------------------------- /test/corpus/programs.txt: -------------------------------------------------------------------------------- 1 | =============================== 2 | Comments 3 | =============================== 4 | 5 | #!/bin/bash 6 | # hi 7 | 8 | --- 9 | 10 | (program 11 | (comment) 12 | (comment)) 13 | 14 | =============================== 15 | Escaped newlines 16 | =============================== 17 | 18 | abc \ 19 | d \ 20 | e 21 | 22 | f=g \ 23 | h=i \ 24 | j \ 25 | --k 26 | 27 | --- 28 | 29 | (program 30 | (command 31 | (command_name 32 | (word)) 33 | (word) 34 | (word)) 35 | (command 36 | (variable_assignment 37 | (variable_name) 38 | (word)) 39 | (variable_assignment 40 | (variable_name) 41 | (word)) 42 | (command_name 43 | (word)) 44 | (word))) 45 | 46 | ============================= 47 | escaped newline immediately after a char 48 | ============================= 49 | 50 | echo a \ 51 | b 52 | 53 | echo a\ 54 | b 55 | 56 | echo a\ 57 | b\ 58 | c 59 | 60 | 61 | ----------------------------- 62 | 63 | (program 64 | (command 65 | (command_name 66 | (word)) 67 | (word) 68 | (word)) 69 | (command 70 | (command_name 71 | (word)) 72 | (word) 73 | (word)) 74 | (command 75 | (command_name 76 | (word)) 77 | (word) 78 | (word) 79 | (word))) 80 | 81 | ============================= 82 | Escaped whitespace 83 | ============================= 84 | 85 | echo 1 \ 2 \ 3 86 | 87 | --- 88 | 89 | (program 90 | (command 91 | (command_name 92 | (word)) 93 | (number) 94 | (number) 95 | (number))) 96 | 97 | ==================================== 98 | Files without trailing terminators 99 | ==================================== 100 | 101 | echo hi 102 | --- 103 | 104 | (program 105 | (command 106 | (command_name 107 | (word)) 108 | (word))) 109 | -------------------------------------------------------------------------------- /tree-sitter.json: -------------------------------------------------------------------------------- 1 | { 2 | "grammars": [ 3 | { 4 | "name": "bash", 5 | "camelcase": "Bash", 6 | "scope": "source.bash", 7 | "path": ".", 8 | "file-types": [ 9 | "sh", 10 | "bash", 11 | ".bashrc", 12 | ".bash_profile", 13 | "ebuild", 14 | "eclass" 15 | ], 16 | "injection-regex": "^(shell|bash|sh)$", 17 | "first-line-regex": "^#!.*\\b(sh|bash|dash)\\b.*$" 18 | } 19 | ], 20 | "metadata": { 21 | "version": "0.23.3", 22 | "license": "MIT", 23 | "description": "Bash grammar for tree-sitter", 24 | "authors": [ 25 | { 26 | "name": "Max Brunsfeld", 27 | "email": "maxbrunsfeld@gmail.com" 28 | }, 29 | { 30 | "name": "Amaan Qureshi", 31 | "email": "amaanq12@gmail.com" 32 | } 33 | ], 34 | "links": { 35 | "repository": "https://github.com/tree-sitter/tree-sitter-bash" 36 | } 37 | }, 38 | "bindings": { 39 | "c": true, 40 | "go": true, 41 | "node": true, 42 | "python": true, 43 | "rust": true, 44 | "swift": true 45 | } 46 | } 47 | --------------------------------------------------------------------------------