mirror of https://github.com/Wilfred/difftastic/
1223 lines
32 KiB
JavaScript
1223 lines
32 KiB
JavaScript
const PREC = {
|
|
COMMENT: -2,
|
|
CURLY_BLOCK: 1,
|
|
DO_BLOCK: -1,
|
|
|
|
AND: -2,
|
|
OR: -2,
|
|
NOT: 5,
|
|
DEFINED: 10,
|
|
ALIAS: 11,
|
|
ASSIGN: 15,
|
|
RESCUE: 16,
|
|
CONDITIONAL: 20,
|
|
RANGE: 25,
|
|
BOOLEAN_OR: 30,
|
|
BOOLEAN_AND: 35,
|
|
RELATIONAL: 40,
|
|
COMPARISON: 45,
|
|
BITWISE_OR: 50,
|
|
BITWISE_AND: 55,
|
|
CALL: 56,
|
|
SHIFT: 60,
|
|
ADDITIVE: 65,
|
|
MULTIPLICATIVE: 70,
|
|
UNARY_MINUS: 75,
|
|
EXPONENTIAL: 80,
|
|
COMPLEMENT: 85,
|
|
};
|
|
|
|
const IDENTIFIER_CHARS = /[^\x00-\x1F\s:;`"'@$#.,|^&<=>+\-*/\\%?!~()\[\]{}]*/;
|
|
const LOWER_ALPHA_CHAR = /[^\x00-\x1F\sA-Z0-9:;`"'@$#.,|^&<=>+\-*/\\%?!~()\[\]{}]/;
|
|
const ALPHA_CHAR = /[^\x00-\x1F\s0-9:;`"'@$#.,|^&<=>+\-*/\\%?!~()\[\]{}]/;
|
|
|
|
module.exports = grammar({
|
|
name: 'ruby',
|
|
inline: $ => [$._arg_rhs, $._call_operator],
|
|
externals: $ => [
|
|
$._line_break,
|
|
$._no_line_break,
|
|
|
|
// Delimited literals
|
|
$.simple_symbol,
|
|
$._string_start,
|
|
$._symbol_start,
|
|
$._subshell_start,
|
|
$._regex_start,
|
|
$._string_array_start,
|
|
$._symbol_array_start,
|
|
$._heredoc_body_start,
|
|
$.string_content,
|
|
$.heredoc_content,
|
|
$._string_end,
|
|
$.heredoc_end,
|
|
$.heredoc_beginning,
|
|
|
|
// Tokens that require lookahead
|
|
'/',
|
|
$._block_ampersand,
|
|
$._splat_star,
|
|
$._unary_minus,
|
|
$._unary_minus_num,
|
|
$._binary_minus,
|
|
$._binary_star,
|
|
$._singleton_class_left_angle_left_langle,
|
|
$.hash_key_symbol,
|
|
$._identifier_suffix,
|
|
$._constant_suffix,
|
|
$._hash_splat_star_star,
|
|
$._binary_star_star,
|
|
$._element_reference_bracket,
|
|
],
|
|
|
|
extras: $ => [
|
|
$.comment,
|
|
$.heredoc_body,
|
|
/\s/,
|
|
/\\\r?\n/
|
|
],
|
|
|
|
word: $ => $.identifier,
|
|
|
|
supertypes: $ => [
|
|
$._statement,
|
|
$._arg,
|
|
$._call_operator,
|
|
$._method_name,
|
|
$._expression,
|
|
$._variable,
|
|
$._primary,
|
|
$._simple_numeric,
|
|
$._lhs,
|
|
$._nonlocal_variable,
|
|
$._pattern_top_expr_body,
|
|
$._pattern_expr,
|
|
$._pattern_expr_basic,
|
|
$._pattern_primitive,
|
|
$._pattern_constant,
|
|
],
|
|
|
|
rules: {
|
|
program: $ => seq(
|
|
optional($._statements),
|
|
optional(seq(
|
|
'__END__',
|
|
$._line_break,
|
|
$.uninterpreted)
|
|
)
|
|
),
|
|
|
|
uninterpreted: $ => /(.|\s)*/,
|
|
|
|
_statements: $ => choice(
|
|
seq(
|
|
repeat1(choice(
|
|
seq($._statement, $._terminator),
|
|
$.empty_statement
|
|
)),
|
|
optional($._statement)
|
|
),
|
|
$._statement
|
|
),
|
|
|
|
begin_block: $ => seq("BEGIN", "{", optional($._statements), "}"),
|
|
end_block: $ => seq("END", "{", optional($._statements), "}"),
|
|
|
|
_statement: $ => choice(
|
|
$.undef,
|
|
$.alias,
|
|
$.if_modifier,
|
|
$.unless_modifier,
|
|
$.while_modifier,
|
|
$.until_modifier,
|
|
$.rescue_modifier,
|
|
$.begin_block,
|
|
$.end_block,
|
|
$._expression
|
|
),
|
|
|
|
method: $ => seq('def', $._method_rest),
|
|
|
|
singleton_method: $ => seq(
|
|
'def',
|
|
seq(
|
|
choice(
|
|
field('object', $._variable),
|
|
seq('(', field('object', $._arg), ')')
|
|
),
|
|
choice('.', '::')
|
|
),
|
|
$._method_rest
|
|
),
|
|
|
|
_method_rest: $ => seq(
|
|
field('name', $._method_name),
|
|
choice(
|
|
$._body_expr,
|
|
seq(
|
|
field('parameters', alias($.parameters, $.method_parameters)),
|
|
choice(
|
|
seq(optional($._terminator), $._body_statement),
|
|
$._body_expr
|
|
)
|
|
|
|
),
|
|
seq(
|
|
optional(
|
|
field('parameters', alias($.bare_parameters, $.method_parameters))
|
|
),
|
|
$._terminator,
|
|
$._body_statement
|
|
),
|
|
),
|
|
),
|
|
|
|
rescue_modifier_arg: $ => prec(PREC.RESCUE,
|
|
seq(
|
|
field('body', $._arg),
|
|
'rescue',
|
|
field('handler', $._arg)
|
|
)
|
|
),
|
|
|
|
rescue_modifier_expression: $ => prec(PREC.RESCUE,
|
|
seq(
|
|
field('body', $._expression),
|
|
'rescue',
|
|
field('handler', $._arg)
|
|
)
|
|
),
|
|
|
|
_body_expr: $ =>
|
|
seq(
|
|
'=',
|
|
choice(
|
|
$._arg,
|
|
alias($.rescue_modifier_arg, $.rescue_modifier),
|
|
)
|
|
),
|
|
|
|
|
|
parameters: $ => seq(
|
|
'(',
|
|
commaSep($._formal_parameter),
|
|
')'
|
|
),
|
|
|
|
bare_parameters: $ => seq(
|
|
$._simple_formal_parameter,
|
|
repeat(seq(',', $._formal_parameter))
|
|
),
|
|
|
|
block_parameters: $ => seq(
|
|
'|',
|
|
seq(commaSep($._formal_parameter), optional(',')),
|
|
optional(seq(';', sep1(field('locals', $.identifier), ','))), // Block shadow args e.g. {|; a, b| ...}
|
|
'|'
|
|
),
|
|
|
|
_formal_parameter: $ => choice(
|
|
$._simple_formal_parameter,
|
|
alias($.parameters, $.destructured_parameter)
|
|
),
|
|
|
|
_simple_formal_parameter: $ => choice(
|
|
$.identifier,
|
|
$.splat_parameter,
|
|
$.hash_splat_parameter,
|
|
$.hash_splat_nil,
|
|
$.forward_parameter,
|
|
$.block_parameter,
|
|
$.keyword_parameter,
|
|
$.optional_parameter
|
|
),
|
|
|
|
forward_parameter: $ => '...',
|
|
|
|
splat_parameter: $ => seq(
|
|
'*',
|
|
field('name', optional($.identifier))
|
|
),
|
|
hash_splat_parameter: $ => seq(
|
|
'**',
|
|
field('name', optional($.identifier))
|
|
),
|
|
hash_splat_nil: $ => seq('**', 'nil'),
|
|
block_parameter: $ => seq(
|
|
'&',
|
|
field('name', optional($.identifier))
|
|
),
|
|
keyword_parameter: $ => prec.right(PREC.BITWISE_OR + 1, seq(
|
|
field('name', $.identifier),
|
|
token.immediate(':'),
|
|
field('value', optional($._arg))
|
|
)),
|
|
optional_parameter: $ => prec(PREC.BITWISE_OR + 1, seq(
|
|
field('name', $.identifier),
|
|
'=',
|
|
field('value', $._arg)
|
|
)),
|
|
|
|
class: $ => seq(
|
|
'class',
|
|
field('name', choice($.constant, $.scope_resolution)),
|
|
field('superclass', optional($.superclass)),
|
|
$._terminator,
|
|
$._body_statement
|
|
),
|
|
|
|
superclass: $ => seq('<', $._expression),
|
|
|
|
singleton_class: $ => seq(
|
|
'class',
|
|
alias($._singleton_class_left_angle_left_langle, '<<'),
|
|
field('value', $._arg),
|
|
$._terminator,
|
|
$._body_statement
|
|
),
|
|
|
|
module: $ => seq(
|
|
'module',
|
|
field('name', choice($.constant, $.scope_resolution)),
|
|
choice(
|
|
seq($._terminator, $._body_statement),
|
|
'end'
|
|
)
|
|
),
|
|
|
|
return_command: $ => prec.left(seq('return', alias($.command_argument_list, $.argument_list))),
|
|
yield_command: $ => prec.left(seq('yield', alias($.command_argument_list, $.argument_list))),
|
|
break_command: $ => prec.left(seq('break', alias($.command_argument_list, $.argument_list))),
|
|
next_command: $ => prec.left(seq('next', alias($.command_argument_list, $.argument_list))),
|
|
return: $ => prec.left(seq('return', optional($.argument_list))),
|
|
yield: $ => prec.left(seq('yield', optional($.argument_list))),
|
|
break: $ => prec.left(seq('break', optional($.argument_list))),
|
|
next: $ => prec.left(seq('next', optional($.argument_list))),
|
|
redo: $ => prec.left(seq('redo', optional($.argument_list))),
|
|
retry: $ => prec.left(seq('retry', optional($.argument_list))),
|
|
|
|
if_modifier: $ => prec(PREC.RESCUE, seq(
|
|
field('body', $._statement),
|
|
'if',
|
|
field('condition', $._expression)
|
|
)),
|
|
|
|
unless_modifier: $ => prec(PREC.RESCUE, seq(
|
|
field('body', $._statement),
|
|
'unless',
|
|
field('condition', $._expression)
|
|
)),
|
|
|
|
while_modifier: $ => prec(PREC.RESCUE, seq(
|
|
field('body', $._statement),
|
|
'while',
|
|
field('condition', $._expression)
|
|
)),
|
|
|
|
until_modifier: $ => prec(PREC.RESCUE, seq(
|
|
field('body', $._statement),
|
|
'until',
|
|
field('condition', $._expression)
|
|
)),
|
|
|
|
rescue_modifier: $ => prec(PREC.RESCUE, seq(
|
|
field('body', $._statement),
|
|
'rescue',
|
|
field('handler', $._expression)
|
|
)),
|
|
|
|
while: $ => seq(
|
|
'while',
|
|
field('condition', $._statement),
|
|
field('body', $.do)
|
|
),
|
|
|
|
until: $ => seq(
|
|
'until',
|
|
field('condition', $._statement),
|
|
field('body', $.do)
|
|
),
|
|
|
|
for: $ => seq(
|
|
'for',
|
|
field('pattern', choice($._lhs, $.left_assignment_list)),
|
|
field('value', $.in),
|
|
field('body', $.do)
|
|
),
|
|
|
|
in: $ => seq('in', $._arg),
|
|
do: $ => seq(
|
|
choice('do', $._terminator),
|
|
optional($._statements),
|
|
'end'
|
|
),
|
|
|
|
case: $ => seq(
|
|
'case',
|
|
field('value', optional($._statement)),
|
|
optional($._terminator),
|
|
repeat($.when),
|
|
optional($.else),
|
|
'end'
|
|
),
|
|
|
|
case_match: $ => seq(
|
|
'case',
|
|
field('value', $._statement),
|
|
optional($._terminator),
|
|
repeat1(field('clauses', $.in_clause)),
|
|
optional(field('else', $.else)),
|
|
'end'
|
|
),
|
|
|
|
when: $ => seq(
|
|
'when',
|
|
commaSep1(field('pattern', $.pattern)),
|
|
choice($._terminator, field('body', $.then))
|
|
),
|
|
|
|
in_clause: $ => seq(
|
|
'in',
|
|
field('pattern', $._pattern_top_expr_body),
|
|
field('guard', optional($._guard)),
|
|
choice($._terminator, field('body', $.then))
|
|
),
|
|
|
|
pattern: $ => choice($._arg, $.splat_argument),
|
|
|
|
_guard: $ => choice(
|
|
$.if_guard,
|
|
$.unless_guard
|
|
),
|
|
|
|
if_guard: $ => seq(
|
|
'if',
|
|
field('condition', $._expression)
|
|
),
|
|
|
|
unless_guard: $ => seq(
|
|
'unless',
|
|
field('condition', $._expression)
|
|
),
|
|
|
|
_pattern_top_expr_body: $ => choice(
|
|
$._pattern_expr,
|
|
alias($._array_pattern_n, $.array_pattern),
|
|
alias($._find_pattern_body, $.find_pattern),
|
|
alias($._hash_pattern_body, $.hash_pattern),
|
|
),
|
|
|
|
_array_pattern_n: $ => choice(
|
|
seq($._pattern_expr, alias(',', $.splat_parameter)),
|
|
seq($._pattern_expr, ',', choice($._pattern_expr, $._array_pattern_n)),
|
|
seq($.splat_parameter, repeat(seq(',', $._pattern_expr))),
|
|
),
|
|
|
|
_pattern_expr: $ => choice(
|
|
$.as_pattern,
|
|
$._pattern_expr_alt,
|
|
),
|
|
|
|
as_pattern: $ => seq(field('value', $._pattern_expr), '=>', field('name', $.identifier)),
|
|
|
|
_pattern_expr_alt: $ => choice(
|
|
$.alternative_pattern,
|
|
$._pattern_expr_basic,
|
|
),
|
|
|
|
alternative_pattern: $ => seq(field('alternatives', $._pattern_expr_basic), repeat1(seq('|', field('alternatives', $._pattern_expr_basic)))),
|
|
|
|
_array_pattern_body: $ => choice(
|
|
$._pattern_expr,
|
|
$._array_pattern_n,
|
|
),
|
|
|
|
array_pattern: $ => choice(
|
|
seq('[', optional($._array_pattern_body), ']'),
|
|
seq(field('class', $._pattern_constant), token.immediate('['), optional($._array_pattern_body), ']'),
|
|
seq(field('class', $._pattern_constant), token.immediate('('), optional($._array_pattern_body), ')')
|
|
),
|
|
|
|
_find_pattern_body: $ => seq($.splat_parameter, repeat1(seq(',', $._pattern_expr)), ',', $.splat_parameter),
|
|
find_pattern: $ => choice(
|
|
seq('[', $._find_pattern_body, ']'),
|
|
seq(field('class', $._pattern_constant), token.immediate('['), $._find_pattern_body, ']'),
|
|
seq(field('class', $._pattern_constant), token.immediate('('), $._find_pattern_body, ')')
|
|
),
|
|
|
|
_hash_pattern_body: $ => choice(
|
|
seq(commaSep1($.keyword_pattern), optional(',')),
|
|
seq(commaSep1($.keyword_pattern), ',', $._hash_pattern_any_rest),
|
|
$._hash_pattern_any_rest
|
|
),
|
|
|
|
keyword_pattern: $ => seq(
|
|
field('key',
|
|
choice(
|
|
alias($.identifier, $.hash_key_symbol),
|
|
alias($.constant, $.hash_key_symbol),
|
|
alias($.identifier_suffix, $.hash_key_symbol),
|
|
alias($.constant_suffix, $.hash_key_symbol),
|
|
$.string
|
|
)
|
|
),
|
|
token.immediate(':'),
|
|
optional(field('value', $._pattern_expr))
|
|
),
|
|
|
|
_hash_pattern_any_rest: $ => choice($.hash_splat_parameter, $.hash_splat_nil),
|
|
|
|
hash_pattern: $ => choice(
|
|
seq('{', optional($._hash_pattern_body), '}'),
|
|
seq(field('class', $._pattern_constant), token.immediate('['), $._hash_pattern_body, ']'),
|
|
seq(field('class', $._pattern_constant), token.immediate('('), $._hash_pattern_body, ')')
|
|
),
|
|
|
|
_pattern_expr_basic: $ => choice(
|
|
$._pattern_value,
|
|
$.identifier,
|
|
$.array_pattern,
|
|
$.find_pattern,
|
|
$.hash_pattern,
|
|
$.parenthesized_pattern,
|
|
),
|
|
|
|
parenthesized_pattern: $ => seq('(', $._pattern_expr, ')'),
|
|
|
|
_pattern_value: $ => choice(
|
|
$._pattern_primitive,
|
|
alias($._pattern_range, $.range),
|
|
$.variable_reference_pattern,
|
|
$.expression_reference_pattern,
|
|
$._pattern_constant
|
|
),
|
|
|
|
_pattern_range: $ => {
|
|
const begin = field('begin', $._pattern_primitive);
|
|
const end = field('end', $._pattern_primitive);
|
|
const operator = field('operator', choice('..', '...'));
|
|
return choice(
|
|
seq(begin, operator, end),
|
|
seq(operator, end),
|
|
seq(begin, operator)
|
|
);
|
|
},
|
|
|
|
_pattern_primitive: $ => choice(
|
|
$._pattern_literal,
|
|
$._pattern_lambda
|
|
),
|
|
|
|
_pattern_lambda: $ => $.lambda,
|
|
|
|
_pattern_literal: $ => choice(
|
|
$._literal,
|
|
$.string,
|
|
$.subshell,
|
|
$.heredoc_beginning,
|
|
$.regex,
|
|
$.string_array,
|
|
$.symbol_array,
|
|
$._keyword_variable
|
|
),
|
|
|
|
_keyword_variable: $ => choice(
|
|
$.nil,
|
|
$.self,
|
|
$.true,
|
|
$.false,
|
|
$.line,
|
|
$.file,
|
|
$.encoding,
|
|
),
|
|
|
|
line: $ => '__LINE__',
|
|
file: $ => '__FILE__',
|
|
encoding: $ => '__ENCODING__',
|
|
|
|
variable_reference_pattern: $ => seq('^', field('name', choice($.identifier, $._nonlocal_variable))),
|
|
|
|
expression_reference_pattern: $ => seq('^', '(', field('value', $._expression), ')'),
|
|
|
|
_pattern_constant: $ => choice(
|
|
$.constant,
|
|
alias($._pattern_constant_resolution, $.scope_resolution)
|
|
),
|
|
|
|
_pattern_constant_resolution: $ => seq(
|
|
optional(field('scope', $._pattern_constant)),
|
|
'::',
|
|
field('name', $.constant)
|
|
),
|
|
|
|
if: $ => seq(
|
|
'if',
|
|
field('condition', $._statement),
|
|
choice($._terminator, field('consequence', $.then)),
|
|
field('alternative', optional(choice($.else, $.elsif))),
|
|
'end'
|
|
),
|
|
|
|
unless: $ => seq(
|
|
'unless',
|
|
field('condition', $._statement),
|
|
choice($._terminator, field('consequence', $.then)),
|
|
field('alternative', optional(choice($.else, $.elsif))),
|
|
'end'
|
|
),
|
|
|
|
elsif: $ => seq(
|
|
'elsif',
|
|
field('condition', $._statement),
|
|
choice($._terminator, field('consequence', $.then)),
|
|
field('alternative', optional(choice($.else, $.elsif)))
|
|
),
|
|
|
|
else: $ => seq(
|
|
'else',
|
|
optional($._terminator),
|
|
optional($._statements)
|
|
),
|
|
|
|
then: $ => choice(
|
|
seq(
|
|
$._terminator,
|
|
$._statements
|
|
),
|
|
seq(
|
|
optional($._terminator),
|
|
'then',
|
|
optional($._statements)
|
|
)
|
|
),
|
|
|
|
begin: $ => seq('begin', optional($._terminator), $._body_statement),
|
|
|
|
ensure: $ => seq('ensure', optional($._statements)),
|
|
|
|
rescue: $ => seq(
|
|
'rescue',
|
|
field('exceptions', optional($.exceptions)),
|
|
field('variable', optional($.exception_variable)),
|
|
choice(
|
|
$._terminator,
|
|
field('body', $.then)
|
|
)
|
|
),
|
|
|
|
exceptions: $ => commaSep1(choice($._arg, $.splat_argument)),
|
|
|
|
exception_variable: $ => seq('=>', $._lhs),
|
|
|
|
_body_statement: $ => seq(
|
|
optional($._statements),
|
|
repeat(choice($.rescue, $.else, $.ensure)),
|
|
'end'
|
|
),
|
|
|
|
// Method calls without parentheses (aka "command calls") are only allowed
|
|
// in certain positions, like the top-level of a statement, the condition
|
|
// of a postfix control-flow operator like `if`, or as the value of a
|
|
// control-flow statement like `return`. In many other places, they're not
|
|
// allowed.
|
|
//
|
|
// Because of this distinction, a lot of rules have two variants: the
|
|
// normal variant, which can appear anywhere that an expression is valid,
|
|
// and the "command" varaint, which is only valid in a more limited set of
|
|
// positions, because it can contain "command calls".
|
|
//
|
|
// The `_expression` rule can appear in relatively few places, but can
|
|
// contain command calls. The `_arg` rule can appear in many more places,
|
|
// but cannot contain command calls (unless they are wrapped in parens).
|
|
// This naming convention is based on Ruby's standard grammar.
|
|
_expression: $ => choice(
|
|
alias($.command_binary, $.binary),
|
|
alias($.command_unary, $.unary),
|
|
alias($.command_assignment, $.assignment),
|
|
alias($.command_operator_assignment, $.operator_assignment),
|
|
alias($.command_call, $.call),
|
|
alias($.command_call_with_block, $.call),
|
|
prec.left(alias($._chained_command_call, $.call)),
|
|
alias($.return_command, $.return),
|
|
alias($.yield_command, $.yield),
|
|
alias($.break_command, $.break),
|
|
alias($.next_command, $.next),
|
|
$._arg
|
|
),
|
|
|
|
_arg: $ => choice(
|
|
alias($._unary_minus_pow, $.unary),
|
|
$._primary,
|
|
$.assignment,
|
|
$.operator_assignment,
|
|
$.conditional,
|
|
$.range,
|
|
$.binary,
|
|
$.unary,
|
|
),
|
|
|
|
_unary_minus_pow: $ => seq(field('operator', alias($._unary_minus_num, '-')), field('operand', alias($._pow, $.binary))),
|
|
_pow: $ => prec.right(PREC.EXPONENTIAL, seq(field('left', $._simple_numeric), field('operator', alias($._binary_star_star, '**')), field('right', $._arg, $.binary))),
|
|
|
|
_primary: $ => choice(
|
|
$.parenthesized_statements,
|
|
$._lhs,
|
|
alias($._function_identifier_call, $.call),
|
|
$.call,
|
|
$.array,
|
|
$.string_array,
|
|
$.symbol_array,
|
|
$.hash,
|
|
$.subshell,
|
|
$._literal,
|
|
$.string,
|
|
$.character,
|
|
$.chained_string,
|
|
$.regex,
|
|
$.lambda,
|
|
$.method,
|
|
$.singleton_method,
|
|
$.class,
|
|
$.singleton_class,
|
|
$.module,
|
|
$.begin,
|
|
$.while,
|
|
$.until,
|
|
$.if,
|
|
$.unless,
|
|
$.for,
|
|
$.case,
|
|
$.case_match,
|
|
$.return,
|
|
$.yield,
|
|
$.break,
|
|
$.next,
|
|
$.redo,
|
|
$.retry,
|
|
alias($.parenthesized_unary, $.unary),
|
|
$.heredoc_beginning
|
|
),
|
|
|
|
parenthesized_statements: $ => seq('(', optional($._statements), ')'),
|
|
|
|
element_reference: $ => prec.left(1, seq(
|
|
field('object', $._primary),
|
|
alias($._element_reference_bracket, '['),
|
|
optional($._argument_list_with_trailing_comma),
|
|
']'
|
|
)),
|
|
|
|
scope_resolution: $ => prec.left(PREC.CALL + 1, seq(
|
|
choice(
|
|
'::',
|
|
seq(field('scope', $._primary), token.immediate('::'))
|
|
),
|
|
field('name', $.constant)
|
|
)),
|
|
|
|
_call_operator: $ => choice('.', '&.', token.immediate('::')),
|
|
_call: $ => prec.left(PREC.CALL, seq(
|
|
field('receiver', $._primary),
|
|
field('operator', $._call_operator),
|
|
field('method', choice($.identifier, $.operator, $.constant, $._function_identifier)),
|
|
)),
|
|
|
|
command_call: $ => seq(
|
|
choice(
|
|
$._call,
|
|
$._chained_command_call,
|
|
field('method', choice(
|
|
$._variable,
|
|
$._function_identifier
|
|
)),
|
|
),
|
|
field('arguments', alias($.command_argument_list, $.argument_list))
|
|
),
|
|
|
|
command_call_with_block: $ => {
|
|
const receiver = choice(
|
|
$._call,
|
|
field('method', choice($._variable, $._function_identifier))
|
|
)
|
|
const arguments = field('arguments', alias($.command_argument_list, $.argument_list))
|
|
const block = field('block', $.block)
|
|
const doBlock = field('block', $.do_block)
|
|
return choice(
|
|
seq(receiver, prec(PREC.CURLY_BLOCK, seq(arguments, block))),
|
|
seq(receiver, prec(PREC.DO_BLOCK, seq(arguments, doBlock))),
|
|
)
|
|
},
|
|
|
|
_chained_command_call: $ => seq(
|
|
field('receiver', alias($.command_call_with_block, $.call)),
|
|
field('operator', $._call_operator),
|
|
field('method', choice($.identifier, $._function_identifier, $.operator, $.constant)),
|
|
),
|
|
|
|
call: $ => {
|
|
const receiver = choice(
|
|
$._call,
|
|
field('method', choice(
|
|
$._variable, $._function_identifier
|
|
))
|
|
)
|
|
|
|
const arguments = field('arguments', $.argument_list)
|
|
const receiver_arguments =
|
|
seq(
|
|
choice(
|
|
receiver,
|
|
prec.left(PREC.CALL, seq(
|
|
field('receiver', $._primary),
|
|
field('operator', $._call_operator)
|
|
))
|
|
),
|
|
arguments
|
|
)
|
|
|
|
const block = field('block', $.block)
|
|
const doBlock = field('block', $.do_block)
|
|
return choice(
|
|
receiver_arguments,
|
|
prec(PREC.CURLY_BLOCK, seq(receiver_arguments, block)),
|
|
prec(PREC.DO_BLOCK, seq(receiver_arguments, doBlock)),
|
|
prec(PREC.CURLY_BLOCK, seq(receiver, block)),
|
|
prec(PREC.DO_BLOCK, seq(receiver, doBlock))
|
|
)
|
|
},
|
|
|
|
command_argument_list: $ => prec.right(commaSep1($._argument)),
|
|
|
|
argument_list: $ => prec.right(seq(
|
|
token.immediate('('),
|
|
optional($._argument_list_with_trailing_comma),
|
|
')'
|
|
)),
|
|
|
|
_argument_list_with_trailing_comma: $ => prec.right(seq(
|
|
commaSep1($._argument),
|
|
optional(',')
|
|
)),
|
|
|
|
_argument: $ => prec.left(choice(
|
|
$._expression,
|
|
$.splat_argument,
|
|
$.hash_splat_argument,
|
|
$.forward_argument,
|
|
$.block_argument,
|
|
$.pair
|
|
)),
|
|
|
|
forward_argument: $ => '...',
|
|
splat_argument: $ => seq(alias($._splat_star, '*'), $._arg),
|
|
hash_splat_argument: $ => seq(alias($._hash_splat_star_star, '**'), $._arg),
|
|
block_argument: $ => prec.right(seq(alias($._block_ampersand, '&'), optional($._arg))),
|
|
|
|
do_block: $ => seq(
|
|
'do',
|
|
optional($._terminator),
|
|
optional(seq(
|
|
field('parameters', $.block_parameters),
|
|
optional($._terminator)
|
|
)),
|
|
$._body_statement
|
|
),
|
|
|
|
block: $ => prec(PREC.CURLY_BLOCK, seq(
|
|
'{',
|
|
field('parameters', optional($.block_parameters)),
|
|
optional($._statements),
|
|
'}'
|
|
)),
|
|
|
|
_arg_rhs: $ => choice($._arg, alias($.rescue_modifier_arg, $.rescue_modifier)),
|
|
assignment: $ => prec.right(PREC.ASSIGN, choice(
|
|
seq(
|
|
field('left', choice($._lhs, $.left_assignment_list)),
|
|
'=',
|
|
field('right', choice(
|
|
$._arg_rhs,
|
|
$.splat_argument,
|
|
$.right_assignment_list
|
|
))
|
|
)
|
|
)),
|
|
|
|
command_assignment: $ => prec.right(PREC.ASSIGN,
|
|
seq(
|
|
field('left', choice($._lhs, $.left_assignment_list)),
|
|
'=',
|
|
field('right', choice($._expression, alias($.rescue_modifier_expression, $.rescue_modifier)))
|
|
)
|
|
),
|
|
|
|
operator_assignment: $ => prec.right(PREC.ASSIGN, seq(
|
|
field('left', $._lhs),
|
|
field('operator', choice('+=', '-=', '*=', '**=', '/=', '||=', '|=', '&&=', '&=', '%=', '>>=', '<<=', '^=')),
|
|
field('right', $._arg_rhs)
|
|
)),
|
|
|
|
command_operator_assignment: $ => prec.right(PREC.ASSIGN, seq(
|
|
field('left', $._lhs),
|
|
field('operator', choice('+=', '-=', '*=', '**=', '/=', '||=', '|=', '&&=', '&=', '%=', '>>=', '<<=', '^=')),
|
|
field('right', choice($._expression, alias($.rescue_modifier_expression, $.rescue_modifier)))
|
|
)),
|
|
|
|
conditional: $ => prec.right(PREC.CONDITIONAL, seq(
|
|
field('condition', $._arg),
|
|
'?',
|
|
field('consequence', $._arg),
|
|
':',
|
|
field('alternative', $._arg)
|
|
)),
|
|
|
|
range: $ => {
|
|
const begin = field('begin', $._arg);
|
|
const end = field('end', $._arg);
|
|
const operator = field('operator', choice('..', '...'));
|
|
return prec.right(PREC.RANGE, choice(
|
|
seq(begin, operator, end),
|
|
seq(operator, end),
|
|
seq(begin, operator)
|
|
));
|
|
},
|
|
|
|
binary: $ => {
|
|
const operators = [
|
|
[prec.left, PREC.AND, 'and'],
|
|
[prec.left, PREC.OR, 'or'],
|
|
[prec.left, PREC.BOOLEAN_OR, '||'],
|
|
[prec.left, PREC.BOOLEAN_AND, '&&'],
|
|
[prec.left, PREC.SHIFT, choice('<<', '>>')],
|
|
[prec.left, PREC.COMPARISON, choice('<', '<=', '>', '>=')],
|
|
[prec.left, PREC.BITWISE_AND, '&'],
|
|
[prec.left, PREC.BITWISE_OR, choice('^', '|')],
|
|
[prec.left, PREC.ADDITIVE, choice('+', alias($._binary_minus, '-'))],
|
|
[prec.left, PREC.MULTIPLICATIVE, choice('/', '%', alias($._binary_star, '*'))],
|
|
[prec.right, PREC.RELATIONAL, choice('==', '!=', '===', '<=>', '=~', '!~')],
|
|
[prec.right, PREC.EXPONENTIAL, alias($._binary_star_star, '**')],
|
|
];
|
|
|
|
return choice(...operators.map(([fn, precedence, operator]) => fn(precedence, seq(
|
|
field('left', $._arg),
|
|
field('operator', operator),
|
|
field('right', $._arg)
|
|
))));
|
|
},
|
|
|
|
command_binary: $ => prec.left(seq(
|
|
field('left', $._expression),
|
|
field('operator', choice('or', 'and')),
|
|
field('right', $._expression)
|
|
)),
|
|
|
|
unary: $ => {
|
|
const operators = [
|
|
[prec, PREC.DEFINED, 'defined?'],
|
|
[prec.right, PREC.NOT, 'not'],
|
|
[prec.right, PREC.UNARY_MINUS, choice(alias($._unary_minus, '-'), alias($._binary_minus, '-'), '+')],
|
|
[prec.right, PREC.COMPLEMENT, choice('!', '~')]
|
|
];
|
|
return choice(...operators.map(([fn, precedence, operator]) => fn(precedence, seq(
|
|
field('operator', operator),
|
|
field('operand', $._arg)
|
|
))));
|
|
},
|
|
|
|
command_unary: $ => {
|
|
const operators = [
|
|
[prec, PREC.DEFINED, 'defined?'],
|
|
[prec.right, PREC.NOT, 'not'],
|
|
[prec.right, PREC.UNARY_MINUS, choice(alias($._unary_minus, '-'), '+')],
|
|
[prec.right, PREC.COMPLEMENT, choice('!', '~')]
|
|
];
|
|
return choice(...operators.map(([fn, precedence, operator]) => fn(precedence, seq(
|
|
field('operator', operator),
|
|
field('operand', $._expression)
|
|
))));
|
|
},
|
|
|
|
parenthesized_unary: $ => prec(PREC.CALL, seq(
|
|
field('operator', choice('defined?', 'not')),
|
|
field('operand', $.parenthesized_statements)
|
|
)),
|
|
|
|
unary_literal: $ => prec.right(PREC.UNARY_MINUS, seq(
|
|
field('operator', choice(alias($._unary_minus_num, '-'), '+')),
|
|
field('operand', $._simple_numeric)
|
|
)),
|
|
|
|
_literal: $ => choice(
|
|
$.simple_symbol,
|
|
$.delimited_symbol,
|
|
$._numeric
|
|
),
|
|
|
|
_numeric: $ => choice(
|
|
$._simple_numeric,
|
|
alias($.unary_literal, $.unary)
|
|
),
|
|
|
|
_simple_numeric: $ =>
|
|
choice(
|
|
$.integer,
|
|
$.float,
|
|
$.complex,
|
|
$.rational
|
|
),
|
|
|
|
right_assignment_list: $ => prec(-1, commaSep1(choice($._arg, $.splat_argument))),
|
|
|
|
left_assignment_list: $ => $._mlhs,
|
|
_mlhs: $ => prec.left(-1, seq(
|
|
commaSep1(choice($._lhs, $.rest_assignment, $.destructured_left_assignment)),
|
|
optional(',')
|
|
)),
|
|
destructured_left_assignment: $ => prec(-1, seq('(', $._mlhs, ')')),
|
|
|
|
rest_assignment: $ => prec(-1, seq('*', optional($._lhs))),
|
|
|
|
_function_identifier: $ => choice(alias($.identifier_suffix, $.identifier), alias($.constant_suffix, $.constant)),
|
|
_function_identifier_call: $ => prec.left(field('method', $._function_identifier)),
|
|
_lhs: $ => prec.left(choice(
|
|
$._variable,
|
|
$.true,
|
|
$.false,
|
|
$.nil,
|
|
$.scope_resolution,
|
|
$.element_reference,
|
|
alias($._call, $.call),
|
|
)),
|
|
|
|
_variable: $ => prec.right(choice(
|
|
$.self,
|
|
$.super,
|
|
$._nonlocal_variable,
|
|
$.identifier,
|
|
$.constant
|
|
)),
|
|
|
|
operator: $ => choice(
|
|
'..', '|', '^', '&', '<=>', '==', '===', '=~', '>', '>=', '<', '<=', '+',
|
|
'-', '*', '/', '%', '!', '!~', '**', '<<', '>>', '~', '+@', '-@', '[]', '[]=', '`'
|
|
),
|
|
|
|
_method_name: $ => choice(
|
|
$.identifier,
|
|
$._function_identifier,
|
|
$.constant,
|
|
$.setter,
|
|
$.simple_symbol,
|
|
$.delimited_symbol,
|
|
$.operator,
|
|
$._nonlocal_variable
|
|
),
|
|
|
|
_nonlocal_variable: $ => choice(
|
|
$.instance_variable,
|
|
$.class_variable,
|
|
$.global_variable
|
|
),
|
|
|
|
setter: $ => seq(field('name', $.identifier), token.immediate('=')),
|
|
|
|
undef: $ => seq('undef', commaSep1($._method_name)),
|
|
alias: $ => seq(
|
|
'alias',
|
|
field('name', $._method_name),
|
|
field('alias', $._method_name)
|
|
),
|
|
|
|
comment: $ => token(prec(PREC.COMMENT, choice(
|
|
seq('#', /.*/),
|
|
seq(
|
|
/=begin.*\r?\n/,
|
|
repeat(choice(
|
|
/[^=]/,
|
|
/=[^e]/,
|
|
/=e[^n]/,
|
|
/=en[^d]/
|
|
)),
|
|
/=end.*/
|
|
)
|
|
))),
|
|
|
|
integer: $ => /0[bB][01](_?[01])*|0[oO]?[0-7](_?[0-7])*|(0[dD])?\d(_?\d)*|0[xX][0-9a-fA-F](_?[0-9a-fA-F])*/,
|
|
_int_or_float: $ => choice($.integer, $.float),
|
|
float: $ => /\d(_?\d)*(\.\d)?(_?\d)*([eE][\+-]?\d(_?\d)*)?/,
|
|
complex: $ => choice(
|
|
seq($._int_or_float, token.immediate('i')),
|
|
seq(alias($._int_or_float, $.rational), token.immediate('ri')),
|
|
),
|
|
rational: $ => seq($._int_or_float, token.immediate('r')),
|
|
super: $ => 'super',
|
|
self: $ => 'self',
|
|
true: $ => 'true',
|
|
false: $ => 'false',
|
|
nil: $ => 'nil',
|
|
|
|
constant: $ => token(seq(/[A-Z]/, IDENTIFIER_CHARS)),
|
|
constant_suffix: $ => choice(token(seq(/[A-Z]/, IDENTIFIER_CHARS, /[?]/)), $._constant_suffix),
|
|
identifier: $ => token(seq(LOWER_ALPHA_CHAR, IDENTIFIER_CHARS)),
|
|
identifier_suffix: $ => choice(token(seq(LOWER_ALPHA_CHAR, IDENTIFIER_CHARS, /[?]/)), $._identifier_suffix),
|
|
instance_variable: $ => token(seq('@', ALPHA_CHAR, IDENTIFIER_CHARS)),
|
|
class_variable: $ => token(seq('@@', ALPHA_CHAR, IDENTIFIER_CHARS)),
|
|
|
|
global_variable: $ => /\$-?(([!@&`'+~=/\\,;.<>*$?:"])|([0-9]*)|([a-zA-Z_][a-zA-Z0-9_]*))/,
|
|
|
|
chained_string: $ => seq($.string, repeat1($.string)),
|
|
|
|
character: $ => /\?(\\\S({[0-9A-Fa-f]*}|[0-9A-Fa-f]*|-\S([MC]-\S)?)?|\S)/,
|
|
|
|
interpolation: $ => seq(
|
|
'#{', optional($._statements), '}'
|
|
),
|
|
|
|
string: $ => seq(
|
|
alias($._string_start, '"'),
|
|
optional($._literal_contents),
|
|
alias($._string_end, '"')
|
|
),
|
|
|
|
subshell: $ => seq(
|
|
alias($._subshell_start, '`'),
|
|
optional($._literal_contents),
|
|
alias($._string_end, '`')
|
|
),
|
|
|
|
string_array: $ => seq(
|
|
alias($._string_array_start, '%w('),
|
|
optional(/\s+/),
|
|
sep(alias($._literal_contents, $.bare_string), /\s+/),
|
|
optional(/\s+/),
|
|
alias($._string_end, ')')
|
|
),
|
|
|
|
symbol_array: $ => seq(
|
|
alias($._symbol_array_start, '%i('),
|
|
optional(/\s+/),
|
|
sep(alias($._literal_contents, $.bare_symbol), /\s+/),
|
|
optional(/\s+/),
|
|
alias($._string_end, ')')
|
|
),
|
|
|
|
delimited_symbol: $ => seq(
|
|
alias($._symbol_start, ':"'),
|
|
optional($._literal_contents),
|
|
alias($._string_end, '"')
|
|
),
|
|
|
|
regex: $ => seq(
|
|
alias($._regex_start, '/'),
|
|
optional($._literal_contents),
|
|
alias($._string_end, '/')
|
|
),
|
|
|
|
heredoc_body: $ => seq(
|
|
$._heredoc_body_start,
|
|
repeat(choice(
|
|
$.heredoc_content,
|
|
$.interpolation,
|
|
$.escape_sequence
|
|
)),
|
|
$.heredoc_end
|
|
),
|
|
|
|
_literal_contents: $ => repeat1(choice(
|
|
$.string_content,
|
|
$.interpolation,
|
|
$.escape_sequence
|
|
)),
|
|
|
|
// https://ruby-doc.org/core-2.5.0/doc/syntax/literals_rdoc.html#label-Strings
|
|
escape_sequence: $ => token(seq(
|
|
'\\',
|
|
choice(
|
|
/[^ux0-7]/, // single character
|
|
/x[0-9a-fA-F]{1,2}/, // hex code
|
|
/[0-7]{1,3}/, // octal
|
|
/u[0-9a-fA-F]{4}/, // single unicode
|
|
/u{[0-9a-fA-F ]+}/, // multiple unicode
|
|
)
|
|
)),
|
|
|
|
array: $ => seq(
|
|
'[',
|
|
optional($._argument_list_with_trailing_comma),
|
|
']'
|
|
),
|
|
|
|
hash: $ => seq(
|
|
'{',
|
|
optional(seq(
|
|
commaSep1(choice($.pair, $.hash_splat_argument)),
|
|
optional(',')
|
|
)),
|
|
'}'
|
|
),
|
|
|
|
pair: $ => prec.right(choice(
|
|
seq(
|
|
field('key', $._arg),
|
|
'=>',
|
|
field('value', $._arg)
|
|
),
|
|
seq(
|
|
field('key', choice(
|
|
$.string
|
|
)),
|
|
token.immediate(':'),
|
|
field('value', $._arg)
|
|
),
|
|
seq(
|
|
field('key', choice(
|
|
$.hash_key_symbol,
|
|
alias($.identifier, $.hash_key_symbol),
|
|
alias($.constant, $.hash_key_symbol),
|
|
alias($.identifier_suffix, $.hash_key_symbol),
|
|
alias($.constant_suffix, $.hash_key_symbol),
|
|
)),
|
|
token.immediate(':'),
|
|
choice(
|
|
field('value', optional($._arg)),
|
|
// This alternative never matches, because '_no_line_break' tokens do not exist.
|
|
// The purpose is give a hint to the scanner that it should not produce any line-break
|
|
// terminators at this point.
|
|
$._no_line_break)
|
|
)
|
|
)),
|
|
|
|
lambda: $ => seq(
|
|
'->',
|
|
field('parameters', optional(choice(
|
|
alias($.parameters, $.lambda_parameters),
|
|
alias($.bare_parameters, $.lambda_parameters)
|
|
))),
|
|
field('body', choice($.block, $.do_block))
|
|
),
|
|
|
|
empty_statement: $ => prec(-1, ';'),
|
|
|
|
_terminator: $ => choice(
|
|
$._line_break,
|
|
';'
|
|
),
|
|
}
|
|
});
|
|
|
|
function sep(rule, separator) {
|
|
return optional(sep1(rule, separator));
|
|
}
|
|
|
|
function sep1(rule, separator) {
|
|
return seq(rule, repeat(seq(separator, rule)));
|
|
}
|
|
|
|
function commaSep1(rule) {
|
|
return sep1(rule, ',');
|
|
}
|
|
|
|
function commaSep(rule) {
|
|
return optional(commaSep1(rule));
|
|
}
|