tree-sitter-bash/grammar.js

441 lines
8.5 KiB
JavaScript
Raw Normal View History

const SPECIAL_CHARACTERS = [
"'", '"',
'<', '>',
'{', '}',
'\\[', '\\]',
'(', ')',
'`', '$',
'|', '&', ';',
'\\',
'\\s',
'#',
];
2017-07-14 19:28:54 +00:00
module.exports = grammar({
name: 'bash',
2017-07-16 05:13:55 +00:00
inline: $ => [
$._statement,
$._terminator,
$._expression,
$._primary_expression,
$._simple_variable_name,
$._special_variable_name,
2017-07-16 05:13:55 +00:00
],
2017-07-14 19:28:54 +00:00
2017-07-14 20:54:05 +00:00
externals: $ => [
$._simple_heredoc,
$._heredoc_beginning,
$._heredoc_middle,
$._heredoc_end,
2017-07-15 00:41:14 +00:00
$.file_descriptor,
$._empty_value,
$._concat,
$.variable_name, // Variable name followed by an operator like '=' or '+='
'}',
']',
'\n',
2017-07-14 20:54:05 +00:00
],
2017-07-14 21:34:49 +00:00
extras: $ => [
$.comment,
2017-07-14 21:39:28 +00:00
token(choice(/\s/, '\\\n')),
2017-07-14 21:34:49 +00:00
],
2017-07-14 19:28:54 +00:00
rules: {
2017-07-14 23:11:35 +00:00
program: $ => repeat($._terminated_statement),
2017-07-14 19:28:54 +00:00
2017-07-14 23:11:35 +00:00
_terminated_statement: $ => seq(
$._statement,
$._terminator
2017-07-14 23:11:35 +00:00
),
2017-07-16 05:13:55 +00:00
// Statements
_statement: $ => choice(
$.variable_assignment,
2017-07-14 23:11:35 +00:00
$.command,
$.declaration_command,
$.unset_command,
$.bracket_command,
2017-07-15 00:51:06 +00:00
$.for_statement,
2017-07-14 23:11:35 +00:00
$.while_statement,
2017-07-14 23:18:46 +00:00
$.if_statement,
2017-07-14 23:29:28 +00:00
$.case_statement,
2017-07-14 23:11:35 +00:00
$.pipeline,
2017-07-15 00:32:55 +00:00
$.list,
2017-07-15 00:35:51 +00:00
$.subshell,
$.function_definition
2017-07-14 23:11:35 +00:00
),
2017-07-15 00:51:06 +00:00
for_statement: $ => seq(
'for',
$._simple_variable_name,
2017-07-15 00:51:06 +00:00
'in',
repeat1($._expression),
$._terminator,
2017-07-15 00:51:06 +00:00
$.do_group
),
2017-07-14 23:11:35 +00:00
while_statement: $ => seq(
'while',
$._terminated_statement,
$.do_group,
repeat(choice(
$.file_redirect,
$.heredoc_redirect,
$.herestring_redirect
))
2017-07-14 23:11:35 +00:00
),
2017-07-14 23:29:28 +00:00
do_group: $ => seq(
'do',
repeat($._terminated_statement),
'done'
),
2017-07-14 23:18:46 +00:00
if_statement: $ => seq(
'if',
$._terminated_statement,
'then',
repeat($._terminated_statement),
repeat($.elif_clause),
optional($.else_clause),
'fi'
),
elif_clause: $ => seq(
'elif',
$._terminated_statement,
'then',
repeat($._terminated_statement)
),
else_clause: $ => seq(
'else',
repeat($._terminated_statement)
),
2017-07-14 23:29:28 +00:00
case_statement: $ => seq(
'case',
$._expression,
optional($._terminator),
2017-07-14 23:29:28 +00:00
'in',
$._terminator,
optional(seq(
repeat($.case_item),
alias($.last_case_item, $.case_item),
)),
2017-07-14 23:29:28 +00:00
'esac'
),
case_item: $ => seq(
$._expression,
repeat(seq('|', $._expression)),
2017-07-14 23:29:28 +00:00
')',
2018-03-01 22:04:29 +00:00
optional(seq(
repeat($._terminated_statement),
optional($._statement)
)),
prec(1, ';;')
2017-07-14 19:28:54 +00:00
),
last_case_item: $ => seq(
$._expression,
repeat(seq('|', $._expression)),
')',
2018-03-01 22:04:29 +00:00
optional(seq(
repeat($._terminated_statement),
optional($._statement)
)),
optional(prec(1, ';;'))
),
2017-07-15 00:35:51 +00:00
function_definition: $ => seq(
choice(
seq('function', $.word, optional(seq('(', ')'))),
seq($.word, '(', ')')
),
2017-12-26 22:27:05 +00:00
$.compound_statement,
optional($.file_redirect)
2017-07-15 00:35:51 +00:00
),
2017-07-16 05:13:55 +00:00
compound_statement: $ => seq(
2017-07-15 00:35:51 +00:00
'{',
repeat($._terminated_statement),
'}'
),
2017-07-16 05:13:55 +00:00
subshell: $ => seq(
'(',
repeat($._terminated_statement),
$._statement,
optional($._terminator),
2017-07-16 05:13:55 +00:00
')'
),
pipeline: $ => prec.left(1, seq(
$._statement,
2017-07-16 05:13:55 +00:00
choice('|', '|&'),
$._statement
2017-07-16 05:13:55 +00:00
)),
list: $ => prec.left(-1, seq(
$._statement,
2017-07-16 05:13:55 +00:00
choice('&&', '||'),
$._statement
2017-07-16 05:13:55 +00:00
)),
// Commands
command: $ => prec.left(seq(
2017-07-14 19:43:42 +00:00
repeat(choice(
$.variable_assignment,
2017-07-14 19:43:42 +00:00
$.file_redirect
)),
$.command_name,
2018-03-01 18:42:20 +00:00
repeat(choice(
$._expression,
seq('=~', choice(
$.regex,
$._expression
))
)),
2017-07-14 20:54:05 +00:00
repeat(choice(
$.file_redirect,
$.heredoc_redirect,
$.herestring_redirect
2017-07-14 20:54:05 +00:00
))
2017-07-14 19:28:54 +00:00
)),
command_name: $ => $._expression,
2018-03-01 18:42:20 +00:00
bracket_command: $ => {
const args = repeat1(choice(
$._expression,
seq('=~', choice(
$.regex,
$._expression
))
))
return seq(
choice(
seq('[', args, ']'),
seq('[[', args, ']]')
),
repeat(choice(
$.file_redirect,
$.heredoc_redirect,
$.herestring_redirect
))
)
},
variable_assignment: $ => seq(
choice(
$.variable_name,
$.subscript
),
2018-02-24 23:02:24 +00:00
$._assignment
),
declaration_command: $ => prec.left(seq(
choice('declare', 'typeset', 'export', 'readonly', 'local'),
repeat(choice(
$._expression,
$._simple_variable_name,
$.variable_assignment
))
)),
2018-02-24 23:02:24 +00:00
unset_command: $ => prec.left(seq(
choice('unset', 'unsetenv'),
repeat(choice(
$._expression,
$._simple_variable_name
))
)),
2018-02-24 23:02:24 +00:00
_assignment: $ => seq(
2017-12-26 22:55:37 +00:00
choice(
'=',
'+='
),
2017-07-15 00:41:14 +00:00
choice(
$._expression,
$.array,
2017-07-15 00:41:14 +00:00
$._empty_value
)
),
subscript: $ => seq(
$.variable_name,
'[',
$._expression,
optional($._concat),
']',
optional($._concat)
),
file_redirect: $ => prec.left(seq(
2017-07-16 05:13:55 +00:00
optional($.file_descriptor),
choice('<', '>', '>>', '&>', '&>>', '<&', '>&'),
$._expression
)),
2017-07-16 05:13:55 +00:00
heredoc_redirect: $ => seq(
choice('<<', '<<-'),
$.heredoc
),
heredoc: $ => choice(
$._simple_heredoc,
seq(
$._heredoc_beginning,
repeat(choice(
$.expansion,
$.simple_expansion,
$._heredoc_middle
)),
$._heredoc_end
)
),
herestring_redirect: $ => seq(
'<<<',
$._expression
),
2017-07-16 05:13:55 +00:00
// Expressions
_expression: $ => choice(
$.concatenation,
$._primary_expression,
alias(prec(-2, $._special_characters), $.word)
),
_primary_expression: $ => choice(
2017-07-16 05:13:55 +00:00
$.word,
$.string,
$.raw_string,
$.expansion,
2017-07-16 05:13:55 +00:00
$.simple_expansion,
2018-03-01 17:54:08 +00:00
$.string_expansion,
2017-07-16 05:22:38 +00:00
$.command_substitution,
$.process_substitution
2017-07-14 19:28:54 +00:00
),
concatenation: $ => prec(-1, seq(
choice(
$._primary_expression,
$._special_characters,
),
repeat1(prec(-1, seq(
$._concat,
choice(
$._primary_expression,
$._special_characters,
)
))),
)),
_special_characters: $ => token(prec(-1, repeat1(choice('{', '}', '[', ']')))),
2017-07-16 05:13:55 +00:00
string: $ => seq(
'"',
repeat(seq(
choice(
2018-03-01 17:54:08 +00:00
seq(optional('$'), $._string_content),
$.expansion,
$.simple_expansion,
$.command_substitution
),
optional($._concat)
)),
'"'
),
2018-03-01 17:54:08 +00:00
_string_content: $ => token(prec(-1, /([^"`$]|\\.)+/)),
array: $ => seq(
'(',
2017-12-26 22:55:37 +00:00
repeat($._expression),
')'
),
2017-07-16 05:13:55 +00:00
raw_string: $ => /'[^']*'/,
2017-07-16 05:13:55 +00:00
simple_expansion: $ => seq(
2017-07-14 20:00:41 +00:00
'$',
choice(
$._simple_variable_name,
$._special_variable_name,
2018-03-01 17:54:08 +00:00
alias('#', $.special_variable_name)
)
2017-07-14 20:00:41 +00:00
),
2018-03-01 17:54:08 +00:00
string_expansion: $ => seq('$', $.string),
2017-07-16 05:13:55 +00:00
expansion: $ => seq(
2017-07-14 20:00:41 +00:00
'${',
optional('#'),
choice(
seq(
$.variable_name,
'=',
optional($._expression)
),
seq(
choice(
$.subscript,
$._simple_variable_name,
$._special_variable_name
),
2018-03-01 18:41:16 +00:00
optional(seq(
token(prec(1, '/')),
alias($.regex_without_right_brace, $.regex)
)),
repeat(choice(
$._expression,
2018-03-01 18:41:16 +00:00
':', ':?', '=', ':-', '%', '-', '#'
))
),
),
2017-07-14 20:00:41 +00:00
'}'
),
2017-07-16 05:13:55 +00:00
command_substitution: $ => choice(
seq('$(', $._statement, ')'),
prec(1, seq('`', $._statement, '`'))
2017-07-14 19:43:42 +00:00
),
2017-07-16 05:22:38 +00:00
process_substitution: $ => seq(
choice('<(', '>('),
$._statement,
2017-07-16 05:22:38 +00:00
')'
),
2018-03-01 17:54:08 +00:00
comment: $ => token(prec(-10, /#.*/)),
2017-07-14 19:28:54 +00:00
_simple_variable_name: $ => alias(/\w+/, $.variable_name),
_special_variable_name: $ => alias(choice('*', '@', '?', '-', '$', '0', '_'), $.special_variable_name),
2017-07-14 21:34:49 +00:00
word: $ => token(repeat1(choice(
noneOf(...SPECIAL_CHARACTERS),
seq('\\', noneOf('\\s'))
))),
2017-07-14 23:29:28 +00:00
2018-03-01 18:41:16 +00:00
regex: $ => /([^\s]|\\.)+/,
regex_without_right_brace: $ => /([^\s}]|\\.)+/,
_terminator: $ => choice(';', ';;', '\n', '&')
2017-07-14 19:28:54 +00:00
}
});
function noneOf(...characters) {
const negatedString = characters.map(c => c == '\\' ? '\\\\' : c).join('')
return new RegExp('[^' + negatedString + ']')
}