diff --git a/tree-sitter-sh/grammar.js b/tree-sitter-sh/grammar.js index 195275d9..a825613f 100644 --- a/tree-sitter-sh/grammar.js +++ b/tree-sitter-sh/grammar.js @@ -9,641 +9,612 @@ // @ts-check const SPECIAL_CHARACTERS = [ - "|", "&", ";", "<", ">", "(", ")", "$", "`", "\\", "\"", "'", " ", "\t", "\n", + '|', '&', ';', '<', '>', '(', ')', '$', '`', '\\', '\"', '\'', ' ', '\t', '\n', ] const PREC = { - UPDATE: 0, - ASSIGN: 1, - TERNARY: 2, - LOGICAL_OR: 3, - LOGICAL_AND: 4, - BITWISE_OR: 5, - BITWISE_XOR: 6, - BITWISE_AND: 7, - EQUALITY: 8, - COMPARE: 9, - TEST: 10, - UNARY: 11, - SHIFT: 12, - ADD: 13, - MULTIPLY: 14, - EXPONENT: 15, - NEGATE: 16, - PREFIX: 17, - POSTFIX: 18, + UPDATE: 0, + ASSIGN: 1, + TERNARY: 2, + LOGICAL_OR: 3, + LOGICAL_AND: 4, + BITWISE_OR: 5, + BITWISE_XOR: 6, + BITWISE_AND: 7, + EQUALITY: 8, + COMPARE: 9, + TEST: 10, + UNARY: 11, + SHIFT: 12, + ADD: 13, + MULTIPLY: 14, + EXPONENT: 15, + NEGATE: 16, + PREFIX: 17, + POSTFIX: 18, }; module.exports = grammar({ - name: 'bash', - - conflicts: $ => [ - [$.command, $.variable_assignments], - [$.redirected_statement, $.command], - [$.redirected_statement, $.command_substitution], - [$.function_definition, $.command_name], - [$.pipeline], - ], - - inline: $ => [ - $._statement, - $._terminator, - $._literal, - $._terminated_statement, - $._primary_expression, - $._simple_variable_name, - $._multiline_variable_name, - $._special_variable_name, - $._statement_not_subshell, - ], - - externals: $ => [ - $.heredoc_start, - $.simple_heredoc_body, - $._heredoc_body_beginning, - $.heredoc_content, - $.heredoc_end, - $.file_descriptor, - $._empty_value, - $._concat, - $.variable_name, // Variable name followed by an operator like '=' or '+=' - $.regex, - $._expansion_word, - $.extglob_pattern, - $._bare_dollar, - $._immediate_double_hash, - '<<', - '<<-', - /\n/, - '(', - 'esac', - $.__error_recovery, - ], - - extras: $ => [ - $.comment, - /\s/, - /\\\r?\n/, - /\\( |\t|\v|\f)/, - ], - - supertypes: $ => [ - $._statement, - $._primary_expression, - ], - - word: $ => $.word, - - rules: { - program: $ => optional($._statements), - - _statements: $ => prec(1, seq( - repeat(seq( - $._statement, - $._terminator, - )), - $._statement, - optional($._terminator), - )), - - _terminated_statement: $ => repeat1(seq( - $._statement, - $._terminator, - )), - - // Statements - - _statement: $ => choice( - $._statement_not_subshell, - $.subshell, - ), - - _statement_not_subshell: $ => choice( - $.redirected_statement, - $.variable_assignment, - $.variable_assignments, - $.command, - $.declaration_command, - $.unset_command, - $.negated_command, - $.for_statement, - $.while_statement, - $.if_statement, - $.case_statement, - $.pipeline, - $.list, - $.compound_statement, - $.function_definition, - ), - - _statement_not_pipeline: $ => prec(1, choice( - $.redirected_statement, - $.variable_assignment, - $.variable_assignments, - $.command, - $.declaration_command, - $.unset_command, - $.negated_command, - $.for_statement, - $.while_statement, - $.if_statement, - $.case_statement, - $.list, - $.compound_statement, - $.function_definition, - $.subshell, - )), - - redirected_statement: $ => prec.dynamic(-1, prec.right(-1, choice( - seq( - field('body', $._statement), - field('redirect', repeat1(choice($.file_redirect, $.heredoc_redirect))), - ), - field('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), - ), - - while_statement: $ => seq( - choice('while', 'until'), - field('condition', $._terminated_statement), - field('body', $.do_group), - ), - - do_group: $ => seq( - 'do', - optional($._terminated_statement), - 'done', - ), - - if_statement: $ => seq( - 'if', - field('condition', $._terminated_statement), - 'then', - optional($._terminated_statement), - repeat($.elif_clause), - optional($.else_clause), - 'fi', - ), - - elif_clause: $ => seq( - 'elif', - $._terminated_statement, - 'then', - optional($._terminated_statement), - ), - - else_clause: $ => seq( - 'else', - optional($._terminated_statement), - ), - - case_statement: $ => seq( - 'case', - field('value', $._literal), - optional($._terminator), - 'in', - optional($._terminator), - optional(seq( - repeat(field('cases', $.case_item)), - field('cases', alias($._case_item_last, $.case_item)) - )), - 'esac', - ), - - _case_item_last: $ => seq( - optional('('), - field('value', choice($._literal, $._extglob_blob)), - repeat(seq('|', field('value', choice($._literal, $._extglob_blob)))), - ')', - repeat('\n'), - choice(field('statements', $._statements)), - optional(';;') - ), - - case_item: $ => seq( - optional('('), - field('value', choice($._literal, $._extglob_blob)), - repeat(seq('|', field('value', choice($._literal, $._extglob_blob)))), - ')', - repeat('\n'), - choice(field('statements', $._statements)), - ';;' - ), - - function_definition: $ => prec.right(seq( - field('name', $.word), - '(', ')', - field('body', choice($.compound_statement, $.subshell)), - field('redirect', optional($.file_redirect)), - )), - - compound_statement: $ => seq( - '{', $._terminated_statement, token(prec(1, '}')) - ), - - subshell: $ => seq('(', $._statements, ')'), - - pipeline: $ => prec.right(seq( - $._statement_not_pipeline, - repeat1(seq('|', $._statement_not_pipeline)), - )), - - list: $ => prec.left(-1, seq( - field('cmd', $._statement), - field('op', choice('&&', '||')), - field('cmd', $._statement), - )), - - // Commands - - negated_command: $ => seq( - '!', - choice( - prec(2, $.command), - prec(1, $.variable_assignment), - $.subshell, - ), - ), - - 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, - field('redirect', $.file_redirect), - )), - field('name', $.command_name), - choice( - repeat(choice( - field('argument', $._literal), - field('argument', alias($._bare_dollar, '$')), - )), - $.subshell, - ), - )), - - command_name: $ => $._literal, - - variable_assignment: $ => seq( - field('name', choice( - $.variable_name, - )), - '=', - field('value', choice( - $._literal, - - $._empty_value, - alias($._comment_word, $.word), - )), - ), - - variable_assignments: $ => seq($.variable_assignment, repeat1($.variable_assignment)), - - file_redirect: $ => prec.left(seq( - field('descriptor', optional($.file_descriptor)), - choice( - seq( - choice('<', '>', '>>', '&>', '&>>', '<&', '>&', '>|'), - field('destination', repeat1($._literal)), - ), - seq( - choice('<&-', '>&-'), // close file descriptor - optional(field('destination', $._literal)), - ), - ), - )), - - heredoc_redirect: $ => seq( - field('descriptor', optional($.file_descriptor)), - choice('<<', '<<-'), - $.heredoc_start, - optional(choice( - alias($._heredoc_pipeline, $.pipeline), - seq( - field('redirect', repeat1($.file_redirect)), - optional($._heredoc_expression), - ), - $._heredoc_expression, - $._heredoc_command, - )), - /\n/, - choice($._heredoc_body, $._simple_heredoc_body), - ), - - _heredoc_pipeline: $ => seq( - choice('|', '|&'), - $._statement, - ), - - _heredoc_expression: $ => seq( - field('operator', choice('||', '&&')), - field('right', $._statement), - ), - - _heredoc_command: $ => repeat1(field('argument', $._literal)), - - _heredoc_body: $ => seq( - $.heredoc_body, - $.heredoc_end, - ), - - heredoc_body: $ => seq( - $._heredoc_body_beginning, - repeat(choice( - $.expansion, - $.simple_expansion, - $.command_substitution, - $.heredoc_content, - )), - ), - - _simple_heredoc_body: $ => seq(alias($.simple_heredoc_body, $.heredoc_body), $.heredoc_end), - - // Literals - - _literal: $ => choice($.concatenation, $._primary_expression), - - _primary_expression: $ => choice( - $.word, - $.string, - $.raw_string, - $.number, - $.expansion, - $.simple_expansion, - $.command_substitution, - $.arithmetic_expansion, - ), - - arithmetic_expansion: $ => seq('$((', optional($._arithmetic_expression), '))'), - - _arithmetic_expression: $ => prec(1, choice( - $.arithmetic_literal, - $.arithmetic_unary_expression, - $.arithmetic_ternary_expression, - $.arithmetic_binary_expression, - $.arithmetic_postfix_expression, - $.arithmetic_parenthesized_expression, - $.command_substitution, - )), - - arithmetic_literal: $ => prec(1, choice( - $.number, - $.simple_expansion, - $.expansion, - $._simple_variable_name, - $.variable_name, - $.string, - )), - - arithmetic_binary_expression: $ => { - - /** @type {[RuleOrLiteral, number][]} */ - const table = [ - [choice('+=', '-=', '*=', '/=', '%=', '<<=', '>>=', '&=', '^=', '|='), PREC.UPDATE], - ['=', PREC.ASSIGN], - ['||', PREC.LOGICAL_OR], - ['&&', PREC.LOGICAL_AND], - ['|', PREC.BITWISE_OR], - ['^', PREC.BITWISE_XOR], - ['&', PREC.BITWISE_AND], - [choice('==', '!='), PREC.EQUALITY], - [choice('<', '>', '<=', '>='), PREC.COMPARE], - [choice('<<', '>>'), PREC.SHIFT], - [choice('+', '-'), PREC.ADD], - [choice('*', '/', '%'), PREC.MULTIPLY], - ]; - - return choice(...table.map(([operator, precedence]) => - prec.left(precedence, seq( - field('left', $._arithmetic_expression), - field('operator', operator), - field('right', $._arithmetic_expression), - )) - )); - }, - - arithmetic_ternary_expression: $ => prec.left(PREC.TERNARY, seq( - field('condition', $._arithmetic_expression), - '?', - field('consequence', $._arithmetic_expression), - ':', - field('alternative', $._arithmetic_expression), - )), - - arithmetic_unary_expression: $ => choice( - prec(PREC.PREFIX, seq( - field('operator', tokenLiterals(1, '++', '--')), - $._arithmetic_expression, - )), - prec(PREC.UNARY, seq( - field('operator', tokenLiterals(1, '-', '+', '~')), - $._arithmetic_expression, - )), - prec.right(PREC.UNARY, seq( - field('operator', '!'), - $._arithmetic_expression, - )), - ), - - arithmetic_postfix_expression: $ => prec(PREC.POSTFIX, seq( - $._arithmetic_expression, - field('operator', choice('++', '--')), - )), - - arithmetic_parenthesized_expression: $ => seq('(', $._arithmetic_expression, ')'), - - concatenation: $ => prec(-1, seq( - $._primary_expression, - repeat1(seq( - choice($._concat, alias(/`\s*`/, '``')), - choice( - $._primary_expression, - alias($._comment_word, $.word), - alias($._bare_dollar, '$'), - ), - )), - optional(seq($._concat, '$')), - )), - - 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]|\\(.|\r?\n))+/)), - - raw_string: _ => /'[^']*'/, - - number: $ => choice( - /-?(0x)?[0-9]+(#[0-9A-Za-z@_]+)?/, - // the base can be an expansion or command substitution - seq(/-?(0x)?[0-9]+#/, choice($.expansion, $.command_substitution)), - ), - - simple_expansion: $ => seq( - '$', - choice( - $._simple_variable_name, - $._multiline_variable_name, - $._special_variable_name, - $.variable_name, - alias('!', $.special_variable_name), - alias('#', $.special_variable_name), - ), - ), - - string_expansion: $ => seq('$', $.string), - - expansion: $ => seq( - '${', - optional($._expansion_body), - '}', - ), - _expansion_body: $ => seq( - choice($.variable_name, $._simple_variable_name, $._special_variable_name), - optional(choice($._expansion_expression, $._expansion_regex)), - ), - - - _expansion_expression: $ => prec(1, seq( - field('operator', immediateLiterals(':-', '-', ':=', '=', ':?', '?', ':+', '+',)), - optional(seq( - choice( - alias($._concatenation_in_expansion, $.concatenation), - $.word, - $.expansion, - $.string, - $.raw_string, - alias($._expansion_word, $.word), - ), - )), - )), - - _expansion_regex: $ => seq( - field('operator', choice('#', alias($._immediate_double_hash, '##'), '%', '%%')), - repeat(choice( - $.regex, - alias(')', $.regex), - $.string, - $.raw_string, - alias(/\s+/, $.regex), - )), - ), - - - _concatenation_in_expansion: $ => prec(-2, seq( - choice( - $.word, - $.variable_name, - $.simple_expansion, - $.expansion, - $.string, - $.raw_string, - $.command_substitution, - alias($._expansion_word, $.word), - ), - repeat1(seq( - choice($._concat, alias(/`\s*`/, '``')), - choice( - $.word, - $.variable_name, - $.simple_expansion, - $.expansion, - $.string, - $.raw_string, - $.command_substitution, - alias($._expansion_word, $.word), - ), - )), - )), - - command_substitution: $ => choice( - seq('$(', $._statements, ')'), - seq('$(', field('redirect', $.file_redirect), ')'), - prec(1, seq('`', $._statements, '`')), - seq('$`', $._statements, '`'), - ), - - _extglob_blob: $ => choice( - $.extglob_pattern, - seq( - $.extglob_pattern, - choice($.string, $.expansion, $.command_substitution), - optional($.extglob_pattern), - ), - ), - - comment: _ => token(prec(-10, /#.*/)), - - _comment_word: _ => token(prec(-8, 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')), - '\\ ', - )), - )), - _terminator: _ => choice(';', ';;', /\n/, '&'), - }, + name: 'sh', + + conflicts: $ => [ + [$.command, $.variable_assignments], + [$.redirected_statement, $.command], + [$.redirected_statement, $.command_substitution], + [$.function_definition, $.command_name], + [$.pipeline], + ], + + inline: $ => [ + $._statement, + $._terminator, + $._literal, + $._terminated_statement, + $._primary_expression, + $._simple_variable_name, + $._multiline_variable_name, + $._special_variable_name, + $._statement_not_subshell, + ], + + externals: $ => [ + $.heredoc_start, + $.simple_heredoc_body, + $._heredoc_body_beginning, + $.heredoc_content, + $.heredoc_end, + $.file_descriptor, + $._empty_value, + $._concat, + $.variable_name, // Variable name followed by an operator like '=' or '+=' + $.regex, + $._expansion_word, + $.extglob_pattern, + $._bare_dollar, + $._immediate_double_hash, + '<<', + '<<-', + /\n/, + '(', + 'esac', + $.__error_recovery, + ], + + extras: $ => [ + $.comment, + /\s/, + /\\\r?\n/, + /\\( |\t|\v|\f)/, + ], + + supertypes: $ => [ + $._statement, + $._primary_expression, + ], + + word: $ => $.word, + + rules: { + program: $ => optional($._statements), + + _statements: $ => prec(1, seq( + repeat(seq( + $._statement, + $._terminator, + )), + $._statement, + optional($._terminator), + )), + + _terminated_statement: $ => repeat1(seq($._statement, $._terminator)), + + // Statements + + _statement: $ => choice( + $._statement_not_subshell, + $.subshell, + ), + + _statement_not_subshell: $ => choice( + $.case_statement, + $.command, + $.compound_statement, + $.for_statement, + $.function_definition, + $.if_statement, + $.list, + $.negated_command, + $.pipeline, + $.redirected_statement, + $.variable_assignment, + $.variable_assignments, + $.while_statement, + ), + + _statement_not_pipeline: $ => prec(1, choice( + $.case_statement, + $.command, + $.compound_statement, + $.for_statement, + $.function_definition, + $.if_statement, + $.list, + $.negated_command, + $.redirected_statement, + $.subshell, + $.variable_assignment, + $.variable_assignments, + $.while_statement, + )), + + redirected_statement: $ => prec.dynamic(-1, prec.right(-1, choice( + seq( + field('body', $._statement), + field('redirect', repeat1(choice($.file_redirect, $.heredoc_redirect))), + ), + field('redirect', repeat1($.file_redirect)), + ))), + + for_statement: $ => seq( + 'for', + field('variable', $._simple_variable_name), + optional(seq( + 'in', + field('value', repeat1($._literal)), + )), + $._terminator, + field('body', $.do_group), + ), + + while_statement: $ => seq( + choice('while', 'until'), + field('condition', $._terminated_statement), + field('body', $.do_group), + ), + + do_group: $ => seq( + 'do', + optional($._terminated_statement), + 'done', + ), + + if_statement: $ => seq( + 'if', + field('condition', $._terminated_statement), + 'then', + optional($._terminated_statement), + repeat($.elif_clause), + optional($.else_clause), + 'fi', + ), + + elif_clause: $ => seq( + 'elif', + $._terminated_statement, + 'then', + optional($._terminated_statement), + ), + + else_clause: $ => seq( + 'else', + optional($._terminated_statement), + ), + + case_statement: $ => seq( + 'case', + field('value', $._literal), + optional($._terminator), + 'in', + optional($._terminator), + optional(seq( + repeat(field('cases', $.case_item)), + field('cases', alias($._case_item_last, $.case_item)) + )), + 'esac', + ), + + _case_item_last: $ => seq( + optional('('), + field('value', choice($._literal, $._extglob_blob)), + repeat(seq('|', field('value', choice($._literal, $._extglob_blob)))), + ')', + repeat('\n'), + choice(field('cmds', $._statements)), + optional(';;') + ), + + case_item: $ => seq( + optional('('), + field('value', choice($._literal, $._extglob_blob)), + repeat(seq('|', field('value', choice($._literal, $._extglob_blob)))), + ')', + repeat('\n'), + choice(field('cmds', $._statements)), + ';;' + ), + + function_definition: $ => prec.right(seq( + field('name', $.word), + '(', ')', + field('body', choice($.compound_statement, $.subshell, $.command, $.while_statement, $.if_statement, $.for_statement, $.variable_assignments, repeat1($.file_redirect))), + )), + + compound_statement: $ => seq('{', $._terminated_statement, '}'), + subshell: $ => seq('(', $._statements, ')'), + + pipeline: $ => prec.right(seq( + $._statement_not_pipeline, + repeat1(seq('|', $._statement_not_pipeline)), + )), + + list: $ => prec.left(-1, seq( + field('cmd', $._statement), + field('op', choice('&&', '||')), + field('cmd', $._statement), + )), + + // Commands + + negated_command: $ => seq( + '!', + choice( + prec(2, $.command), + prec(1, $.variable_assignment), + $.subshell, + ), + ), + + command: $ => prec.left(seq( + repeat(choice( + $.variable_assignment, + field('redirect', $.file_redirect), + )), + field('name', $.command_name), + choice( + repeat(choice( + field('arg', $._literal), + field('arg', alias($._bare_dollar, '$')), + )), + $.subshell, + ), + )), + + command_name: $ => $._literal, + + variable_assignment: $ => seq( + field('name', choice( + $.variable_name, + )), + '=', + field('value', choice( + $._literal, + + $._empty_value, + alias($._comment_word, $.word), + )), + ), + + variable_assignments: $ => seq($.variable_assignment, repeat1($.variable_assignment)), + + file_redirect: $ => prec.left(seq( + field('fd', optional($.file_descriptor)), + choice( + seq( + field('op', choice('<', '>', '>>', '&>', '&>>', '<&', '>&', '>|')), + field('dest', repeat1($._literal)), + ), + seq( + field('op', choice('<&-', '>&-')), + field('dest', optional($._literal)), + ), + ), + )), + + heredoc_redirect: $ => seq( + field('fd', optional($.file_descriptor)), + field('op', choice('<<', '<<-')), + $.heredoc_start, + optional(choice( + alias($._heredoc_pipeline, $.pipeline), + seq( + field('redirect', repeat1($.file_redirect)), + optional($._heredoc_expression), + ), + $._heredoc_expression, + $._heredoc_command, + )), + /\n/, + choice($._heredoc_body, $._simple_heredoc_body), + ), + + _heredoc_pipeline: $ => seq( + choice('|', '|&'), + $._statement, + ), + + _heredoc_expression: $ => seq( + field('op', choice('||', '&&')), + field('right', $._statement), + ), + + _heredoc_command: $ => repeat1(field('arg', $._literal)), + + _heredoc_body: $ => seq( + $.heredoc_body, + $.heredoc_end, + ), + + heredoc_body: $ => seq( + $._heredoc_body_beginning, + repeat(choice( + $.expansion, + $.simple_expansion, + $.command_substitution, + $.heredoc_content, + )), + ), + + _simple_heredoc_body: $ => seq(alias($.simple_heredoc_body, $.heredoc_body), $.heredoc_end), + + // Literals + + _literal: $ => choice($.concatenation, $._primary_expression), + + _primary_expression: $ => choice( + $.word, + $.string, + $.raw_string, + $.number, + $.expansion, + $.simple_expansion, + $.command_substitution, + $.arithmetic_expansion, + ), + + arithmetic_expansion: $ => seq('$((', optional($._arithmetic_expression), '))'), + + _arithmetic_expression: $ => prec(1, choice( + $.arithmetic_literal, + $.arithmetic_unary_expression, + $.arithmetic_ternary_expression, + $.arithmetic_binary_expression, + $.arithmetic_postfix_expression, + $.arithmetic_parenthesized_expression, + $.command_substitution, + )), + + arithmetic_literal: $ => prec(1, choice( + $.number, + $.simple_expansion, + $.expansion, + $._simple_variable_name, + $.variable_name, + $.string, + )), + + arithmetic_binary_expression: $ => { + + /** @type {[RuleOrLiteral, number][]} */ + const table = [ + [choice('+=', '-=', '*=', '/=', '%=', '<<=', '>>=', '&=', '^=', '|='), PREC.UPDATE], + ['=', PREC.ASSIGN], + ['||', PREC.LOGICAL_OR], + ['&&', PREC.LOGICAL_AND], + ['|', PREC.BITWISE_OR], + ['^', PREC.BITWISE_XOR], + ['&', PREC.BITWISE_AND], + [choice('==', '!='), PREC.EQUALITY], + [choice('<', '>', '<=', '>='), PREC.COMPARE], + [choice('<<', '>>'), PREC.SHIFT], + [choice('+', '-'), PREC.ADD], + [choice('*', '/', '%'), PREC.MULTIPLY], + ]; + + return choice(...table.map(([operator, precedence]) => + prec.left(precedence, seq( + field('left', $._arithmetic_expression), + field('op', operator), + field('right', $._arithmetic_expression), + )) + )); + }, + + arithmetic_ternary_expression: $ => prec.left(PREC.TERNARY, seq( + field('condition', $._arithmetic_expression), + '?', + field('consequence', $._arithmetic_expression), + ':', + field('alternative', $._arithmetic_expression), + )), + + arithmetic_unary_expression: $ => choice( + prec(PREC.PREFIX, seq( + field('op', tokenLiterals(1, '++', '--')), + $._arithmetic_expression, + )), + prec(PREC.UNARY, seq( + field('op', tokenLiterals(1, '-', '+', '~')), + $._arithmetic_expression, + )), + prec.right(PREC.UNARY, seq( + field('op', '!'), + $._arithmetic_expression, + )), + ), + + arithmetic_postfix_expression: $ => prec(PREC.POSTFIX, seq( + $._arithmetic_expression, + field('op', choice('++', '--')), + )), + + arithmetic_parenthesized_expression: $ => seq('(', $._arithmetic_expression, ')'), + + concatenation: $ => prec(-1, seq( + $._primary_expression, + repeat1(seq( + choice($._concat, alias(/`\s*`/, '``')), + choice( + $._primary_expression, + alias($._comment_word, $.word), + alias($._bare_dollar, '$'), + ), + )), + optional(seq($._concat, '$')), + )), + + 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]|\\(.|\r?\n))+/)), + + raw_string: _ => /'[^']*'/, + + number: $ => choice( + /-?(0x)?[0-9]+(#[0-9A-Za-z@_]+)?/, + // the base can be an expansion or command substitution + seq(/-?(0x)?[0-9]+#/, choice($.expansion, $.command_substitution)), + ), + + simple_expansion: $ => seq( + '$', + choice( + $._simple_variable_name, + $._multiline_variable_name, + $._special_variable_name, + $.variable_name, + alias('!', $.special_variable_name), + alias('#', $.special_variable_name), + ), + ), + + string_expansion: $ => seq('$', $.string), + + expansion: $ => seq( + '${', + optional($._expansion_body), + '}', + ), + _expansion_body: $ => seq( + field('name', choice($.variable_name, $._simple_variable_name, $._special_variable_name)), + field('op', optional(choice($.expansion_expression, $.expansion_regex))), + ), + + + expansion_expression: $ => prec(1, seq( + field('op', immediateLiterals(':-', '-', ':=', '=', ':?', '?', ':+', '+',)), + optional(seq( + choice( + alias($._concatenation_in_expansion, $.concatenation), + $.word, + $.expansion, + $.string, + $.raw_string, + alias($._expansion_word, $.word), + ), + )), + )), + + expansion_regex: $ => seq( + field('op', choice('#', alias($._immediate_double_hash, '##'), '%', '%%')), + repeat(choice( + $.regex, + alias(')', $.regex), + $.string, + $.raw_string, + alias(/\s+/, $.regex), + )), + ), + + + _concatenation_in_expansion: $ => prec(-2, seq( + choice( + $.word, + $.variable_name, + $.simple_expansion, + $.expansion, + $.string, + $.raw_string, + $.command_substitution, + alias($._expansion_word, $.word), + ), + repeat1(seq( + choice($._concat, alias(/`\s*`/, '``')), + choice( + $.word, + $.variable_name, + $.simple_expansion, + $.expansion, + $.string, + $.raw_string, + $.command_substitution, + alias($._expansion_word, $.word), + ), + )), + )), + + command_substitution: $ => choice( + seq('$(', $._statements, ')'), + seq('$(', field('redirect', $.file_redirect), ')'), + prec(1, seq('`', $._statements, '`')), + ), + + _extglob_blob: $ => choice( + $.extglob_pattern, + seq( + $.extglob_pattern, + choice($.string, $.expansion, $.command_substitution), + optional($.extglob_pattern), + ), + ), + + comment: _ => token(prec(-10, /#.*/)), + + _comment_word: _ => token(prec(-8, 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')), + '\\ ', + )), + )), + _terminator: _ => choice(';', ';;', /\n/, '&'), + }, }); /** @@ -656,8 +627,8 @@ module.exports = grammar({ * */ function noneOf(...characters) { - const negatedString = characters.map(c => c == '\\' ? '\\\\' : c).join(''); - return new RegExp('[^' + negatedString + ']'); + const negatedString = characters.map(c => c == '\\' ? '\\\\' : c).join(''); + return new RegExp('[^' + negatedString + ']'); } /** @@ -669,7 +640,7 @@ function noneOf(...characters) { * */ function commaSep(rule) { - return optional(commaSep1(rule)); + return optional(commaSep1(rule)); } /** @@ -681,7 +652,7 @@ function commaSep(rule) { * */ function commaSep1(rule) { - return seq(rule, repeat(seq(',', rule))); + return seq(rule, repeat(seq(',', rule))); } /** @@ -693,7 +664,7 @@ function commaSep1(rule) { * @return {ChoiceRule} */ function immediateLiterals(...literals) { - return choice(...literals.map(l => token.immediate(l))); + return choice(...literals.map(l => token.immediate(l))); } /** @@ -707,5 +678,5 @@ function immediateLiterals(...literals) { * @return {ChoiceRule} */ function tokenLiterals(precedence, ...literals) { - return choice(...literals.map(l => token(prec(precedence, l)))); -} \ No newline at end of file + return choice(...literals.map(l => token(prec(precedence, l)))); +} diff --git a/tree-sitter-sh/src/scanner.c b/tree-sitter-sh/src/scanner.c index e94166c3..a695a203 100644 --- a/tree-sitter-sh/src/scanner.c +++ b/tree-sitter-sh/src/scanner.c @@ -1204,32 +1204,32 @@ brace_start: return false; } -void *tree_sitter_bash_external_scanner_create() +void *tree_sitter_sh_external_scanner_create() { Scanner *scanner = calloc(1, sizeof(Scanner)); array_init(&scanner->heredocs); return scanner; } -bool tree_sitter_bash_external_scanner_scan(void *payload, TSLexer *lexer, const bool *valid_symbols) +bool tree_sitter_sh_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) +unsigned tree_sitter_sh_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) +void tree_sitter_sh_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) +void tree_sitter_sh_external_scanner_destroy(void *payload) { Scanner *scanner = (Scanner *)payload; for (size_t i = 0; i < scanner->heredocs.size; i++)