Use tree-sitter-bash from crates.io

pull/795/head
Wilfred Hughes 2024-12-20 08:32:54 +07:00
parent 7e8974e295
commit f9d663ea9f
51 changed files with 20 additions and 238341 deletions

@ -10,9 +10,9 @@ with YAML.
Improved language detection when one argument is a named pipe.
Updated to the latest tree-sitter parser for C, C++, C#, Go, Haskell,
Java, JavaScript, Julia, Objective-C, OCaml, Python, Ruby, Scala and
TypeScript.
Updated to the latest tree-sitter parser for Bash, C, C++, C#, Go,
Haskell, Java, JavaScript, Julia, Objective-C, OCaml, Python, Ruby,
Scala and TypeScript.
### Syntax Highlighting

11
Cargo.lock generated

@ -249,6 +249,7 @@ dependencies = [
"strsim",
"strum",
"tree-sitter",
"tree-sitter-bash",
"tree-sitter-c",
"tree-sitter-c-sharp",
"tree-sitter-cpp",
@ -1015,6 +1016,16 @@ dependencies = [
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-bash"
version = "0.23.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "329a4d48623ac337d42b1df84e81a1c9dbb2946907c102ca72db158c1964a52e"
dependencies = [
"cc",
"tree-sitter-language",
]
[[package]]
name = "tree-sitter-c"
version = "0.23.4"

@ -92,6 +92,7 @@ tree-sitter-typescript = "0.23.2"
tree-sitter-java = "0.23.4"
tree-sitter-julia = "0.23.1"
tree-sitter-go = "0.23.4"
tree-sitter-bash = "0.23.3"
[dev-dependencies]
# assert_cmd 2.0.10 requires predicates 3.

@ -74,11 +74,6 @@ fn main() {
src_dir: "vendored_parsers/tree-sitter-ada-src",
extra_files: vec![],
},
TreeSitterParser {
name: "tree-sitter-bash",
src_dir: "vendored_parsers/tree-sitter-bash-src",
extra_files: vec!["scanner.c"],
},
TreeSitterParser {
name: "tree-sitter-clojure",
src_dir: "vendored_parsers/tree-sitter-clojure-src",

@ -63,7 +63,6 @@ pub(crate) struct TreeSitterConfig {
extern "C" {
fn tree_sitter_ada() -> ts::Language;
fn tree_sitter_apex() -> ts::Language;
fn tree_sitter_bash() -> ts::Language;
fn tree_sitter_clojure() -> ts::Language;
fn tree_sitter_cmake() -> ts::Language;
fn tree_sitter_commonlisp() -> ts::Language;
@ -163,18 +162,17 @@ pub(crate) fn from_language(language: guess::Language) -> TreeSitterConfig {
}
}
Bash => {
let language = unsafe { tree_sitter_bash() };
let language_fn = tree_sitter_bash::LANGUAGE;
let language = tree_sitter::Language::new(language_fn);
TreeSitterConfig {
language: language.clone(),
atom_nodes: vec!["string", "raw_string", "heredoc_body"]
.into_iter()
.collect(),
delimiter_tokens: vec![("(", ")"), ("{", "}"), ("[", "]")],
highlight_query: ts::Query::new(
&language,
include_str!("../../vendored_parsers/highlights/bash.scm"),
)
.unwrap(),
highlight_query: ts::Query::new(&language, tree_sitter_bash::HIGHLIGHT_QUERY)
.unwrap(),
sub_languages: vec![],
}
}

@ -1 +0,0 @@
../tree-sitter-bash/queries/highlights.scm

@ -1 +0,0 @@
tree-sitter-bash/src

@ -1,20 +0,0 @@
module.exports = {
'env': {
'commonjs': true,
'es2021': true,
},
'extends': 'google',
'overrides': [
],
'parserOptions': {
'ecmaVersion': 'latest',
'sourceType': 'module',
},
'rules': {
'indent': ['error', 2, {'SwitchCase': 1}],
'max-len': [
'error',
{'code': 120, 'ignoreComments': true, 'ignoreUrls': true, 'ignoreStrings': true},
],
},
};

@ -1,6 +0,0 @@
/examples/* linguist-vendored
+src/tree_sitter/* linguist-generated
src/grammar.json linguist-generated
src/node-types.json linguist-generated
src/parser.c linguist-generated

@ -1,37 +0,0 @@
name: CI
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
os: [macos-latest, ubuntu-latest]
steps:
- uses: actions/checkout@v3
with:
submodules: true
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm install
- run: npm test
test_windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
with:
submodules: true
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm install
- run: npm run-script test-windows

@ -1,22 +0,0 @@
name: Fuzz Parser
on:
push:
paths:
- src/scanner.c
pull_request:
paths:
- src/scanner.c
workflow_dispatch:
jobs:
test:
name: Parser fuzzing
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: vigoux/tree-sitter-fuzz-action@v1
with:
language: bash
external-scanner: src/scanner.c
time: 60

@ -1,19 +0,0 @@
name: Lint
on:
push:
branches:
- master
pull_request:
branches:
- "**"
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install modules
run: npm install
- name: Run ESLint
run: npm run lint

@ -1,93 +0,0 @@
name: Pack
on:
release:
types:
- released
env:
NODE_PREBUILD_CMD: npx prebuild -t 10.0.0 -t 12.0.0 -t 14.0.0 -t 16.0.0 -t 18.0.0 -t 20.0.0 --strip -u ${{ secrets.GH_TOKEN }}
ELECTRON_PREBUILD_CMD: npx prebuild -r electron -t 3.0.0 -t 4.0.0 -t 5.0.0 -t 6.0.0 -t 7.0.0 -t 8.0.0 -t 9.0.0 -t 10.0.0 -t 11.0.0 -t 12.0.0 -t 13.0.0 -t 14.0.0 -t 15.0.0 -t 16.0.0 -t 17.0.0 -t 18.0.0 -t 19.0.0 -t 20.0.0 -t 21.0.0 -t 22.0.0 -t 23.0.0 -t 24.0.0 -t 25.0.0 --strip -u ${{ secrets.GH_TOKEN }}
jobs:
test:
strategy:
matrix:
os:
- macos-latest
- ubuntu-latest
node:
- 10
- 12
- 14
- 16
- 18
- 20
fail-fast: false
name: Testing Node ${{ matrix.node }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
with:
submodules: true
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- run: npm install
- run: npm test
test-windows:
strategy:
matrix:
os:
- windows-2019
node:
- 10
- 12
- 14
- 16
- 18
- 20
fail-fast: false
name: Testing Node ${{ matrix.node }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
with:
submodules: true
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- run: npm install
- run: npm run test-windows
prebuild:
strategy:
matrix:
os:
- windows-2019
- macos-latest
- ubuntu-latest
fail-fast: false
name: Prebuild for ${{ matrix.os }}
runs-on: ${{ matrix.os }}
needs: [test, test-windows]
steps:
- uses: actions/checkout@v3
with:
submodules: true
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: 20
- run: npm install
- if: runner.os == 'macOS'
run: ${{ env.NODE_PREBUILD_CMD }} --arch arm64
- if: runner.os == 'macOS'
run: ${{ env.ELECTRON_PREBUILD_CMD }} --arch arm64
- run: ${{ env.NODE_PREBUILD_CMD }}
- run: ${{ env.ELECTRON_PREBUILD_CMD }}

@ -1,103 +0,0 @@
name: Release
on:
workflow_run:
workflows: ["CI"]
branches:
- master
types:
- completed
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get previous commit SHA
id: get_previous_commit
run: |
LATEST_TAG=$(git describe --tags --abbrev=0)
if [[ -z "$LATEST_TAG" ]]; then
echo "No tag found. Failing..."
exit 1
fi
echo "latest_tag=${LATEST_TAG#v}" >> "$GITHUB_ENV" # Remove 'v' prefix from the tag
- name: Check if version changed and is greater than the previous
id: version_check
run: |
# Compare the current version with the version from the previous commit
PREVIOUS_NPM_VERSION=${{ env.latest_tag }}
CURRENT_NPM_VERSION=$(jq -r '.version' package.json)
CURRENT_CARGO_VERSION=$(awk -F '"' '/^version/ {print $2}' Cargo.toml)
if [[ "$CURRENT_NPM_VERSION" != "$CURRENT_CARGO_VERSION" ]]; then # Cargo.toml and package.json versions must match
echo "Mismatch: NPM version ($CURRENT_NPM_VERSION) and Cargo.toml version ($CURRENT_CARGO_VERSION)"
echo "version_changed=false" >> "$GITHUB_ENV"
else
if [[ "$PREVIOUS_NPM_VERSION" == "$CURRENT_NPM_VERSION" ]]; then
echo "version_changed=" >> "$GITHUB_ENV"
else
IFS='.' read -ra PREVIOUS_VERSION_PARTS <<< "$PREVIOUS_NPM_VERSION"
IFS='.' read -ra CURRENT_VERSION_PARTS <<< "$CURRENT_NPM_VERSION"
VERSION_CHANGED=false
for i in "${!PREVIOUS_VERSION_PARTS[@]}"; do
if [[ ${CURRENT_VERSION_PARTS[i]} -gt ${PREVIOUS_VERSION_PARTS[i]} ]]; then
VERSION_CHANGED=true
break
elif [[ ${CURRENT_VERSION_PARTS[i]} -lt ${PREVIOUS_VERSION_PARTS[i]} ]]; then
break
fi
done
echo "version_changed=$VERSION_CHANGED" >> "$GITHUB_ENV"
echo "current_version=${CURRENT_NPM_VERSION}" >> "$GITHUB_ENV"
fi
fi
- name: Display result
run: |
echo "Version bump detected: ${{ env.version_changed }}"
- name: Fail if version is lower
if: env.version_changed == 'false'
run: exit 1
- name: Setup Node
if: env.version_changed == 'true'
uses: actions/setup-node@v3
with:
node-version: 18
registry-url: "https://registry.npmjs.org"
- name: Publish to NPM
if: env.version_changed == 'true'
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
run: npm publish
- name: Setup Rust
if: env.version_changed == 'true'
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Publish to Crates.io
if: env.version_changed == 'true'
uses: katyo/publish-crates@v2
with:
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Tag versions
if: env.version_changed == 'true'
run: |
git checkout master
git config user.name github-actions[bot]
git config user.email github-actions[bot]@users.noreply.github.com
git tag -d "v${{ env.current_version }}" || true
git push origin --delete "v${{ env.current_version }}" || true
git tag -a "v${{ env.current_version }}" -m "Version ${{ env.current_version }}"
git push origin "v${{ env.current_version }}"

@ -1,7 +0,0 @@
Cargo.lock
package-lock.json
node_modules
build
*.log
/examples/*/
/target/

@ -1,6 +0,0 @@
/test
/examples
/build
/script
/target
bindings/rust

@ -1,24 +0,0 @@
[package]
name = "tree-sitter-bash"
description = "Bash grammar for tree-sitter"
version = "0.20.2"
authors = ["Max Brunsfeld <maxbrunsfeld@gmail.com"]
license = "MIT"
readme = "bindings/rust/README.md"
keywords = ["incremental", "parsing", "bash"]
categories = ["parsing", "text-editors"]
repository = "https://github.com/tree-sitter/tree-sitter-bash"
edition = "2021"
autoexamples = false
build = "bindings/rust/build.rs"
include = ["bindings/rust/*", "grammar.js", "queries/*", "src/*"]
[lib]
path = "bindings/rust/lib.rs"
[dependencies]
tree-sitter = "~0.20.10"
[build-dependencies]
cc = "~1.0.82"

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2017 Max Brunsfeld
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -1,39 +0,0 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "TreeSitterBash",
products: [
.library(name: "TreeSitterBash", targets: ["TreeSitterBash"]),
],
dependencies: [],
targets: [
.target(name: "TreeSitterBash",
path: ".",
exclude: [
"binding.gyp",
"bindings",
"Cargo.toml",
"corpus",
"grammar.js",
"LICENSE",
"Makefile",
"package.json",
"README.md",
"script",
".travis.yml",
"appveyor.yaml",
"src/grammar.json",
"src/node-types.json",
],
sources: [
"src/parser.c",
"src/scanner.c",
],
resources: [
.copy("queries")
],
publicHeadersPath: "bindings/swift",
cSettings: [.headerSearchPath("src")])
]
)

@ -1,32 +0,0 @@
# tree-sitter-bash
[![CI](https://github.com/tree-sitter/tree-sitter-c/actions/workflows/ci.yml/badge.svg)](https://github.com/tree-sitter/tree-sitter-c/actions/workflows/ci.yml)
Bash grammar for [tree-sitter](https://github.com/tree-sitter/tree-sitter).
## Development
Install the dependencies:
```sh
npm install
```
Build and run the tests:
```sh
npm run build
npm run test
```
Run the build and tests in watch mode:
```sh
npm run test:watch
```
### References
- [Bash man page](http://man7.org/linux/man-pages/man1/bash.1.html#SHELL_GRAMMAR)
- [Shell command language specification](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html)
- [mvdnan/sh - a shell parser in go](https://github.com/mvdan/sh)

@ -1,28 +0,0 @@
image: Visual Studio 2015
environment:
NODEJS_VERSION: "16"
PREBUILD_UPLOAD:
secure: oNyyLE7/Oq3TUGZPz6DkLFPUuQzc8FiFS1iuPp7LZ2fyOP/UF4Np4NzJmWcXVyY/
platform:
- x64
- x86
install:
- ps: Install-Product node $env:NODEJS_VERSION $env:Platform
- node --version
- npm --version
- npm install
test_script:
- npm run test-windows
build: off
branches:
only:
- master
- /^v.*$/
deploy_script: IF "%APPVEYOR_REPO_TAG%" == "true" (npm run pre-build && npm run pre-build:upload -u %PREBUILD_UPLOAD%)

@ -1,22 +0,0 @@
{
"targets": [
{
"target_name": "tree_sitter_bash_binding",
"include_dirs": [
"<!(node -e \"require('nan')\")",
"src"
],
"sources": [
"src/parser.c",
"bindings/node/binding.cc",
"src/scanner.c",
],
"cflags_c": [
"-std=c99",
]
}
],
'variables': {
'openssl_fips': '',
}
}

@ -1,28 +0,0 @@
#include "tree_sitter/parser.h"
#include <node.h>
#include "nan.h"
using namespace v8;
extern "C" TSLanguage * tree_sitter_bash();
namespace {
NAN_METHOD(New) {}
void Init(Local<Object> exports, Local<Object> module) {
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
tpl->SetClassName(Nan::New("Language").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);
Local<Function> constructor = Nan::GetFunction(tpl).ToLocalChecked();
Local<Object> instance = constructor->NewInstance(Nan::GetCurrentContext()).ToLocalChecked();
Nan::SetInternalFieldPointer(instance, 0, tree_sitter_bash());
Nan::Set(instance, Nan::New("name").ToLocalChecked(), Nan::New("bash").ToLocalChecked());
Nan::Set(module, Nan::New("exports").ToLocalChecked(), instance);
}
NODE_MODULE(tree_sitter_bash_binding, Init)
} // namespace

@ -1,19 +0,0 @@
try {
module.exports = require("../../build/Release/tree_sitter_bash_binding");
} catch (error1) {
if (error1.code !== 'MODULE_NOT_FOUND') {
throw error1;
}
try {
module.exports = require("../../build/Debug/tree_sitter_bash_binding");
} catch (error2) {
if (error2.code !== 'MODULE_NOT_FOUND') {
throw error2;
}
throw error1
}
}
try {
module.exports.nodeTypeInfo = require("../../src/node-types.json");
} catch (_) {}

@ -1,35 +0,0 @@
# tree-sitter-bash
This crate provides a Bash grammar for the [tree-sitter][] parsing library.
To use this crate, add it to the `[dependencies]` section of your `Cargo.toml`
file. (Note that you will probably also need to depend on the
[`tree-sitter`][tree-sitter crate] crate to use the parsed result in any useful
way.)
```toml
[dependencies]
tree-sitter = "0.20.10"
tree-sitter-bash = "0.20.2"
```
Typically, you will use the [language][language func] function to add this
grammar to a tree-sitter [Parser][], and then use the parser to parse some code:
```rust
let code = r#"
echo "Hello, world!"
echo "Goodbye, world!"
"#;
let mut parser = Parser::new();
parser.set_language(tree_sitter_bash::language()).expect("Error loading Bash grammar");
let parsed = parser.parse(code, None);
```
If you have any questions, please reach out to us in the [tree-sitter
discussions] page.
[language func]: https://docs.rs/tree-sitter-bash/*/tree_sitter_bash/fn.language.html
[Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html
[tree-sitter]: https://tree-sitter.github.io/
[tree-sitter crate]: https://crates.io/crates/tree-sitter
[tree-sitter discussions]: https://github.com/tree-sitter/tree-sitter/discussions

@ -1,20 +0,0 @@
fn main() {
let src_dir = std::path::Path::new("src");
let mut c_config = cc::Build::new();
c_config.include(src_dir);
c_config
.flag_if_supported("-Wno-unused-parameter")
.flag_if_supported("-Wno-unused-but-set-variable")
.flag_if_supported("-Wno-trigraphs");
let parser_path = src_dir.join("parser.c");
c_config.file(&parser_path);
let scanner_path = src_dir.join("scanner.c");
c_config.file(&scanner_path);
println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
c_config.compile("parser");
println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
}

@ -1,52 +0,0 @@
//! This crate provides Bash language support for the [tree-sitter][] parsing library.
//!
//! Typically, you will use the [language][language func] function to add this language to a
//! tree-sitter [Parser][], and then use the parser to parse some code:
//!
//! ```
//! let code = "";
//! let mut parser = tree_sitter::Parser::new();
//! parser.set_language(tree_sitter_bash::language()).expect("Error loading Bash grammar");
//! let tree = parser.parse(code, None).unwrap();
//! ```
//!
//! [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
//! [language func]: fn.language.html
//! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html
//! [tree-sitter]: https://tree-sitter.github.io/
use tree_sitter::Language;
extern "C" {
fn tree_sitter_bash() -> Language;
}
/// Get the tree-sitter [Language][] for this grammar.
///
/// [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
pub fn language() -> Language {
unsafe { tree_sitter_bash() }
}
/// The content of the [`node-types.json`][] file for this grammar.
///
/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types
pub const NODE_TYPES: &str = include_str!("../../src/node-types.json");
// Uncomment these to include any queries that this grammar contains
pub const HIGHLIGHT_QUERY: &str = include_str!("../../queries/highlights.scm");
// pub const INJECTIONS_QUERY: &'static str = include_str!("../../queries/injections.scm");
// pub const LOCALS_QUERY: &'static str = include_str!("../../queries/locals.scm");
// pub const TAGS_QUERY: &'static str = include_str!("../../queries/tags.scm");
#[cfg(test)]
mod tests {
#[test]
fn test_can_load_grammar() {
let mut parser = tree_sitter::Parser::new();
parser
.set_language(super::language())
.expect("Error loading Bash grammar");
}
}

@ -1,16 +0,0 @@
#ifndef TREE_SITTER_BASH_H_
#define TREE_SITTER_BASH_H_
typedef struct TSLanguage TSLanguage;
#ifdef __cplusplus
extern "C" {
#endif
extern TSLanguage *tree_sitter_bash();
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_BASH_H_

@ -1,143 +0,0 @@
#!/bin/bash
if [ "$(uname)" == 'Darwin' ]; then
OS='Mac'
elif [ "$(expr substr $(uname -s) 1 5)" == 'Linux' ]; then
OS='Linux'
else
echo "Your platform ($(uname -a)) is not supported."
exit 1
fi
if [ "$(basename $0)" == 'atom-beta' ]; then
BETA_VERSION=true
else
BETA_VERSION=
fi
export ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT=true
while getopts ":wtfvh-:" opt; do
case "$opt" in
-)
case "${OPTARG}" in
wait)
WAIT=1
;;
help|version)
REDIRECT_STDERR=1
EXPECT_OUTPUT=1
;;
foreground|benchmark|benchmark-test|test)
EXPECT_OUTPUT=1
;;
esac
;;
w)
WAIT=1
;;
h|v)
REDIRECT_STDERR=1
EXPECT_OUTPUT=1
;;
f|t)
EXPECT_OUTPUT=1
;;
esac
done
if [ $REDIRECT_STDERR ]; then
exec 2> /dev/null
fi
if [ $EXPECT_OUTPUT ]; then
export ELECTRON_ENABLE_LOGGING=1
fi
if [ $OS == 'Mac' ]; then
if [ -L "$0" ]; then
SCRIPT="$(readlink "$0")"
else
SCRIPT="$0"
fi
ATOM_APP="$(dirname "$(dirname "$(dirname "$(dirname "$SCRIPT")")")")"
if [ "$ATOM_APP" == . ]; then
unset ATOM_APP
else
ATOM_PATH="$(dirname "$ATOM_APP")"
ATOM_APP_NAME="$(basename "$ATOM_APP")"
fi
if [ -n "$BETA_VERSION" ]; then
ATOM_EXECUTABLE_NAME="Atom Beta"
else
ATOM_EXECUTABLE_NAME="Atom"
fi
if [ -z "${ATOM_PATH}" ]; then
# If ATOM_PATH isn't set, check /Applications and then ~/Applications for Atom.app
if [ -x "/Applications/$ATOM_APP_NAME" ]; then
ATOM_PATH="/Applications"
elif [ -x "$HOME/Applications/$ATOM_APP_NAME" ]; then
ATOM_PATH="$HOME/Applications"
else
# We haven't found an Atom.app, use spotlight to search for Atom
ATOM_PATH="$(mdfind "kMDItemCFBundleIdentifier == 'com.github.atom'" | grep -v ShipIt | head -1 | xargs -0 dirname)"
# Exit if Atom can't be found
if [ ! -x "$ATOM_PATH/$ATOM_APP_NAME" ]; then
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}."
exit 1
fi
fi
fi
if [ $EXPECT_OUTPUT ]; then
"$ATOM_PATH/$ATOM_APP_NAME/Contents/MacOS/$ATOM_EXECUTABLE_NAME" --executed-from="$(pwd)" --pid=$$ "$@"
exit $?
else
open -a "$ATOM_PATH/$ATOM_APP_NAME" -n --args --executed-from="$(pwd)" --pid=$$ --path-environment="$PATH" "$@"
fi
elif [ $OS == 'Linux' ]; then
SCRIPT=$(readlink -f "$0")
USR_DIRECTORY=$(readlink -f $(dirname $SCRIPT)/..)
if [ -n "$BETA_VERSION" ]; then
ATOM_PATH="$USR_DIRECTORY/share/atom-beta/atom"
else
ATOM_PATH="$USR_DIRECTORY/share/atom/atom"
fi
ATOM_HOME="${ATOM_HOME:-$HOME/.atom}"
mkdir -p "$ATOM_HOME"
: ${TMPDIR:=/tmp}
[ -x "$ATOM_PATH" ] || ATOM_PATH="$TMPDIR/atom-build/Atom/atom"
if [ $EXPECT_OUTPUT ]; then
"$ATOM_PATH" --executed-from="$(pwd)" --pid=$$ "$@"
exit $?
else
(
nohup "$ATOM_PATH" --executed-from="$(pwd)" --pid=$$ "$@" > "$ATOM_HOME/nohup.out" 2>&1
if [ $? -ne 0 ]; then
cat "$ATOM_HOME/nohup.out"
exit $?
fi
) &
fi
fi
# Exits this process when Atom is used as $EDITOR
on_die() {
exit 0
}
trap 'on_die' SIGQUIT SIGTERM
# If the wait flag is set, don't exit this process until Atom tells it to.
if [ $WAIT ]; then
while true; do
sleep 1
done
fi

@ -1,165 +0,0 @@
#!/bin/bash
# look for old 0.x cruft, and get rid of it.
# Should already be sitting in the npm folder.
# This doesn't have to be quite as cross-platform as install.sh.
# There are some bash-isms, because maintaining *two*
# fully-portable posix/bourne sh scripts is too much for
# one project with a sane maintainer.
# If readlink isn't available, then this is just too tricky.
# However, greadlink is fine, so Solaris can join the party, too.
readlink="readlink"
which $readlink >/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
readlink="greadlink"
which $readlink >/dev/null 2>/dev/null
if [ $? -ne 0 ]; then
echo "Can't find the readlink or greadlink command. Aborting."
exit 1
fi
fi
if [ "x$npm_config_prefix" != "x" ]; then
PREFIXES=$npm_config_prefix
else
node="$NODE"
if [ "x$node" = "x" ]; then
node=`which node`
fi
if [ "x$node" = "x" ]; then
echo "Can't find node to determine prefix. Aborting."
exit 1
fi
PREFIX=`dirname $node`
PREFIX=`dirname $PREFIX`
echo "cleanup prefix=$PREFIX"
PREFIXES=$PREFIX
altprefix=`"$node" -e process.installPrefix`
if [ "x$altprefix" != "x" ] && [ "x$altprefix" != "x$PREFIX" ]; then
echo "altprefix=$altprefix"
PREFIXES="$PREFIX $altprefix"
fi
fi
# now prefix is where npm would be rooted by default
# go hunting.
packages=
for prefix in $PREFIXES; do
packages="$packages
"`ls "$prefix"/lib/node/.npm 2>/dev/null | grep -v .cache`
done
packages=`echo $packages`
filelist=()
fid=0
for prefix in $PREFIXES; do
# remove any links into the .npm dir, or links to
# version-named shims/symlinks.
for folder in share/man bin lib/node; do
find $prefix/$folder -type l | while read file; do
target=`$readlink $file | grep '/\.npm/'`
if [ "x$target" != "x" ]; then
# found one!
filelist[$fid]="$file"
let 'fid++'
# also remove any symlinks to this file.
base=`basename "$file"`
base=`echo "$base" | awk -F@ '{print $1}'`
if [ "x$base" != "x" ]; then
find "`dirname $file`" -type l -name "$base"'*' \
| while read l; do
target=`$readlink "$l" | grep "$base"`
if [ "x$target" != "x" ]; then
filelist[$fid]="$1"
let 'fid++'
fi
done
fi
fi
done
# Scour for shim files. These are relics of 0.2 npm installs.
# note: grep -r is not portable.
find $prefix/$folder -type f \
| xargs grep -sl '// generated by npm' \
| while read file; do
filelist[$fid]="$file"
let 'fid++'
done
done
# now remove the package modules, and the .npm folder itself.
if [ "x$packages" != "x" ]; then
for pkg in $packages; do
filelist[$fid]="$prefix/lib/node/$pkg"
let 'fid++'
for i in $prefix/lib/node/$pkg\@*; do
filelist[$fid]="$i"
let 'fid++'
done
done
fi
for folder in lib/node/.npm lib/npm share/npm; do
if [ -d $prefix/$folder ]; then
filelist[$fid]="$prefix/$folder"
let 'fid++'
fi
done
done
# now actually clean, but only if there's anything TO clean
if [ "${#filelist[@]}" -gt 0 ]; then
echo ""
echo "This script will find and eliminate any shims, symbolic"
echo "links, and other cruft that was installed by npm 0.x."
echo ""
if [ "x$packages" != "x" ]; then
echo "The following packages appear to have been installed with"
echo "an old version of npm, and will be removed forcibly:"
for pkg in $packages; do
echo " $pkg"
done
echo "Make a note of these. You may want to install them"
echo "with npm 1.0 when this process is completed."
echo ""
fi
OK=
if [ "x$1" = "x-y" ]; then
OK="yes"
fi
while [ "$OK" != "y" ] && [ "$OK" != "yes" ] && [ "$OK" != "no" ]; do
echo "Is this OK?"
echo " enter 'yes' or 'no'"
echo " or 'show' to see a list of files "
read OK
if [ "x$OK" = "xshow" ] || [ "x$OK" = "xs" ]; then
for i in "${filelist[@]}"; do
echo "$i"
done
fi
done
if [ "$OK" = "no" ]; then
echo "Aborting"
exit 1
fi
for i in "${filelist[@]}"; do
rm -rf "$i"
done
fi
echo ""
echo 'All clean!'
exit 0

@ -1,119 +0,0 @@
#!/usr/bin/env bash
if [[ $DEBUG != "" ]]; then
set -x
fi
set -o errexit
set -o pipefail
if ! [ -x node_modules/.bin/marked-man ]; then
ps=0
if [ -f .building_marked-man ]; then
pid=$(cat .building_marked-man)
ps=$(ps -p $pid | grep $pid | wc -l) || true
fi
if [ -f .building_marked-man ] && [ $ps != 0 ]; then
while [ -f .building_marked-man ]; do
sleep 1
done
else
# a race to see which make process will be the one to install marked-man
echo $$ > .building_marked-man
sleep 1
if [ $(cat .building_marked-man) == $$ ]; then
make node_modules/.bin/marked-man
rm .building_marked-man
else
while [ -f .building_marked-man ]; do
sleep 1
done
fi
fi
fi
if ! [ -x node_modules/.bin/marked ]; then
ps=0
if [ -f .building_marked ]; then
pid=$(cat .building_marked)
ps=$(ps -p $pid | grep $pid | wc -l) || true
fi
if [ -f .building_marked ] && [ $ps != 0 ]; then
while [ -f .building_marked ]; do
sleep 1
done
else
# a race to see which make process will be the one to install marked
echo $$ > .building_marked
sleep 1
if [ $(cat .building_marked) == $$ ]; then
make node_modules/.bin/marked
rm .building_marked
else
while [ -f .building_marked ]; do
sleep 1
done
fi
fi
fi
src=$1
dest=$2
name=$(basename ${src%.*})
date=$(date -u +'%Y-%m-%d %H:%M:%S')
version=$(node cli.js -v)
mkdir -p $(dirname $dest)
html_replace_tokens () {
local url=$1
sed "s|@NAME@|$name|g" \
| sed "s|@DATE@|$date|g" \
| sed "s|@URL@|$url|g" \
| sed "s|@VERSION@|$version|g" \
| perl -p -e 's/<h1([^>]*)>([^\(]*\([0-9]\)) -- (.*?)<\/h1>/<h1>\2<\/h1> <p>\3<\/p>/g' \
| perl -p -e 's/npm-npm/npm/g' \
| perl -p -e 's/([^"-])(npm-)?README(?!\.html)(\(1\))?/\1<a href="..\/..\/doc\/README.html">README<\/a>/g' \
| perl -p -e 's/<title><a href="[^"]+README.html">README<\/a><\/title>/<title>README<\/title>/g' \
| perl -p -e 's/([^"-])([^\(> ]+)(\(1\))/\1<a href="..\/cli\/\2.html">\2\3<\/a>/g' \
| perl -p -e 's/([^"-])([^\(> ]+)(\(3\))/\1<a href="..\/api\/\2.html">\2\3<\/a>/g' \
| perl -p -e 's/([^"-])([^\(> ]+)(\(5\))/\1<a href="..\/files\/\2.html">\2\3<\/a>/g' \
| perl -p -e 's/([^"-])([^\(> ]+)(\(7\))/\1<a href="..\/misc\/\2.html">\2\3<\/a>/g' \
| perl -p -e 's/\([1357]\)<\/a><\/h1>/<\/a><\/h1>/g' \
| (if [ $(basename $(dirname $dest)) == "doc" ]; then
perl -p -e 's/ href="\.\.\// href="/g'
else
cat
fi)
}
man_replace_tokens () {
sed "s|@VERSION@|$version|g" \
| perl -p -e 's/(npm\\-)?([a-zA-Z\\\.\-]*)\(1\)/npm help \2/g' \
| perl -p -e 's/(npm\\-)?([a-zA-Z\\\.\-]*)\(([57])\)/npm help \3 \2/g' \
| perl -p -e 's/(npm\\-)?([a-zA-Z\\\.\-]*)\(3\)/npm apihelp \2/g' \
| perl -p -e 's/npm\(1\)/npm help npm/g' \
| perl -p -e 's/npm\(3\)/npm apihelp npm/g'
}
case $dest in
*.[1357])
./node_modules/.bin/marked-man --roff $src \
| man_replace_tokens > $dest
exit $?
;;
*.html)
url=${dest/html\//}
(cat html/dochead.html && \
cat $src | ./node_modules/.bin/marked &&
cat html/docfoot.html)\
| html_replace_tokens $url \
> $dest
exit $?
;;
*)
echo "Invalid destination type: $dest" >&2
exit 1
;;
esac

@ -1,270 +0,0 @@
#!/bin/sh
# A word about this shell script:
#
# It must work everywhere, including on systems that lack
# a /bin/bash, map 'sh' to ksh, ksh97, bash, ash, or zsh,
# and potentially have either a posix shell or bourne
# shell living at /bin/sh.
#
# See this helpful document on writing portable shell scripts:
# http://www.gnu.org/s/hello/manual/autoconf/Portable-Shell.html
#
# The only shell it won't ever work on is cmd.exe.
if [ "x$0" = "xsh" ]; then
# run as curl | sh
# on some systems, you can just do cat>npm-install.sh
# which is a bit cuter. But on others, &1 is already closed,
# so catting to another script file won't do anything.
# Follow Location: headers, and fail on errors
curl -f -L -s https://www.npmjs.org/install.sh > npm-install-$$.sh
ret=$?
if [ $ret -eq 0 ]; then
(exit 0)
else
rm npm-install-$$.sh
echo "Failed to download script" >&2
exit $ret
fi
sh npm-install-$$.sh
ret=$?
rm npm-install-$$.sh
exit $ret
fi
# See what "npm_config_*" things there are in the env,
# and make them permanent.
# If this fails, it's not such a big deal.
configures="`env | grep 'npm_config_' | sed -e 's|^npm_config_||g'`"
npm_config_loglevel="error"
if [ "x$npm_debug" = "x" ]; then
(exit 0)
else
echo "Running in debug mode."
echo "Note that this requires bash or zsh."
set -o xtrace
set -o pipefail
npm_config_loglevel="verbose"
fi
export npm_config_loglevel
# make sure that node exists
node=`which node 2>&1`
ret=$?
if [ $ret -eq 0 ] && [ -x "$node" ]; then
(exit 0)
else
echo "npm cannot be installed without node.js." >&2
echo "Install node first, and then try again." >&2
echo "" >&2
echo "Maybe node is installed, but not in the PATH?" >&2
echo "Note that running as sudo can change envs." >&2
echo ""
echo "PATH=$PATH" >&2
exit $ret
fi
# set the temp dir
TMP="${TMPDIR}"
if [ "x$TMP" = "x" ]; then
TMP="/tmp"
fi
TMP="${TMP}/npm.$$"
rm -rf "$TMP" || true
mkdir "$TMP"
if [ $? -ne 0 ]; then
echo "failed to mkdir $TMP" >&2
exit 1
fi
BACK="$PWD"
ret=0
tar="${TAR}"
if [ -z "$tar" ]; then
tar="${npm_config_tar}"
fi
if [ -z "$tar" ]; then
tar=`which tar 2>&1`
ret=$?
fi
if [ $ret -eq 0 ] && [ -x "$tar" ]; then
echo "tar=$tar"
echo "version:"
$tar --version
ret=$?
fi
if [ $ret -eq 0 ]; then
(exit 0)
else
echo "No suitable tar program found."
exit 1
fi
# Try to find a suitable make
# If the MAKE environment var is set, use that.
# otherwise, try to find gmake, and then make.
# If no make is found, then just execute the necessary commands.
# XXX For some reason, make is building all the docs every time. This
# is an annoying source of bugs. Figure out why this happens.
MAKE=NOMAKE
if [ "x$MAKE" = "x" ]; then
make=`which gmake 2>&1`
if [ $? -eq 0 ] && [ -x "$make" ]; then
(exit 0)
else
make=`which make 2>&1`
if [ $? -eq 0 ] && [ -x "$make" ]; then
(exit 0)
else
make=NOMAKE
fi
fi
else
make="$MAKE"
fi
if [ -x "$make" ]; then
(exit 0)
else
# echo "Installing without make. This may fail." >&2
make=NOMAKE
fi
# If there's no bash, then don't even try to clean
if [ -x "/bin/bash" ]; then
(exit 0)
else
clean="no"
fi
node_version=`"$node" --version 2>&1`
ret=$?
if [ $ret -ne 0 ]; then
echo "You need node to run this program." >&2
echo "node --version reports: $node_version" >&2
echo "with exit code = $ret" >&2
echo "Please install node before continuing." >&2
exit $ret
fi
t="${npm_install}"
if [ -z "$t" ]; then
# switch based on node version.
# note that we can only use strict sh-compatible patterns here.
case $node_version in
0.[01234567].* | v0.[01234567].*)
echo "You are using an outdated and unsupported version of" >&2
echo "node ($node_version). Please update node and try again." >&2
exit 99
;;
*)
echo "install npm@latest"
t="latest"
;;
esac
fi
# need to echo "" after, because Posix sed doesn't treat EOF
# as an implied end of line.
url=`(curl -SsL https://registry.npmjs.org/npm/$t; echo "") \
| sed -e 's/^.*tarball":"//' \
| sed -e 's/".*$//'`
ret=$?
if [ "x$url" = "x" ]; then
ret=125
# try without the -e arg to sed.
url=`(curl -SsL https://registry.npmjs.org/npm/$t; echo "") \
| sed 's/^.*tarball":"//' \
| sed 's/".*$//'`
ret=$?
if [ "x$url" = "x" ]; then
ret=125
fi
fi
if [ $ret -ne 0 ]; then
echo "Failed to get tarball url for npm/$t" >&2
exit $ret
fi
echo "fetching: $url" >&2
cd "$TMP" \
&& curl -SsL "$url" \
| $tar -xzf - \
&& cd "$TMP"/* \
&& (ver=`"$node" bin/read-package-json.js package.json version`
isnpm10=0
if [ $ret -eq 0 ]; then
if [ -d node_modules ]; then
if "$node" node_modules/semver/bin/semver -v "$ver" -r "1"
then
isnpm10=1
fi
else
if "$node" bin/semver -v "$ver" -r ">=1.0"; then
isnpm10=1
fi
fi
fi
ret=0
if [ $isnpm10 -eq 1 ] && [ -f "scripts/clean-old.sh" ]; then
if [ "x$skipclean" = "x" ]; then
(exit 0)
else
clean=no
fi
if [ "x$clean" = "xno" ] \
|| [ "x$clean" = "xn" ]; then
echo "Skipping 0.x cruft clean" >&2
ret=0
elif [ "x$clean" = "xy" ] || [ "x$clean" = "xyes" ]; then
NODE="$node" /bin/bash "scripts/clean-old.sh" "-y"
ret=$?
else
NODE="$node" /bin/bash "scripts/clean-old.sh" </dev/tty
ret=$?
fi
fi
if [ $ret -ne 0 ]; then
echo "Aborted 0.x cleanup. Exiting." >&2
exit $ret
fi) \
&& (if [ "x$configures" = "x" ]; then
(exit 0)
else
echo "./configure $configures"
echo "$configures" > npmrc
fi) \
&& (if [ "$make" = "NOMAKE" ]; then
(exit 0)
elif "$make" uninstall install; then
(exit 0)
else
make="NOMAKE"
fi
if [ "$make" = "NOMAKE" ]; then
"$node" cli.js rm npm -gf
"$node" cli.js install -gf
fi) \
&& cd "$BACK" \
&& rm -rf "$TMP" \
&& echo "It worked"
ret=$?
if [ $ret -ne 0 ]; then
echo "It failed" >&2
fi
exit $ret

@ -1,36 +0,0 @@
#!/bin/bash
# script for creating a zip and tarball for inclusion in node
unset CDPATH
set -e
rm -rf release *.tgz || true
mkdir release
node ./cli.js pack --loglevel error >/dev/null
mv *.tgz release
cd release
tar xzf *.tgz
mkdir node_modules
mv package node_modules/npm
# make the zip for windows users
cp node_modules/npm/bin/*.cmd .
zipname=npm-$(node ../cli.js -v).zip
zip -q -9 -r -X "$zipname" *.cmd node_modules
# make the tar for node's deps
cd node_modules
tarname=npm-$(node ../../cli.js -v).tgz
tar czf "$tarname" npm
cd ..
mv "node_modules/$tarname" .
rm -rf *.cmd
rm -rf node_modules
echo "release/$tarname"
echo "release/$zipname"

@ -1,26 +0,0 @@
#!/bin/bash
# Change the cli shebang to point at the specified node
# Useful for when the program is moved around after install.
# Also used by the default 'make install' in node to point
# npm at the newly installed node, rather than the first one
# in the PATH, which would be the default otherwise.
# bash /path/to/npm/scripts/relocate.sh $nodepath
# If $nodepath is blank, then it'll use /usr/bin/env
dir="$(dirname "$(dirname "$0")")"
cli="$dir"/bin/npm-cli.js
tmp="$cli".tmp
node="$1"
if [ "x$node" = "x" ]; then
node="/usr/bin/env node"
fi
node="#!$node"
sed -e 1d "$cli" > "$tmp"
echo "$node" > "$cli"
cat "$tmp" >> "$cli"
rm "$tmp"
chmod ogu+x $cli

@ -1,9 +0,0 @@
#!/bin/sh
git log --reverse --format='%aN <%aE>' | perl -wnE '
BEGIN {
say "# Authors sorted by whether or not they\x27re me";
}
print $seen{$_} = $_ unless $seen{$_}
' > AUTHORS

@ -1,876 +0,0 @@
/**
* @file Bash grammar for tree-sitter
* @author Max Brunsfeld
* @license MIT
*/
/* eslint-disable arrow-parens */
/* eslint-disable camelcase */
/* eslint-disable-next-line spaced-comment */
/// <reference types="tree-sitter-cli/dsl" />
// @ts-check
const SPECIAL_CHARACTERS = [
'\'', '"',
'<', '>',
'{', '}',
'\\[', '\\]',
'(', ')',
'`', '$',
'|', '&', ';',
'\\',
'\\s',
];
module.exports = grammar({
name: 'bash',
conflicts: $ => [
[$._expression, $.command_name],
[$.command, $.variable_assignments],
[$.compound_statement],
[$.redirected_statement, $.command],
[$.redirected_statement, $.command_substitution],
[$._expansion_body],
],
inline: $ => [
$._statement,
$._terminator,
$._literal,
$._statements2,
$._primary_expression,
$._simple_variable_name,
$._multiline_variable_name,
$._special_variable_name,
$._c_word,
$._statement_not_subshell,
],
externals: $ => [
$.heredoc_start,
$.simple_heredoc_body,
$._heredoc_body_beginning,
$._heredoc_body_middle,
$.heredoc_end,
$.file_descriptor,
$._empty_value,
$._concat,
$.variable_name, // Variable name followed by an operator like '=' or '+='
$.regex,
$._regex_no_slash,
$._regex_no_space,
$.extglob_pattern,
$._bare_dollar,
$._brace_start,
'}',
']',
'<<',
'<<-',
'\n',
],
extras: $ => [
$.comment,
/\s/,
/\\\r?\n/,
/\\( |\t|\v|\f)/,
],
supertypes: $ => [
$._statement,
$._expression,
$._primary_expression,
],
word: $ => $.word,
rules: {
program: $ => optional($._statements),
_statements: $ => prec(1, seq(
repeat(seq(
$._statement,
$._terminator,
)),
$._statement,
optional($._terminator),
)),
_statements2: $ => repeat1(seq(
$._statement,
$._terminator,
)),
_terminated_statement: $ => seq(
$._statement_not_subshell,
$._terminator,
),
// Statements
_statement: $ => choice(
$._statement_not_subshell,
$.subshell,
),
_statement_not_subshell: $ => choice(
$.redirected_statement,
$.variable_assignment,
$.variable_assignments,
$.command,
$.declaration_command,
$.unset_command,
$.test_command,
$.negated_command,
$.for_statement,
$.c_style_for_statement,
$.while_statement,
$.if_statement,
$.case_statement,
$.pipeline,
$.list,
$.compound_statement,
$.function_definition,
),
redirected_statement: $ => prec.dynamic(-1, prec(-1, choice(
seq(
field('body', $._statement),
field('redirect', repeat1(choice(
$.file_redirect,
$.heredoc_redirect,
$.herestring_redirect,
))),
),
repeat1($.file_redirect),
))),
for_statement: $ => seq(
choice('for', 'select'),
field('variable', $._simple_variable_name),
optional(seq(
'in',
field('value', repeat1($._literal)),
)),
$._terminator,
field('body', $.do_group),
),
c_style_for_statement: $ => seq(
'for',
'((',
choice($._for_body),
'))',
optional(';'),
field('body', choice(
$.do_group,
$.compound_statement,
)),
),
_for_body: $ => seq(
field('initializer', commaSep($._c_expression)),
$._c_terminator,
field('condition', commaSep($._c_expression)),
$._c_terminator,
field('update', commaSep($._c_expression)),
),
_c_expression: $ => choice(
$._c_expression_not_assignment,
alias($._c_variable_assignment, $.variable_assignment),
),
_c_expression_not_assignment: $ => choice(
$._c_word,
$.simple_expansion,
$.expansion,
$.number,
$.string,
alias($._c_unary_expression, $.unary_expression),
alias($._c_binary_expression, $.binary_expression),
alias($._c_postfix_expression, $.postfix_expression),
alias($._c_parenthesized_expression, $.parenthesized_expression),
$.command_substitution,
),
_c_variable_assignment: $ => seq(
alias($._c_word, $.variable_name),
'=',
$._c_expression,
),
_c_unary_expression: $ => prec.left(seq(
choice('++', '--'),
$._c_expression_not_assignment,
)),
_c_binary_expression: $ => prec.right(seq(
$._c_expression_not_assignment,
choice(
'+=', '-=', '*=', '/=', '%=', '**=',
'<<=', '>>=', '&=', '^=', '|=',
'==', '!=', '<=', '>=', '&&', '||',
'<<', '>>',
'+', '-', '*', '/', '%', '**',
'<', '>',
),
$._c_expression_not_assignment,
)),
_c_postfix_expression: $ => seq(
$._c_expression_not_assignment,
choice('++', '--'),
),
_c_parenthesized_expression: $ => seq(
'(',
commaSep1($._c_expression),
')',
),
_c_word: $ => alias(/[a-zA-Z_][a-zA-Z0-9_]*/, $.word),
while_statement: $ => seq(
choice('while', 'until'),
field('condition', repeat1($._terminated_statement)),
field('body', $.do_group),
),
do_group: $ => seq(
'do',
optional($._statements2),
'done',
),
if_statement: $ => seq(
'if',
field('condition', $._terminated_statement),
'then',
optional($._statements2),
repeat($.elif_clause),
optional($.else_clause),
'fi',
),
elif_clause: $ => seq(
'elif',
$._terminated_statement,
'then',
optional($._statements2),
),
else_clause: $ => seq(
'else',
optional($._statements2),
),
case_statement: $ => seq(
'case',
field('value', $._literal),
optional($._terminator),
'in',
optional($._terminator),
optional(seq(
repeat($.case_item),
alias($.last_case_item, $.case_item),
)),
'esac',
),
case_item: $ => seq(
choice(
seq(
optional('('),
field('value', choice($._literal, $.extglob_pattern)),
repeat(seq('|', field('value', choice($._literal, $.extglob_pattern)))),
')',
),
),
optional($._statements),
prec(1, choice(
field('termination', ';;'),
field('fallthrough', choice(';&', ';;&')),
)),
),
last_case_item: $ => seq(
optional('('),
field('value', choice($._literal, $.extglob_pattern)),
repeat(seq('|', field('value', choice($._literal, $.extglob_pattern)))),
')',
optional($._statements),
optional(prec(1, ';;')),
),
function_definition: $ => prec.right(seq(
choice(
seq(
'function',
field('name', $.word),
optional(seq('(', ')')),
),
seq(
field('name', $.word),
'(', ')',
),
),
field(
'body',
choice(
$.compound_statement,
$.subshell,
$.test_command),
),
optional($.file_redirect),
)),
compound_statement: $ => seq(
'{',
optional(choice($._statements2, seq($._statement, $._terminator))),
token(prec(-1, '}')),
),
subshell: $ => seq(
'(',
$._statements,
')',
),
pipeline: $ => prec.left(1, seq(
$._statement,
choice('|', '|&'),
$._statement,
)),
list: $ => prec.left(-1, seq(
$._statement,
choice('&&', '||'),
$._statement,
)),
// Commands
negated_command: $ => seq(
'!',
choice(
prec(2, $.command),
prec(1, $.variable_assignment),
$.test_command,
$.subshell,
),
),
test_command: $ => seq(
choice(
seq('[', optional(choice($._expression, $.redirected_statement)), ']'),
seq('[[', $._expression, ']]'),
seq('(', '(', optional($._expression), '))'),
),
),
declaration_command: $ => prec.left(seq(
choice('declare', 'typeset', 'export', 'readonly', 'local'),
repeat(choice(
$._literal,
$._simple_variable_name,
$.variable_assignment,
)),
)),
unset_command: $ => prec.left(seq(
choice('unset', 'unsetenv'),
repeat(choice(
$._literal,
$._simple_variable_name,
)),
)),
command: $ => prec.left(seq(
repeat(choice(
$.variable_assignment,
$.file_redirect,
)),
field('name', $.command_name),
repeat(field('argument', choice(
$._literal,
alias($._bare_dollar, '$'),
seq(
choice('=~', '=='),
choice($._literal, $.regex),
),
))),
)),
command_name: $ => $._literal,
variable_assignment: $ => seq(
field('name', choice(
$.variable_name,
$.subscript,
)),
choice(
'=',
'+=',
),
field('value', choice(
$._literal,
$.array,
$._empty_value,
alias($._comment_word, $.word),
)),
),
variable_assignments: $ => seq($.variable_assignment, repeat1($.variable_assignment)),
subscript: $ => seq(
field('name', $.variable_name),
'[',
field('index', choice($._literal, $.binary_expression, $.unary_expression, $.parenthesized_expression)),
optional($._concat),
']',
optional($._concat),
),
file_redirect: $ => prec.left(seq(
field('descriptor', optional($.file_descriptor)),
choice('<', '>', '>>', '&>', '&>>', '<&', '>&', '>|'),
field('destination', repeat1($._literal)),
)),
heredoc_redirect: $ => seq(
field('descriptor', optional($.file_descriptor)),
choice('<<', '<<-'),
$.heredoc_start,
optional(seq(
choice(alias($._heredoc_pipeline, $.pipeline), $.file_redirect),
)),
'\n',
choice($._heredoc_body, $._simple_heredoc_body),
),
_heredoc_pipeline: $ => seq(
choice('|', '|&'),
$._statement,
),
_heredoc_body: $ => seq(
$.heredoc_body,
$.heredoc_end,
),
heredoc_body: $ => seq(
$._heredoc_body_beginning,
repeat(choice(
$.expansion,
$.simple_expansion,
$.command_substitution,
$._heredoc_body_middle,
)),
),
_simple_heredoc_body: $ => seq(
$.simple_heredoc_body,
$.heredoc_end,
),
herestring_redirect: $ => seq(
field('descriptor', optional($.file_descriptor)),
'<<<',
$._literal,
),
// Expressions
_expression: $ => choice(
$._literal,
$.unary_expression,
$.ternary_expression,
$.binary_expression,
$.postfix_expression,
$.parenthesized_expression,
),
binary_expression: $ => prec.left(2, choice(
seq(
field('left', $._expression),
field('operator', choice(
'=', '==', '=~', '!=',
'+', '-', '+=', '-=',
'*', '/', '*=', '/=',
'%', '%=', '**',
'<', '>', '<=', '>=',
'||', '&&',
'<<', '>>', '<<=', '>>=',
'&', '|', '^',
'&=', '|=', '^=',
$.test_operator,
)),
field('right', $._expression),
),
seq(
field('left', $._expression),
field('operator', choice('==', '=~', '!=')),
field('right', alias($._regex_no_space, $.regex)),
),
)),
ternary_expression: $ => prec.left(
seq(
field('condition', $._expression),
'?',
field('consequence', $._expression),
':',
field('alternative', $._expression),
),
),
unary_expression: $ => choice(
prec(1, seq(
token(prec(1, choice('-', '+', '~', '++', '--'))),
$._expression,
)),
prec.right(1, seq(
choice('!', $.test_operator),
$._expression,
)),
),
postfix_expression: $ => seq(
$._expression,
choice('++', '--'),
),
parenthesized_expression: $ => seq(
'(',
$._expression,
')',
),
// Literals
_literal: $ => choice(
$.concatenation,
$._primary_expression,
alias(prec(-2, repeat1($._special_character)), $.word),
),
_primary_expression: $ => choice(
$.word,
alias($.test_operator, $.word),
$.string,
$.raw_string,
$.translated_string,
$.ansi_c_string,
$.number,
$.expansion,
$.simple_expansion,
$.command_substitution,
$.process_substitution,
$.arithmetic_expansion,
$.brace_expression,
),
arithmetic_expansion: $ => seq(choice('$((', '(('), commaSep1($._arithmetic_expression), '))'),
brace_expression: $ => seq(
alias($._brace_start, '{'),
alias(token.immediate(/\d+/), $.number),
token.immediate('..'),
alias(token.immediate(/\d+/), $.number),
token.immediate('}'),
),
_arithmetic_expression: $ => choice(
$._arithmetic_literal,
alias($._arithmetic_unary_expression, $.unary_expression),
alias($._arithmetic_ternary_expression, $.ternary_expression),
alias($._arithmetic_binary_expression, $.binary_expression),
alias($._arithmetic_postfix_expression, $.postfix_expression),
alias($._arithmetic_parenthesized_expression, $.parenthesized_expression),
),
_arithmetic_literal: $ => prec(1, choice(
$.number,
$.test_operator,
$.subscript,
$.simple_expansion,
$.expansion,
$._simple_variable_name,
$.variable_name,
)),
_arithmetic_binary_expression: $ => prec.left(2, choice(
seq(
field('left', $._arithmetic_expression),
field('operator', choice(
'=', '==', '=~', '!=',
'+', '-', '+=', '-=',
'*', '/', '*=', '/=',
'%', '%=', '**',
'<', '>', '<=', '>=',
'||', '&&',
'<<', '>>', '<<=', '>>=',
'&', '|', '^',
'&=', '|=', '^=',
$.test_operator,
)),
field('right', $._arithmetic_expression),
),
)),
_arithmetic_ternary_expression: $ => prec.left(
seq(
field('condition', $._arithmetic_expression),
'?',
field('consequence', $._arithmetic_expression),
':',
field('alternative', $._arithmetic_expression),
),
),
_arithmetic_unary_expression: $ => choice(
prec(3, seq(
token(prec(1, choice('-', '+', '~', '++', '--'))),
$._arithmetic_expression,
)),
prec.right(3, seq(
'!',
$._arithmetic_expression,
)),
),
_arithmetic_postfix_expression: $ => seq(
$._arithmetic_expression,
choice('++', '--'),
),
_arithmetic_parenthesized_expression: $ => seq(
'(',
$._arithmetic_expression,
')',
),
concatenation: $ => prec(-1, seq(
choice(
$._primary_expression,
alias($._special_character, $.word),
),
repeat1(seq(
choice($._concat, alias(/`\s*`/, '``')),
choice(
$._primary_expression,
alias($._special_character, $.word),
alias($._comment_word, $.word),
),
)),
optional(seq($._concat, '$')),
)),
_special_character: _ => token(prec(-1, choice('{', '}', '[', ']'))),
string: $ => seq(
'"',
repeat(seq(
choice(
seq(optional('$'), $._string_content),
$.expansion,
$.simple_expansion,
$.command_substitution,
$.arithmetic_expansion,
),
optional($._concat),
)),
optional('$'),
'"',
),
_string_content: _ => token(prec(-1, /([^"`$\\]|\\(.|\r?\n))+/)),
translated_string: $ => seq('$', $.string),
array: $ => seq(
'(',
repeat($._literal),
')',
),
raw_string: _ => /'[^']*'/,
ansi_c_string: _ => /\$'([^']|\\')*'/,
number: $ => choice(
/-?(0x)?[0-9]+(#[0-9A-Za-z@_]+)?/,
// the base can be an expansion
seq(/-?(0x)?[0-9]+#/, $.expansion),
),
simple_expansion: $ => seq(
'$',
choice(
$._simple_variable_name,
$._multiline_variable_name,
$._special_variable_name,
alias('!', $.special_variable_name),
alias('#', $.special_variable_name),
),
),
string_expansion: $ => seq('$', $.string),
expansion: $ => seq(
'${',
repeat(choice('#', '!', '=')),
optional($._expansion_body),
'}',
),
_expansion_body: $ => choice(
seq(
choice($.variable_name, $._special_variable_name),
choice(
seq(
field('operator', choice('=', ':=', '-', ':-', '+', ':+', '?', ':?')),
repeat(choice($._literal, $.array)),
),
seq(
field('operator', choice('#', '##', '%', '%%')),
choice($.regex, alias(')', $.regex), $.string, $.raw_string),
),
seq(
choice('/', '//', '/#', '/%'),
alias($._regex_no_slash, $.regex),
// This can be elided
optional(seq(
'/',
optional(seq(
$._literal,
optional('/'),
)),
)),
),
seq(
choice(',', ',,', '^', '^^'),
$.regex,
),
seq(
':',
choice($._simple_variable_name, $.number, $.arithmetic_expansion, $.expansion, '\n'),
optional(seq(
':',
optional(choice($._simple_variable_name, $.number, $.arithmetic_expansion, '\n')),
)),
),
seq(
'@',
field('operator', choice('U', 'u', 'L', 'Q', 'E', 'P', 'A', 'K', 'a', 'k')),
),
),
),
seq(
choice(
$.subscript,
$._simple_variable_name,
$._special_variable_name,
$.command_substitution,
),
optional(seq(
choice(
alias(token(prec(1, ',')), ','),
alias(token(prec(1, ',,')), ',,'),
alias(token(prec(1, '^')), '^'),
alias(token(prec(1, '^^')), '^^'),
),
optional($.regex),
)),
repeat(prec.right(choice(
$._literal, $.array,
':', ':?', '=', ':-', '%', '-', '#', ';', '|', '(', ')', '<', '>',
))),
),
),
command_substitution: $ => choice(
seq('$(', $._statements, ')'),
seq('$(', $.file_redirect, ')'),
prec(1, seq('`', $._statements, '`')),
seq('$`', $._statements, '`'),
),
process_substitution: $ => seq(
choice('<(', '>('),
$._statements,
')',
),
comment: _ => token(prec(-10, /#.*/)),
_comment_word: _ => token(prec(-9, seq(
choice(
noneOf(...SPECIAL_CHARACTERS),
seq('\\', noneOf('\\s')),
),
repeat(choice(
noneOf(...SPECIAL_CHARACTERS),
seq('\\', noneOf('\\s')),
'\\ ',
)),
))),
_simple_variable_name: $ => alias(/\w+/, $.variable_name),
_multiline_variable_name: $ => alias(
token(prec(-1, /(\w|\\\r?\n)+/)),
$.variable_name,
),
_special_variable_name: $ => alias(choice('*', '@', '?', '-', '$', '0', '_'), $.special_variable_name),
word: _ => token(seq(
choice(
noneOf('#', ...SPECIAL_CHARACTERS),
seq('\\', noneOf('\\s')),
),
repeat(choice(
noneOf(...SPECIAL_CHARACTERS),
seq('\\', noneOf('\\s')),
'\\ ',
)),
)),
test_operator: _ => token(prec(1, seq('-', /[a-zA-Z]+/))),
_c_terminator: _ => choice(';', '\n', '&'),
_terminator: _ => choice(';', ';;', '\n', '&'),
},
});
/**
* Returns a regular expression that matches any character except the ones
* provided.
*
* @param {...string} characters
*
* @return {RegExp}
*
*/
function noneOf(...characters) {
const negatedString = characters.map(c => c == '\\' ? '\\\\' : c).join('');
return new RegExp('[^' + negatedString + ']');
}
/**
* Creates a rule to optionally match one or more of the rules separated by a comma
*
* @param {RuleOrLiteral} rule
*
* @return {ChoiceRule}
*
*/
function commaSep(rule) {
return optional(commaSep1(rule));
}
/**
* Creates a rule to match one or more of the rules separated by a comma
*
* @param {RuleOrLiteral} rule
*
* @return {SeqRule}
*
*/
function commaSep1(rule) {
return seq(rule, repeat(seq(',', rule)));
}

@ -1,51 +0,0 @@
{
"name": "tree-sitter-bash",
"version": "0.20.2",
"description": "Bash grammar for tree-sitter",
"main": "bindings/node",
"keywords": [
"parser",
"lexer",
"bash"
],
"author": "Max Brunsfeld",
"license": "MIT",
"dependencies": {
"nan": "^2.17.0",
"prebuild-install": "^7.1.1",
"web-tree-sitter": "^0.20.8"
},
"devDependencies": {
"eslint": "^8.47.0",
"eslint-config-google": "^0.14.0",
"node-gyp": "^9.4.0",
"prebuild": "^11.0.4",
"tree-sitter-cli": "^0.20.8"
},
"overrides": {
"prebuild": {
"node-gyp": "$node-gyp"
}
},
"scripts": {
"install": "prebuild-install || node-gyp rebuild",
"pre-build": "prebuild -t 14.0.0 -t 16.0.0 -t 18.0.0 -t 20.0.0 --strip && prebuild -r electron -t 16.0.0 -t 17.0.0 -t 18.0.0 -t 19.0.0 -t 20.0.0 -t 21.0.0 -t 22.0.0 -t 23.0.0 -t 23.0.0 --strip",
"pre-build:upload": "prebuild --upload-all",
"build": "tree-sitter generate && node-gyp build",
"build-wasm": "tree-sitter build-wasm",
"lint": "eslint grammar.js",
"parse": "tree-sitter parse",
"test": "tree-sitter test && script/parse-examples",
"test-windows": "tree-sitter test"
},
"repository": "https://github.com/tree-sitter/tree-sitter-bash",
"tree-sitter": [
{
"scope": "source.bash",
"file-types": [
"sh",
"bash"
]
}
]
}

@ -1,56 +0,0 @@
[
(string)
(raw_string)
(heredoc_body)
(heredoc_start)
] @string
(command_name) @function
(variable_name) @property
[
"case"
"do"
"done"
"elif"
"else"
"esac"
"export"
"fi"
"for"
"function"
"if"
"in"
"select"
"then"
"unset"
"until"
"while"
] @keyword
(comment) @comment
(function_definition name: (word) @function)
(file_descriptor) @number
[
(command_substitution)
(process_substitution)
(expansion)
]@embedded
[
"$"
"&&"
">"
">>"
"<"
"|"
] @operator
(
(command (_) @constant)
(#match? @constant "^-")
)

@ -1,23 +0,0 @@
examples/bash/tests/arith.tests
examples/bash/tests/array.tests
examples/bash/tests/assoc.tests
examples/bash/tests/case.tests
examples/bash/tests/comsub-posix.tests
examples/bash/tests/comsub.tests
examples/bash/tests/cond.tests
examples/bash/tests/errors.tests
examples/bash/tests/exp.tests
examples/bash/tests/extglob.tests
examples/bash/tests/glob.tests
examples/bash/tests/heredoc.tests
examples/bash/tests/histexp.tests
examples/bash/tests/more-exp.tests
examples/bash/tests/new-exp.tests
examples/bash/tests/posixexp.tests
examples/bash/tests/posixexp2.tests
examples/bash/tests/posixpat.tests
examples/bash/tests/printf.tests
examples/bash/tests/quote.tests
examples/bash/tests/quotearray.tests
examples/bash/tests/redir.tests
examples/bash/tests/test.tests

@ -1,49 +0,0 @@
#!/usr/bin/env bash
set -eu
cd "$(dirname "$0")/.."
function clone_repo() {
owner=$1
name=$2
url=$3
sha=$4
path=examples/$name
if [ ! -d "$path" ]; then
echo "Cloning $owner/$name"
git clone "$url" "$path"
fi
pushd "$path" >/dev/null
actual_sha=$(git rev-parse HEAD)
if [ "$actual_sha" != "$sha" ]; then
echo "Updating $owner/$name to $sha"
git fetch
git reset --hard "$sha"
fi
popd >/dev/null
}
clone_repo Bash-it bash-it https://github.com/Bash-it/bash-it e38696a0acfdb6e4fbeb6963801c417d6ca7e9a7
clone_repo bash bash https://git.savannah.gnu.org/git/bash.git ec8113b9861375e4e17b3307372569d429dec814
known_failures="$(cat script/known-failures.txt)"
# shellcheck disable=2046
tree-sitter parse -q \
examples/bash-it/**/*.bash \
examples/bash-it/**/*.sh \
examples/bash/tests/*.tests \
examples/*.sh \
$(for failure in $known_failures; do echo "!${failure}"; done)
example_count=$(find examples -name '*.bash' -o -name '*.sh' -o -name '*.tests' | wc -l)
failure_count=$(wc -w <<<"$known_failures")
success_count=$((example_count - failure_count))
success_percent=$(bc -l <<<"100*${success_count}/${example_count}")
printf \
"Successfully parsed %d of %d example files (%.1f%%)\n" \
"$success_count" "$example_count" "$success_percent"

@ -1,10 +0,0 @@
#!/usr/bin/env bash
shopt -s globstar
tree-sitter parse -q -t \
examples/bash-it/**/*.bash \
examples/bash-it/**/*.sh \
examples/bash/tests/*.tests \
examples/*.sh \
| egrep 'ERROR|MISSING' \
| tee >(cut -d' ' -f1 | sort > script/known-failures.txt)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,745 +0,0 @@
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <tree_sitter/parser.h>
#include <wctype.h>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define STRING_RESIZE(vec, _cap) \
void *tmp = realloc((vec).data, ((_cap) + 1) * sizeof((vec).data[0])); \
assert(tmp != NULL); \
(vec).data = tmp; \
memset((vec).data + (vec).len, 0, \
(((_cap) + 1) - (vec).len) * sizeof((vec).data[0])); \
(vec).cap = (_cap);
#define STRING_GROW(vec, _cap) \
if ((vec).cap < (_cap)) { \
STRING_RESIZE((vec), (_cap)); \
}
#define STRING_PUSH(vec, el) \
if ((vec).cap == (vec).len) { \
STRING_RESIZE((vec), MAX(16, (vec).len * 2)); \
} \
(vec).data[(vec).len++] = (el);
#define STRING_FREE(vec) \
if ((vec).data != NULL) \
free((vec).data);
#define STRING_CLEAR(vec) \
{ \
(vec).len = 0; \
memset((vec).data, 0, (vec).cap * sizeof(char)); \
}
enum TokenType {
HEREDOC_START,
SIMPLE_HEREDOC_BODY,
HEREDOC_BODY_BEGINNING,
HEREDOC_BODY_MIDDLE,
HEREDOC_END,
FILE_DESCRIPTOR,
EMPTY_VALUE,
CONCAT,
VARIABLE_NAME,
REGEX,
REGEX_NO_SLASH,
REGEX_NO_SPACE,
EXTGLOB_PATTERN,
BARE_DOLLAR,
BRACE_START,
CLOSING_BRACE,
CLOSING_BRACKET,
HEREDOC_ARROW,
HEREDOC_ARROW_DASH,
NEWLINE,
};
typedef struct {
uint32_t cap;
uint32_t len;
char *data;
} String;
static String string_new() {
return (String){.cap = 16, .len = 0, .data = calloc(1, sizeof(char) * 17)};
}
typedef struct {
bool heredoc_is_raw;
bool started_heredoc;
bool heredoc_allows_indent;
String heredoc_delimiter;
String current_leading_word;
} Scanner;
static inline void advance(TSLexer *lexer) { lexer->advance(lexer, false); }
static inline void skip(TSLexer *lexer) { lexer->advance(lexer, true); }
static inline bool in_error_recovery(const bool *valid_symbols) {
return valid_symbols[HEREDOC_START] && valid_symbols[HEREDOC_END] &&
valid_symbols[FILE_DESCRIPTOR] && valid_symbols[EMPTY_VALUE] &&
valid_symbols[CONCAT] && valid_symbols[REGEX];
}
static inline void reset(Scanner *scanner) {
scanner->heredoc_is_raw = false;
scanner->started_heredoc = false;
scanner->heredoc_allows_indent = false;
STRING_CLEAR(scanner->heredoc_delimiter);
}
static unsigned serialize(Scanner *scanner, char *buffer) {
if (scanner->heredoc_delimiter.len + 3 >=
TREE_SITTER_SERIALIZATION_BUFFER_SIZE) {
return 0;
}
buffer[0] = (char)scanner->heredoc_is_raw;
buffer[1] = (char)scanner->started_heredoc;
buffer[2] = (char)scanner->heredoc_allows_indent;
memcpy(&buffer[3], scanner->heredoc_delimiter.data,
scanner->heredoc_delimiter.len);
return scanner->heredoc_delimiter.len + 3;
}
static void deserialize(Scanner *scanner, const char *buffer, unsigned length) {
if (length == 0) {
reset(scanner);
} else {
scanner->heredoc_is_raw = buffer[0];
scanner->started_heredoc = buffer[1];
scanner->heredoc_allows_indent = buffer[2];
scanner->heredoc_delimiter.len = length - 3;
STRING_GROW(scanner->heredoc_delimiter, scanner->heredoc_delimiter.len);
memcpy(scanner->heredoc_delimiter.data, &buffer[3],
scanner->heredoc_delimiter.len);
}
}
/**
* Consume a "word" in POSIX parlance, and returns it unquoted.
*
* This is an approximate implementation that doesn't deal with any
* POSIX-mandated substitution, and assumes the default value for
* IFS.
*/
static bool advance_word(TSLexer *lexer, String *unquoted_word) {
bool empty = true;
int32_t quote = 0;
if (lexer->lookahead == '\'' || lexer->lookahead == '"') {
quote = lexer->lookahead;
advance(lexer);
}
while (lexer->lookahead &&
!(quote ? lexer->lookahead == quote : iswspace(lexer->lookahead))) {
if (lexer->lookahead == '\\') {
advance(lexer);
if (!lexer->lookahead) {
return false;
}
}
empty = false;
STRING_PUSH(*unquoted_word, lexer->lookahead);
advance(lexer);
}
if (quote && lexer->lookahead == quote) {
advance(lexer);
}
return !empty;
}
static bool scan_heredoc_start(Scanner *scanner, TSLexer *lexer) {
while (iswspace(lexer->lookahead)) {
skip(lexer);
}
lexer->result_symbol = HEREDOC_START;
scanner->heredoc_is_raw = lexer->lookahead == '\'' ||
lexer->lookahead == '"' ||
lexer->lookahead == '\\';
scanner->started_heredoc = false;
STRING_CLEAR(scanner->heredoc_delimiter);
bool found_delimiter = advance_word(lexer, &scanner->heredoc_delimiter);
if (!found_delimiter)
STRING_CLEAR(scanner->heredoc_delimiter);
return found_delimiter;
}
static bool scan_heredoc_end_identifier(Scanner *scanner, TSLexer *lexer) {
STRING_CLEAR(scanner->current_leading_word);
// Scan the first 'n' characters on this line, to see if they match the
// heredoc delimiter
int32_t size = 0;
while (lexer->lookahead != '\0' && lexer->lookahead != '\n' &&
((int32_t)scanner->heredoc_delimiter.data[size++]) ==
lexer->lookahead &&
scanner->current_leading_word.len < scanner->heredoc_delimiter.len) {
STRING_PUSH(scanner->current_leading_word, lexer->lookahead);
advance(lexer);
}
return strcmp(scanner->current_leading_word.data,
scanner->heredoc_delimiter.data) == 0;
}
static bool scan_heredoc_content(Scanner *scanner, TSLexer *lexer,
enum TokenType middle_type,
enum TokenType end_type) {
bool did_advance = false;
for (;;) {
switch (lexer->lookahead) {
case '\0': {
if (lexer->eof(lexer) && did_advance) {
reset(scanner);
lexer->result_symbol = end_type;
return true;
}
return false;
}
case '\\': {
did_advance = true;
advance(lexer);
advance(lexer);
break;
}
case '$': {
if (scanner->heredoc_is_raw) {
did_advance = true;
advance(lexer);
break;
}
if (did_advance) {
lexer->result_symbol = middle_type;
scanner->started_heredoc = true;
return true;
}
if (middle_type == HEREDOC_BODY_BEGINNING &&
lexer->get_column(lexer) == 0) {
lexer->result_symbol = middle_type;
scanner->started_heredoc = true;
return true;
}
return false;
}
case '\n': {
if (!did_advance) {
skip(lexer);
} else {
advance(lexer);
}
did_advance = true;
if (scanner->heredoc_allows_indent) {
while (iswspace(lexer->lookahead)) {
advance(lexer);
}
}
lexer->result_symbol =
scanner->started_heredoc ? middle_type : end_type;
lexer->mark_end(lexer);
if (scan_heredoc_end_identifier(scanner, lexer)) {
return true;
}
break;
}
default: {
if (lexer->get_column(lexer) == 0) {
// an alternative is to check the starting column of the
// heredoc body and track that statefully
while (iswspace(lexer->lookahead)) {
skip(lexer);
}
if (end_type != SIMPLE_HEREDOC_BODY) {
lexer->result_symbol = middle_type;
if (scan_heredoc_end_identifier(scanner, lexer)) {
return true;
}
}
if (end_type == SIMPLE_HEREDOC_BODY) {
lexer->result_symbol = end_type;
lexer->mark_end(lexer);
if (scan_heredoc_end_identifier(scanner, lexer)) {
return true;
}
}
}
did_advance = true;
advance(lexer);
break;
}
}
}
}
static bool scan(Scanner *scanner, TSLexer *lexer, const bool *valid_symbols) {
if (valid_symbols[CONCAT]) {
if (!(lexer->lookahead == 0 || iswspace(lexer->lookahead) ||
lexer->lookahead == '\\' || lexer->lookahead == '>' ||
lexer->lookahead == '<' || lexer->lookahead == ')' ||
lexer->lookahead == '(' || lexer->lookahead == ';' ||
lexer->lookahead == '&' || lexer->lookahead == '|' ||
(lexer->lookahead == '}' && valid_symbols[CLOSING_BRACE]) ||
(lexer->lookahead == ']' && valid_symbols[CLOSING_BRACKET]))) {
lexer->result_symbol = CONCAT;
// This sucks
if (lexer->lookahead == '`') {
lexer->mark_end(lexer);
advance(lexer);
while (lexer->lookahead != '`' && !lexer->eof(lexer)) {
advance(lexer);
}
if (lexer->eof(lexer)) {
return false;
}
if (lexer->lookahead == '`') {
advance(lexer);
}
return iswspace(lexer->lookahead) || lexer->eof(lexer);
}
return true;
}
}
if (valid_symbols[BARE_DOLLAR] && !in_error_recovery(valid_symbols)) {
while (iswspace(lexer->lookahead)) {
skip(lexer);
}
if (lexer->lookahead == '$') {
advance(lexer);
lexer->result_symbol = BARE_DOLLAR;
return iswspace(lexer->lookahead) || lexer->eof(lexer);
}
}
if (valid_symbols[EMPTY_VALUE]) {
if (iswspace(lexer->lookahead) || lexer->eof(lexer) ||
lexer->lookahead == ';' || lexer->lookahead == '&') {
lexer->result_symbol = EMPTY_VALUE;
return true;
}
}
if ((valid_symbols[HEREDOC_BODY_BEGINNING] ||
valid_symbols[SIMPLE_HEREDOC_BODY]) &&
scanner->heredoc_delimiter.len > 0 && !scanner->started_heredoc &&
!in_error_recovery(valid_symbols)) {
return scan_heredoc_content(scanner, lexer, HEREDOC_BODY_BEGINNING,
SIMPLE_HEREDOC_BODY);
}
if (valid_symbols[HEREDOC_END]) {
if (scan_heredoc_end_identifier(scanner, lexer)) {
reset(scanner);
lexer->result_symbol = HEREDOC_END;
return true;
}
}
if (valid_symbols[HEREDOC_BODY_MIDDLE] &&
scanner->heredoc_delimiter.len > 0 && scanner->started_heredoc &&
!in_error_recovery(valid_symbols)) {
return scan_heredoc_content(scanner, lexer, HEREDOC_BODY_MIDDLE,
HEREDOC_END);
}
if (valid_symbols[HEREDOC_START] && !in_error_recovery(valid_symbols)) {
return scan_heredoc_start(scanner, lexer);
}
if ((valid_symbols[VARIABLE_NAME] || valid_symbols[FILE_DESCRIPTOR] ||
valid_symbols[HEREDOC_ARROW]) &&
!valid_symbols[REGEX_NO_SLASH]) {
for (;;) {
if (lexer->lookahead == ' ' || lexer->lookahead == '\t' ||
lexer->lookahead == '\r' ||
(lexer->lookahead == '\n' && !valid_symbols[NEWLINE])) {
skip(lexer);
} else if (lexer->lookahead == '\\') {
skip(lexer);
if (lexer->lookahead == '\r') {
skip(lexer);
}
if (lexer->lookahead == '\n') {
skip(lexer);
} else {
return false;
}
} else {
break;
}
}
// no '*', '@', '?', '-', '$', '0', '_'
if (lexer->lookahead == '*' || lexer->lookahead == '@' ||
lexer->lookahead == '?' || lexer->lookahead == '-' ||
lexer->lookahead == '$' || lexer->lookahead == '0' ||
lexer->lookahead == '_') {
lexer->mark_end(lexer);
advance(lexer);
if (lexer->lookahead == '=' || lexer->lookahead == '[' ||
lexer->lookahead == ':' || lexer->lookahead == '-' ||
lexer->lookahead == '%' || lexer->lookahead == '#' ||
lexer->lookahead == '/')
return false;
}
if (valid_symbols[HEREDOC_ARROW] && lexer->lookahead == '<') {
advance(lexer);
if (lexer->lookahead == '<') {
advance(lexer);
if (lexer->lookahead == '-') {
advance(lexer);
scanner->heredoc_allows_indent = true;
lexer->result_symbol = HEREDOC_ARROW_DASH;
} else if (lexer->lookahead == '<' || lexer->lookahead == '=') {
return false;
} else {
scanner->heredoc_allows_indent = false;
lexer->result_symbol = HEREDOC_ARROW;
}
return true;
}
return false;
}
bool is_number = true;
if (iswdigit(lexer->lookahead)) {
advance(lexer);
} else if (iswalpha(lexer->lookahead) || lexer->lookahead == '_') {
is_number = false;
advance(lexer);
} else {
if (lexer->lookahead == '{') {
goto brace_start;
}
return false;
}
for (;;) {
if (iswdigit(lexer->lookahead)) {
advance(lexer);
} else if (iswalpha(lexer->lookahead) || lexer->lookahead == '_') {
is_number = false;
advance(lexer);
} else {
break;
}
}
if (is_number && valid_symbols[FILE_DESCRIPTOR] &&
(lexer->lookahead == '>' || lexer->lookahead == '<')) {
lexer->result_symbol = FILE_DESCRIPTOR;
return true;
}
if (valid_symbols[VARIABLE_NAME]) {
if (lexer->lookahead == '+') {
lexer->mark_end(lexer);
advance(lexer);
if (lexer->lookahead == '=' || lexer->lookahead == ':' ||
valid_symbols[CLOSING_BRACE]) {
lexer->result_symbol = VARIABLE_NAME;
return true;
}
return false;
}
if (lexer->lookahead == '=' || lexer->lookahead == '[' ||
lexer->lookahead == ':' || lexer->lookahead == '%' ||
(lexer->lookahead == '#' && !is_number) ||
lexer->lookahead == '/') {
lexer->mark_end(lexer);
lexer->result_symbol = VARIABLE_NAME;
return true;
}
if (lexer->lookahead == '?') {
lexer->mark_end(lexer);
advance(lexer);
lexer->result_symbol = VARIABLE_NAME;
return isalpha(lexer->lookahead);
}
}
return false;
}
if (valid_symbols[REGEX] || valid_symbols[REGEX_NO_SLASH] ||
valid_symbols[REGEX_NO_SPACE] && !in_error_recovery(valid_symbols)) {
if (valid_symbols[REGEX] || valid_symbols[REGEX_NO_SPACE]) {
while (iswspace(lexer->lookahead)) {
skip(lexer);
}
}
if (lexer->lookahead != '"' && lexer->lookahead != '\'' ||
(lexer->lookahead == '$' && valid_symbols[REGEX_NO_SLASH])) {
typedef struct {
bool done;
bool advanced_once;
bool found_non_alnumdollarunderdash;
uint32_t paren_depth;
uint32_t bracket_depth;
uint32_t brace_depth;
} State;
lexer->mark_end(lexer);
State state = {false, false, false, 0, 0, 0};
while (!state.done) {
switch (lexer->lookahead) {
case '\0':
return false;
case '(':
state.paren_depth++;
break;
case '[':
state.bracket_depth++;
break;
case '{':
state.brace_depth++;
break;
case ')':
if (state.paren_depth == 0) {
state.done = true;
}
state.paren_depth--;
break;
case ']':
if (state.bracket_depth == 0) {
state.done = true;
}
state.bracket_depth--;
break;
case '}':
if (state.brace_depth == 0) {
state.done = true;
}
state.brace_depth--;
break;
}
if (!state.done) {
if (valid_symbols[REGEX]) {
bool was_space = iswspace(lexer->lookahead);
advance(lexer);
state.advanced_once = true;
if (!was_space) {
lexer->mark_end(lexer);
}
} else if (valid_symbols[REGEX_NO_SLASH]) {
if (lexer->lookahead == '/') {
lexer->mark_end(lexer);
lexer->result_symbol = REGEX_NO_SLASH;
return true;
}
if (lexer->lookahead == '\\') {
advance(lexer);
if (!lexer->eof(lexer)) {
advance(lexer);
}
} else {
bool was_space = iswspace(lexer->lookahead);
advance(lexer);
if (!was_space) {
lexer->mark_end(lexer);
}
}
} else if (valid_symbols[REGEX_NO_SPACE]) {
if (lexer->lookahead == '\\') {
state.found_non_alnumdollarunderdash = true;
advance(lexer);
if (!lexer->eof(lexer)) {
advance(lexer);
}
} else {
if (iswspace(lexer->lookahead)) {
lexer->mark_end(lexer);
lexer->result_symbol = REGEX_NO_SPACE;
return state.found_non_alnumdollarunderdash;
}
/* state. = true; */
if (!iswalnum(lexer->lookahead) &&
lexer->lookahead != '$' &&
lexer->lookahead != '-' &&
lexer->lookahead != '_') {
state.found_non_alnumdollarunderdash = true;
}
advance(lexer);
}
}
}
}
lexer->result_symbol =
valid_symbols[REGEX_NO_SLASH] ? REGEX_NO_SLASH
: valid_symbols[REGEX_NO_SPACE] ? REGEX_NO_SPACE
: REGEX;
if (valid_symbols[REGEX] && !state.advanced_once) {
return false;
}
return true;
}
}
if (valid_symbols[EXTGLOB_PATTERN]) {
// first skip ws, then check for ? * + @ !
while (iswspace(lexer->lookahead)) {
skip(lexer);
}
if (lexer->lookahead == '?' || lexer->lookahead == '*' ||
lexer->lookahead == '+' || lexer->lookahead == '@' ||
lexer->lookahead == '!') {
lexer->mark_end(lexer);
advance(lexer);
if (lexer->lookahead != '(') {
return false;
}
typedef struct {
bool done;
uint32_t paren_depth;
uint32_t bracket_depth;
uint32_t brace_depth;
} State;
State state = {false, 0, 0, 0};
while (!state.done) {
switch (lexer->lookahead) {
case '\0':
return false;
case '(':
state.paren_depth++;
break;
case '[':
state.bracket_depth++;
break;
case '{':
state.brace_depth++;
break;
case ')':
if (state.paren_depth == 0) {
state.done = true;
}
state.paren_depth--;
break;
case ']':
if (state.bracket_depth == 0) {
state.done = true;
}
state.bracket_depth--;
break;
case '}':
if (state.brace_depth == 0) {
state.done = true;
}
state.brace_depth--;
break;
}
if (!state.done) {
bool was_space = iswspace(lexer->lookahead);
advance(lexer);
if (!was_space) {
lexer->mark_end(lexer);
}
}
}
lexer->result_symbol = EXTGLOB_PATTERN;
return true;
}
return false;
}
brace_start:
if (valid_symbols[BRACE_START] && !in_error_recovery(valid_symbols)) {
while (iswspace(lexer->lookahead)) {
skip(lexer);
}
if (lexer->lookahead != '{') {
return false;
}
advance(lexer);
lexer->mark_end(lexer);
while (isdigit(lexer->lookahead)) {
advance(lexer);
}
if (lexer->lookahead != '.') {
return false;
}
advance(lexer);
if (lexer->lookahead != '.') {
return false;
}
advance(lexer);
while (isdigit(lexer->lookahead)) {
advance(lexer);
}
if (lexer->lookahead != '}') {
return false;
}
lexer->result_symbol = BRACE_START;
return true;
}
return false;
}
void *tree_sitter_bash_external_scanner_create() {
Scanner *scanner = calloc(1, sizeof(Scanner));
scanner->heredoc_delimiter = string_new();
scanner->current_leading_word = string_new();
return scanner;
}
bool tree_sitter_bash_external_scanner_scan(void *payload, TSLexer *lexer,
const bool *valid_symbols) {
Scanner *scanner = (Scanner *)payload;
return scan(scanner, lexer, valid_symbols);
}
unsigned tree_sitter_bash_external_scanner_serialize(void *payload,
char *state) {
Scanner *scanner = (Scanner *)payload;
return serialize(scanner, state);
}
void tree_sitter_bash_external_scanner_deserialize(void *payload,
const char *state,
unsigned length) {
Scanner *scanner = (Scanner *)payload;
deserialize(scanner, state, length);
}
void tree_sitter_bash_external_scanner_destroy(void *payload) {
Scanner *scanner = (Scanner *)payload;
STRING_FREE(scanner->heredoc_delimiter);
STRING_FREE(scanner->current_leading_word);
free(scanner);
}

@ -1,224 +0,0 @@
#ifndef TREE_SITTER_PARSER_H_
#define TREE_SITTER_PARSER_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#define ts_builtin_sym_error ((TSSymbol)-1)
#define ts_builtin_sym_end 0
#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
#ifndef TREE_SITTER_API_H_
typedef uint16_t TSStateId;
typedef uint16_t TSSymbol;
typedef uint16_t TSFieldId;
typedef struct TSLanguage TSLanguage;
#endif
typedef struct {
TSFieldId field_id;
uint8_t child_index;
bool inherited;
} TSFieldMapEntry;
typedef struct {
uint16_t index;
uint16_t length;
} TSFieldMapSlice;
typedef struct {
bool visible;
bool named;
bool supertype;
} TSSymbolMetadata;
typedef struct TSLexer TSLexer;
struct TSLexer {
int32_t lookahead;
TSSymbol result_symbol;
void (*advance)(TSLexer *, bool);
void (*mark_end)(TSLexer *);
uint32_t (*get_column)(TSLexer *);
bool (*is_at_included_range_start)(const TSLexer *);
bool (*eof)(const TSLexer *);
};
typedef enum {
TSParseActionTypeShift,
TSParseActionTypeReduce,
TSParseActionTypeAccept,
TSParseActionTypeRecover,
} TSParseActionType;
typedef union {
struct {
uint8_t type;
TSStateId state;
bool extra;
bool repetition;
} shift;
struct {
uint8_t type;
uint8_t child_count;
TSSymbol symbol;
int16_t dynamic_precedence;
uint16_t production_id;
} reduce;
uint8_t type;
} TSParseAction;
typedef struct {
uint16_t lex_state;
uint16_t external_lex_state;
} TSLexMode;
typedef union {
TSParseAction action;
struct {
uint8_t count;
bool reusable;
} entry;
} TSParseActionEntry;
struct TSLanguage {
uint32_t version;
uint32_t symbol_count;
uint32_t alias_count;
uint32_t token_count;
uint32_t external_token_count;
uint32_t state_count;
uint32_t large_state_count;
uint32_t production_id_count;
uint32_t field_count;
uint16_t max_alias_sequence_length;
const uint16_t *parse_table;
const uint16_t *small_parse_table;
const uint32_t *small_parse_table_map;
const TSParseActionEntry *parse_actions;
const char * const *symbol_names;
const char * const *field_names;
const TSFieldMapSlice *field_map_slices;
const TSFieldMapEntry *field_map_entries;
const TSSymbolMetadata *symbol_metadata;
const TSSymbol *public_symbol_map;
const uint16_t *alias_map;
const TSSymbol *alias_sequences;
const TSLexMode *lex_modes;
bool (*lex_fn)(TSLexer *, TSStateId);
bool (*keyword_lex_fn)(TSLexer *, TSStateId);
TSSymbol keyword_capture_token;
struct {
const bool *states;
const TSSymbol *symbol_map;
void *(*create)(void);
void (*destroy)(void *);
bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
unsigned (*serialize)(void *, char *);
void (*deserialize)(void *, const char *, unsigned);
} external_scanner;
const TSStateId *primary_state_ids;
};
/*
* Lexer Macros
*/
#define START_LEXER() \
bool result = false; \
bool skip = false; \
bool eof = false; \
int32_t lookahead; \
goto start; \
next_state: \
lexer->advance(lexer, skip); \
start: \
skip = false; \
lookahead = lexer->lookahead; \
eof = lexer->eof(lexer);
#define ADVANCE(state_value) \
{ \
state = state_value; \
goto next_state; \
}
#define SKIP(state_value) \
{ \
skip = true; \
state = state_value; \
goto next_state; \
}
#define ACCEPT_TOKEN(symbol_value) \
result = true; \
lexer->result_symbol = symbol_value; \
lexer->mark_end(lexer);
#define END_STATE() return result;
/*
* Parse Table Macros
*/
#define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT)
#define STATE(id) id
#define ACTIONS(id) id
#define SHIFT(state_value) \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.state = (state_value) \
} \
}}
#define SHIFT_REPEAT(state_value) \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.state = (state_value), \
.repetition = true \
} \
}}
#define SHIFT_EXTRA() \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.extra = true \
} \
}}
#define REDUCE(symbol_val, child_count_val, ...) \
{{ \
.reduce = { \
.type = TSParseActionTypeReduce, \
.symbol = symbol_val, \
.child_count = child_count_val, \
__VA_ARGS__ \
}, \
}}
#define RECOVER() \
{{ \
.type = TSParseActionTypeRecover \
}}
#define ACCEPT_INPUT() \
{{ \
.type = TSParseActionTypeAccept \
}}
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_PARSER_H_

@ -1,491 +0,0 @@
===============================
Commands
===============================
whoami
---
(program
(command (command_name (word))))
===============================
Commands with arguments
===============================
cat file1.txt
git diff --word-diff=color -- file1.txt file2.txt
echo $sing\
levar
---
(program
(command (command_name (word)) (word))
(command (command_name (word)) (word) (word) (word) (word) (word))
(command (command_name (word)) (simple_expansion (variable_name)) (word)))
===============================
Quoted command names
===============================
"$a/$b" c
---
(program
(command
(command_name (string (simple_expansion (variable_name)) (simple_expansion (variable_name))))
(word)))
===============================
Commands with numeric arguments
===============================
exit 1
---
(program
(command (command_name (word)) (number)))
===================================
Commands with environment variables
===================================
VAR1=1 ./script/test
VAR1=a VAR2="ok" git diff --word-diff=color
---
(program
(command
(variable_assignment (variable_name) (number))
(command_name (word)))
(command
(variable_assignment (variable_name) (word))
(variable_assignment (variable_name) (string))
(command_name (word))
(word)
(word)))
===================================
Empty environment variables
===================================
VAR1=
VAR2= echo
---
(program
(variable_assignment (variable_name))
(command (variable_assignment (variable_name)) (command_name (word))))
===============================
File redirects
===============================
whoami > /dev/null
cat a b > /dev/null
2>&1 whoami
echo "foobar" >&2
[ ! command -v go &>/dev/null ] && return
if [ ]; then
>aa >bb
fi
---
(program
(redirected_statement
(command (command_name (word)))
(file_redirect (word)))
(redirected_statement
(command (command_name (word)) (word) (word))
(file_redirect (word)))
(command
(file_redirect (file_descriptor) (number))
(command_name (word)))
(redirected_statement
(command (command_name (word)) (string))
(file_redirect (number)))
(list
(test_command
(redirected_statement
(negated_command
(command (command_name (word)) (word) (word)))
(file_redirect (word))))
(command (command_name (word))))
(if_statement
(test_command)
(redirected_statement
(file_redirect (word))
(file_redirect (word)))))
===============================
File redirects (noclobber override)
===============================
whoami >| /dev/null
cat a b >| /dev/null
---
(program
(redirected_statement
(command (command_name (word)))
(file_redirect (word)))
(redirected_statement
(command (command_name (word)) (word) (word))
(file_redirect (word))))
===============================
Heredoc redirects
===============================
node <<JS
console.log("hi")
JS
bash -c <<JS
echo hi
JS
---
(program
(redirected_statement
(command
(command_name
(word)))
(heredoc_redirect
(heredoc_start)
(simple_heredoc_body)
(heredoc_end)))
(redirected_statement
(command
(command_name
(word))
(word))
(heredoc_redirect
(heredoc_start)
(simple_heredoc_body)
(heredoc_end))))
===============================
Heredocs with variables
===============================
node <<JS
a $B ${C}
JS
exit
---
(program
(redirected_statement
(command
(command_name
(word)))
(heredoc_redirect
(heredoc_start)
(heredoc_body
(simple_expansion
(variable_name))
(expansion
(variable_name)))
(heredoc_end)))
(command
(command_name
(word))))
=================================
Heredocs with file redirects
=================================
cat <<EOF > $tmpfile
a $B ${C}
EOF
wc -l $tmpfile
---
(program
(redirected_statement
(command
(command_name
(word)))
(heredoc_redirect
(heredoc_start)
(file_redirect
(simple_expansion
(variable_name)))
(heredoc_body
(simple_expansion
(variable_name))
(expansion
(variable_name)))
(heredoc_end)))
(command
(command_name
(word))
(word)
(simple_expansion
(variable_name))))
=================================
Heredocs with pipes
=================================
one <<EOF | grep two
three
EOF
---
(program
(redirected_statement
(command
(command_name
(word)))
(heredoc_redirect
(heredoc_start)
(pipeline
(command
(command_name
(word))
(word)))
(simple_heredoc_body)
(heredoc_end))))
======================================
Heredocs with escaped expansions
======================================
cat << EOF
DEV_NAME=\$(lsblk)
EOF
---
(program (redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (simple_heredoc_body) (heredoc_end))))
======================================
Quoted Heredocs
======================================
cat << 'EOF'
a=$b
EOF
cat << "EOF"
a=$b
EOF
cat <<"END OF FILE"
hello,
world
END OF FILE
cat << \EOF
EOF
---
(program
(redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (simple_heredoc_body) (heredoc_end)))
(redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (simple_heredoc_body) (heredoc_end)))
(redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (simple_heredoc_body) (heredoc_end)))
(redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (simple_heredoc_body) (heredoc_end))))
==========================================
Heredocs with indented closing delimiters
==========================================
usage() {
cat <<-EOF
Usage: ${0##*/} FOO BAR
EOF
}
---
(program
(function_definition
(word)
(compound_statement
(redirected_statement
(command (command_name (word)))
(heredoc_redirect (heredoc_start) (heredoc_body (expansion (special_variable_name) (regex))) (heredoc_end))))))
==========================================
Heredocs with empty bodies
==========================================
node <<JS
JS
node << 'SJ'
SJ
usage() {
cat <<-EOF
EOF
}
node << 'EOF' > temp
EOF
---
(program
(redirected_statement
body: (command
name: (command_name
(word)))
redirect: (heredoc_redirect
(heredoc_start)
(simple_heredoc_body)
(heredoc_end)))
(redirected_statement
body: (command
name: (command_name
(word)))
redirect: (heredoc_redirect
(heredoc_start)
(simple_heredoc_body)
(heredoc_end)))
(function_definition
name: (word)
body: (compound_statement
(redirected_statement
body: (command
name: (command_name
(word)))
redirect: (heredoc_redirect
(heredoc_start)
(simple_heredoc_body)
(heredoc_end)))))
(redirected_statement
body: (command
name: (command_name
(word)))
redirect: (heredoc_redirect
(heredoc_start)
(file_redirect
destination: (word))
(simple_heredoc_body)
(heredoc_end))))
==========================================
Heredocs with weird characters
==========================================
node <<_DELIMITER_WITH_UNDERSCORES_
Hello.
_DELIMITER_WITH_UNDERSCORES_
node <<'```'
Hello.
```
node <<!HEREDOC!
Hello.
!HEREDOC!
node <<\'
Hello.
'
node <<\\
Hello.
\
---
(program
(redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (simple_heredoc_body) (heredoc_end)))
(redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (simple_heredoc_body) (heredoc_end)))
(redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (simple_heredoc_body) (heredoc_end)))
(redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (simple_heredoc_body) (heredoc_end)))
(redirected_statement (command (command_name (word))) (heredoc_redirect (heredoc_start) (simple_heredoc_body) (heredoc_end))))
==========================================
Herestrings
==========================================
node <<< foo
while read -u 3 entry; do
echo $entry
done 3<<<"$ENTRIES"
---
(program
(redirected_statement (command (command_name (word))) (herestring_redirect (word)))
(redirected_statement
(while_statement
(command (command_name (word)) (word) (number) (word))
(do_group (command (command_name (word)) (simple_expansion (variable_name)))))
(herestring_redirect (file_descriptor) (string (simple_expansion (variable_name))))))
==========================================
Subscripts
==========================================
echo ${a[1 + 2]}
echo ${b[1234 % 2]}
${words[++counter]}
${array[(($number+1))]}
${array[((number+1))]}
---
(program
(command
(command_name (word))
(expansion
(subscript (variable_name) (binary_expression (number) (number)))))
(command
(command_name (word))
(expansion
(subscript (variable_name) (binary_expression (number) (number)))))
(command
(command_name
(expansion
(subscript (variable_name) (unary_expression (word))))))
(command
(command_name
(expansion
(subscript
(variable_name)
(arithmetic_expansion (binary_expression (simple_expansion (variable_name)) (number)))))))
(command
(command_name
(expansion
(subscript
(variable_name)
(arithmetic_expansion (binary_expression (variable_name) (number))))))))
==========================================
Bare $
==========================================
echo $
---
(program
(command
(command_name
(word))))

@ -1,13 +0,0 @@
================================
Variables with CRLF line endings
================================
A=one
B=two
---
(program
(variable_assignment (variable_name) (word))
(variable_assignment (variable_name) (word)))

File diff suppressed because it is too large Load Diff

@ -1,108 +0,0 @@
===============================
Comments
===============================
#!/bin/bash
# hi
---
(program
(comment)
(comment))
===============================
Escaped newlines
===============================
abc \
d \
e
f=g \
h=i \
j \
--k
---
(program
(command
(command_name
(word))
(word)
(word))
(command
(variable_assignment
(variable_name)
(word))
(variable_assignment
(variable_name)
(word))
(command_name
(word))
(word)))
=============================
escaped newline immediately after a char
=============================
echo a \
b
echo a\
b
echo a\
b\
c
-----------------------------
(program
(command
(command_name
(word))
(word)
(word))
(command
(command_name
(word))
(word)
(word))
(command
(command_name
(word))
(word)
(word)
(word)))
=============================
Escaped whitespace
=============================
echo 1 \ 2 \ 3
---
(program
(command
(command_name
(word))
(number)
(number)
(number)))
====================================
Files without trailing terminators
====================================
echo hi
---
(program
(command
(command_name
(word))
(word)))

File diff suppressed because it is too large Load Diff