const primitives = require('./lib/primitives.js')
const unicode_ranges = require('./lib/unicode_ranges')
const TERMPREC = {
LOW: 0,
LOOPEX: 1,
OROP: 2,
ANDOP: 3,
NOTOP: 4,
LSTOP: 5,
COMMA: 6,
ASSIGNOP: 7,
QUESTION_MARK: 8,
DOTDOT: 9,
OROR: 10,
ANDAND: 11,
BITOROP: 12,
BITANDOP: 13,
CHEQOP: 14,
CHRELOP: 15,
UNOP: 16,
REQUIRE: 17,
SHIFTOP: 18,
ADDOP: 19,
MULOP: 20,
MATCHOP: 21,
UMINUS: 22,
POWOP: 23,
PREINC: 24,
POSTINC: 24,
ARROW: 25,
PAREN: 26,
}
const unop_pre = (op, term) =>
seq(field('operator', op), field('operand', term))
const unop_post = (op, term) =>
seq(field('operand', term), field('operator', op))
const binop = (op, term) =>
seq(field('left', term), field('operator', op), field('right', term))
binop.nonassoc = ($, op, term) =>
seq(
field('left', term),
field('operator', op),
field('right', term),
optseq(
field('operator', op),
$._NONASSOC,
$._ERROR
)
)
const stringContent = ($, node) =>
field('content', alias(node, $.string_content))
const regexpContent = ($, node) =>
field('content', alias(node, $.regexp_content))
const replacement = ($, node) =>
alias(node, $.replacement)
const trContent = ($, node) =>
field('content', alias(node, $.transliteration_content))
const aliasMany = (to, tokens) => tokens.map(t => alias(t, to))
const recoverParen = ($) => choice(')', alias($._RECOVER_PAREN_CLOSE, ')'))
const recoverBracket = ($) => choice(']', alias($._RECOVER_BRACKET_CLOSE, ']'))
const recoverBrace = ($) => choice('}', alias($._RECOVER_BRACE_CLOSE, '}'))
const subExtensions = () => repeat(choice('extended', 'async'))
const optseq = (...terms) => optional(seq(...terms))
const paren_list_of = rule =>
seq('(', repeat(seq(optional(rule), ',')), optional(rule), ')')
module.exports = grammar({
name: 'perl',
supertypes: $ => [
$.primitive,
$.postfix_deref,
$.subscripted,
$.slices,
],
word: $ => $._identifier,
inline: $ => [
$._var_indirob,
$._semicolon,
$._fullstmt,
$._else,
$._conditionals,
$._quotelike_end,
$._quotelike_begin,
$._declared_vars,
$._interpolations,
$._nonvar_interpolation_fallbacks,
$._apostrophe,
$._brace_autoquoted,
$._version,
$._loops,
$._func0op,
$._func1op,
$._map_grep,
$._PERLY_COMMA,
$._KW_USE,
$._KW_FOR,
$._LOOPEX,
$._PHASE_NAME,
$._HASH_PERCENT,
$._bareword,
$._unambiguous_function,
],
externals: $ => [
$._single_quote,
$._double_quote,
$._backtick_quote,
$._search_slash_quote,
$._no_search_slash_plz,
$._open_readline_bracket,
$._open_fileglob_bracket,
$._PERLY_SEMICOLON,
$._PERLY_HEREDOC,
$._ctrl_z_hack,
$._quotelike_begin_quote,
$._quotelike_middle_close_quote,
$._quotelike_middle_skip,
$._quotelike_end_zw,
$._quotelike_end_quote,
$._q_string_content,
$._qq_string_content,
$.escape_sequence,
$.escaped_delimiter,
$._dollar_in_regexp,
$._regexp_open_bracket,
$._regexp_open_brace,
$.pod,
$._gobbled_content,
$._attribute_value_begin,
$.attribute_value,
$.prototype,
$._signature_start,
$._heredoc_delimiter,
$._command_heredoc_delimiter,
$._heredoc_start,
$._heredoc_middle,
$.heredoc_end,
$._fat_comma_autoquoted,
$._filetest,
$._brace_autoquoted_token,
$._brace_end_zw,
$._dollar_ident_zw,
$._no_interp_whitespace_zw,
$._NONASSOC,
$._RECOVER_PAREN_CLOSE,
$._RECOVER_BRACKET_CLOSE,
$._RECOVER_BRACE_CLOSE,
$._RECOVER_ARROW,
$._x_op,
$._ERROR
],
extras: $ => [
/\p{White_Space}/,
$.comment,
$.pod,
$.heredoc_content,
],
conflicts: $ => [
[$.preinc_expression, $.postinc_expression],
[$._listexpr, $._term_rightward],
[$.return_expression],
[$.function, $.bareword],
[$.function, $.function_call_expression],
[$._variables, $.indirect_object],
[$._term, $.indirect_object],
[$.expression_statement, $._tricky_indirob_hashref],
[$.autoquoted_bareword],
[$.optional_parameter],
[$._loop_body],
[$._interp_arrow, $._interpolation_fallbacks]
],
rules: {
source_file: $ => seq(repeat($._fullstmt), optional($.__DATA__)),
_PERLY_BRACE_OPEN: $ => '{',
block: $ => seq($._PERLY_BRACE_OPEN, repeat($._fullstmt), '}'),
_fullstmt: $ => choice($._barestmt, $.statement_label),
statement_label: $ => seq(field('label', $.identifier), ':', field('statement', $._fullstmt)),
_semicolon: $ => choice(';', $._PERLY_SEMICOLON),
_barestmt: $ => choice(
$.package_statement,
$.class_statement,
$.role_statement,
$.class_phaser_statement,
$.use_version_statement,
$.use_statement,
$.subroutine_declaration_statement,
$.method_declaration_statement,
$.phaser_statement,
$.conditional_statement,
$.loop_statement,
$.cstyle_for_statement,
$.for_statement,
$.try_statement,
alias($.block, $.block_statement),
seq($.expression_statement, choice($._semicolon, $.__DATA__)),
$.defer_statement,
';', ),
package_statement: $ => choice(
seq('package', field('name', $.package), optional(field('version', $._version)), $._semicolon),
seq('package', field('name', $.package), optional(field('version', $._version)), $.block),
),
class_statement: $ => choice(
seq('class',
field('name', $.package),
optional(field('version', $._version)),
optseq(':', optional(field('attributes', $.attrlist))),
$._semicolon),
seq('class',
field('name', $.package),
optional(field('version', $._version)),
optseq(':', optional(field('attributes', $.attrlist))),
$.block),
),
role_statement: $ => choice(
seq('role',
field('name', $.package),
optional(field('version', $._version)),
optseq(':', optional(field('attributes', $.attrlist))),
$._semicolon),
seq('role',
field('name', $.package),
optional(field('version', $._version)),
optseq(':', optional(field('attributes', $.attrlist))),
$.block),
),
class_phaser_statement: $ => seq(
field('phase', choice('BUILD', 'ADJUST')),
optseq(':', optional(field('attributes', $.attrlist))),
optional($.signature),
$.block
),
use_version_statement: $ => seq($._KW_USE, field('version', $._version), $._semicolon),
use_statement: $ => seq(
$._KW_USE,
field('module', $.package),
optional(field('version', $._version)),
optional($._listexpr),
$._semicolon
),
mandatory_parameter: $ => alias(choice('$', $._signature_scalar), $.scalar),
optional_parameter: $ => choice(
seq(
alias($._signature_scalar, $.scalar),
choice('=', '||=', '//='),
field('default', $._term),
),
seq(
alias('$', $.scalar),
choice('=', '||=', '//='),
field('default', optional($._term))
)
),
named_parameter: $ => seq(
':',
alias($._signature_scalar, $.scalar),
optseq(
choice('=', '||=', '//='),
field('default', $._term),
)
),
slurpy_parameter: $ => choice(
alias(choice('@', $._signature_array), $.array),
alias(choice($._HASH_PERCENT, $._signature_hash), $.hash)
),
_signature_vars: $ => choice(
$.mandatory_parameter,
$.optional_parameter,
$.slurpy_parameter,
$.named_parameter,
),
signature: $ => seq(
alias($._signature_start, '('),
repeat(seq(
$._signature_vars,
optseq(',', optional($._signature_vars)))
),
')'
),
subroutine_declaration_statement: $ => seq(
optional(field('lexical', 'my')),
subExtensions(),
'sub',
field('name', $.bareword),
$._sub_decl_tail,
),
method_declaration_statement: $ => seq(
optional(field('lexical', 'my')),
subExtensions(),
'method',
field('name', $.bareword),
$._sub_decl_tail,
),
_sub_decl_tail: $ => seq(
optseq(':', optional(field('attributes', $.attrlist))),
optional(choice($.prototype, $.signature)),
choice(field('body', $.block), $._semicolon),
),
phaser_statement: $ => seq(field('phase', $._PHASE_NAME), $.block),
conditional_statement: $ =>
seq($._conditionals, '(', field('condition', $._expr), ')',
field('block', $.block),
optional($._else)
),
_loop_body: $ => seq(field('block', $.block), optseq('continue', field('continue', $.block))),
loop_statement: $ => seq($._loops, '(', field('condition', $._expr), ')', $._loop_body),
cstyle_for_statement: $ =>
seq($._KW_FOR,
'(',
field('initialiser', $._barestmt),
field('condition', $._barestmt),
field('iterator', optional($._expr)),
')',
$._loop_body
),
_for_initializer: $ => choice(
seq(optional(choice('my', 'state', 'our')), field('variable', $.scalar)),
seq(optional(choice('my', 'state', 'our')), field('variable', $.refalias_variable)),
seq('my', field('variables', paren_list_of($.scalar))),
),
for_statement: $ =>
seq($._KW_FOR,
optional($._for_initializer),
'(', field('list', $._expr), ')',
$._loop_body
),
try_statement: $ => seq(
'try',
field('try_block', $.block),
optseq('catch', optseq('(', field('catch_expr', $._expr), ')'),
field('catch_block', $.block)),
optseq('finally',
field('finally_block', $.block)),
),
defer_statement: $ => seq(
'defer',
field('block', $.block),
),
expression_statement: $ => choice(
$._expr,
$._postfix_expressions,
$.yadayada,
),
_postfix_expressions: $ => choice(
$.postfix_conditional_expression,
$.postfix_loop_expression,
$.postfix_for_expression,
),
postfix_conditional_expression: $ => seq($._expr, $._conditionals, field('condition', $._expr)),
postfix_loop_expression: $ => seq($._expr, $._loops, field('condition', $._expr)),
postfix_for_expression: $ => seq($._expr, $._KW_FOR, field('list', $._expr)),
yadayada: $ => '...',
_else: $ => choice($.else, $.elsif),
else: $ => seq('else', field('block', $.block)),
elsif: $ =>
seq('elsif', '(', field('condition', $._expr), ')',
field('block', $.block),
optional($._else)
),
_expr: $ => choice($.lowprec_logical_expression, $._listexpr),
lowprec_logical_expression: $ => choice(
prec.left(TERMPREC.ANDOP, binop('and', $._expr)),
prec.left(TERMPREC.OROP, binop(choice('or', 'xor'), $._expr)),
),
_listexpr: $ => choice(
alias($._term_rightward, $.list_expression),
$._term
),
_term_rightward: $ => prec.right(seq(
$._term, repeat1(seq($._PERLY_COMMA, optional($._term))),
)),
subscripted: $ => choice(
$.glob_slot_expression,
$.array_element_expression,
$.hash_element_expression,
$.coderef_call_expression,
$.anonymous_slice_expression,
),
container_variable: $ => prec(2, seq('$', $._var_indirob)),
_glob_slot_subscript: $ => seq('{', $._hash_key, '}'),
glob_slot_expression: $ => choice(
seq($.glob, $._glob_slot_subscript),
prec.left(TERMPREC.ARROW, seq($._term, '->', '*', $._glob_slot_subscript)),
),
_index_subscript: $ => seq('[', field('index', $._expr), recoverBracket($)),
_key_subscript: $ => seq('{', field('key', $._hash_key), recoverBrace($)),
_args_subscript: $ => seq('(', optional(field('arguments', $._expr)), recoverParen($)),
array_element_expression: $ => choice(
seq(field('array', $.container_variable), $._index_subscript),
prec.left(TERMPREC.ARROW, seq($._term, '->', $._index_subscript)),
seq($.subscripted, $._index_subscript),
),
_hash_key: $ => choice($._brace_autoquoted, $._expr),
hash_element_expression: $ => choice(
seq(field('hash', $.container_variable), $._key_subscript),
prec.left(TERMPREC.ARROW, seq($._term, '->', $._key_subscript)),
seq($.subscripted, $._key_subscript),
),
coderef_call_expression: $ => choice(
prec.left(TERMPREC.ARROW, seq($._term, '->', $._args_subscript)),
seq($.subscripted, $._args_subscript),
),
_anon_slice_subscript: $ => seq('[', $._expr, ']'),
anonymous_slice_expression: $ => choice(
seq('(', optional(field('list', $._expr)), ')', $._anon_slice_subscript),
seq(field('list', $.quoted_word_list), $._anon_slice_subscript),
),
slices: $ => choice(
$.slice_expression,
$.keyval_expression,
),
_slice_index_subscript: $ => seq('[', $._expr, recoverBracket($)),
_slice_key_subscript: $ => seq('{', $._hash_key, recoverBrace($)),
slice_container_variable: $ => seq('@', $._var_indirob),
slice_expression: $ => choice(
seq(field('array', $.slice_container_variable), $._slice_index_subscript),
seq(field('hash', $.slice_container_variable), $._slice_key_subscript),
prec.left(TERMPREC.ARROW,
seq(field('arrayref', $._term), '->', '@', $._slice_index_subscript)),
prec.left(TERMPREC.ARROW,
seq(field('hashref', $._term), '->', '@', $._slice_key_subscript)),
),
keyval_container_variable: $ => seq($._HASH_PERCENT, $._var_indirob),
keyval_expression: $ => choice(
seq(field('array', $.keyval_container_variable), $._slice_index_subscript),
seq(field('hash', $.keyval_container_variable), $._slice_key_subscript),
prec.left(TERMPREC.ARROW,
seq(field('arrayref', $._term), '->', '%', $._slice_index_subscript)),
prec.left(TERMPREC.ARROW,
seq(field('hashref', $._term), '->', '%', $._slice_key_subscript)),
),
_term: $ => choice(
$.readline_expression,
$.fileglob_expression,
$.assignment_expression,
$.binary_expression,
$.equality_expression,
$.relational_expression,
$.unary_expression,
$.preinc_expression,
$.postinc_expression,
$.anonymous_array_expression,
$.anonymous_hash_expression,
$.anonymous_subroutine_expression,
$.anonymous_method_expression,
$.do_expression,
$.eval_expression,
$.await_expression,
$.conditional_expression,
$.refgen_expression,
$.localization_expression,
seq('(', $._expr, ')'),
$.quoted_word_list,
$.heredoc_token,
$.command_heredoc_token,
$.stub_expression,
$._variables,
$.subscripted,
$.slices,
$.postfix_deref,
$.loopex_expression,
$.goto_expression,
$.return_expression,
$.undef_expression,
$.logical_not_expression,
$.require_expression,
$.require_version_expression,
$.func0op_call_expression,
$.func1op_call_expression,
$.map_grep_expression,
$.sort_expression,
alias($._builtin_filehandle, $.filehandle),
$.bareword,
alias(choice($._listop_keyword, $._indirob_listop), $.function),
$.autoquoted_bareword,
$._listop,
$.variable_declaration,
$.primitive,
$._literal,
),
readline_expression: $ => choice(
seq(
field('operator', alias($._open_readline_bracket, '<')),
optional(alias(choice($.scalar, $.bareword), $.filehandle)),
field('operator', '>')
),
field('operator', seq('<<', token.immediate('>>'))),
),
fileglob_expression: $ => seq(
field('operator', alias($._open_fileglob_bracket, '<')),
stringContent($, $._interpolated_string_content),
field('operator', alias($._quotelike_end, '>'))
),
assignment_expression: $ => prec.right(TERMPREC.ASSIGNOP,
binop(
choice( '=', token(prec(2, '**=')),
'+=', '-=', '.=',
token(prec(2, '*=')), '/=', token(prec(2, '%=')), 'x=',
token(prec(2, '&=')), '|=', '^=',
'<<=', '>>=',
token(prec(2, '&&=')), '||=', '//=',
),
$._term
)
),
binary_expression: $ => {
const table = [
[prec.right, binop.nonassoc, choice('..', '...'), TERMPREC.DOTDOT], [prec.right, binop, token(prec(2, '**')), TERMPREC.POWOP], [prec.left, binop, choice('||', '//', '^^'), TERMPREC.OROR], [prec.left, binop, token(prec(2, '&&')), TERMPREC.ANDAND], [prec.left, binop, choice('|', '^'), TERMPREC.BITOROP], [prec.left, binop, '&', TERMPREC.BITANDOP], [prec.left, binop, choice('<<', '>>'), TERMPREC.SHIFTOP], [prec.left, binop, choice('+', '-', '.'), TERMPREC.ADDOP], [prec.left, binop, choice('*', '/', '%', 'x', alias($._x_op, 'x')), TERMPREC.MULOP], [prec.left, binop, choice('=~', '!~'), TERMPREC.MATCHOP], ]
return choice(...table.map(([fn, fnop, operator, precedence]) => fn(
precedence,
fnop === binop ? fnop(operator, $._term) : fnop($, operator, $._term)
)))
},
equality_expression: $ =>
choice(
prec.left(TERMPREC.CHEQOP, binop(choice('==', '!=', 'eq', '===', 'equ', 'eqr', 'ne'), $._term)), prec.right(TERMPREC.CHEQOP, binop.nonassoc($, choice('<=>', 'cmp', '~~'), $._term)), ),
relational_expression: $ =>
choice(
prec.left(TERMPREC.CHRELOP, binop(choice('<', '<=', '>=', '>', 'lt', 'le', 'ge', 'gt'), $._term)), prec.right(TERMPREC.CHRELOP, binop.nonassoc($, 'isa', $._term)), )
,
unary_expression: $ => choice(
prec(TERMPREC.UMINUS, unop_pre('-', $._term)),
prec(TERMPREC.UMINUS, unop_pre('+', $._term)),
prec(TERMPREC.UMINUS, unop_pre('~', $._term)), prec(TERMPREC.UMINUS, unop_pre('!', $._term)),
),
logical_not_expression: $ =>
prec.right(TERMPREC.NOTOP, unop_pre('not', $._listexpr)),
preinc_expression: $ =>
prec(TERMPREC.PREINC, unop_pre(choice('++', '--'), $._term)),
postinc_expression: $ =>
prec(TERMPREC.POSTINC, unop_post(choice('++', '--'), $._term)),
conditional_expression: $ => prec.right(TERMPREC.QUESTION_MARK, seq(
field('condition', $._term), '?', field('consequent', $._term), ':', field('alternative', $._term)
)),
refgen_expression: $ => prec.left(TERMPREC.UMINUS, seq('\\', choice(alias($.amper_sub, $.function), $._term))),
anonymous_array_expression: $ => seq(
'[', optional($._expr), recoverBracket($)
),
_tricky_list: $ => prec(1, seq(
choice($.string_literal, $.interpolated_string_literal, $.command_string, $.autoquoted_bareword, $.number), $._PERLY_COMMA, $._listexpr
)),
anonymous_hash_expression: $ => choice(
seq($._PERLY_BRACE_OPEN, $._expr, '}'),
prec(1, seq($._PERLY_BRACE_OPEN, '}')),
seq($._PERLY_BRACE_OPEN, alias($._tricky_list, $.list_expression), '}'),
),
_anon_sub_tail: $ => seq(
optseq(':', optional(field('attributes', $.attrlist))),
optional(choice($.prototype, $.signature)),
field('body', $.block),
),
anonymous_subroutine_expression: $ => seq(
subExtensions(),
'sub',
$._anon_sub_tail,
),
anonymous_method_expression: $ => seq(
subExtensions(),
'method',
$._anon_sub_tail,
),
do_expression: $ => choice(seq('do', $.block)),
eval_expression: $ => prec(TERMPREC.UNOP,
choice(
seq('eval', choice($.block, $._term)),
seq('do', alias($._term, $.filename))
)
),
await_expression: $ => seq('await', $._term),
_declared_vars: $ => choice(
alias($._declare_scalar, $.scalar),
alias($._declare_array, $.array),
alias($._declare_hash, $.hash),
),
refalias_variable: $ => seq('\\', $._declared_vars),
variable_declaration: $ => prec.left(TERMPREC.QUESTION_MARK + 1,
seq(
choice('my', 'state', 'our', 'field'),
choice(
field('variable', $._declared_vars),
field('variable', $.refalias_variable),
field('variables', $._decl_variable_list)),
optseq(':', optional(field('attributes', $.attrlist))))
),
_decl_variable_list: $ => seq('(', optional($._decl_variable_list_body), ')'),
_decl_variable_list_body: $ => seq(
$._decl_variable_list_element,
repeat(seq(',', optional($._decl_variable_list_element)))
),
_decl_variable_list_element: $ => choice(
$.undef_expression,
$._declared_vars,
$.refalias_variable,
$.variable_group
),
variable_group: $ => seq('(', optional($._decl_variable_list_body), ')'),
localization_expression: $ =>
prec(TERMPREC.UNOP, seq(choice('local', 'dynamically'), $._term)),
stub_expression: $ => prec(-1, seq('(', ')')),
postfix_deref: $ => choice(
$.scalar_deref_expression,
$.array_deref_expression,
$.arraylen_deref_expression,
$.hash_deref_expression,
$.amper_deref_expression,
$.glob_deref_expression,
),
scalar_deref_expression: $ =>
prec.left(TERMPREC.ARROW, seq($._term, '->', '$', '*')),
array_deref_expression: $ =>
prec.left(TERMPREC.ARROW, seq($._term, '->', '@', '*')),
arraylen_deref_expression: $ =>
prec.left(TERMPREC.ARROW, seq($._term, '->', '$#', '*')),
hash_deref_expression: $ =>
prec.left(TERMPREC.ARROW, seq($._term, '->', '%', '*')),
amper_deref_expression: $ =>
prec.left(TERMPREC.ARROW, seq($._term, '->', '&', '*')),
glob_deref_expression: $ =>
prec.left(TERMPREC.ARROW, seq($._term, '->', '*', '*')),
require_expression: $ =>
prec.left(TERMPREC.REQUIRE, seq('require', $._term)),
require_version_expression: $ =>
prec.left(TERMPREC.REQUIRE, seq('require', field('version', $._version))),
func0op_call_expression: $ =>
seq(field('function', $._func0op), optseq('(', ')')),
func1op_call_expression: $ =>
prec.left(TERMPREC.UNOP, seq(
field('function', $._func1op),
choice(optseq('(', optional($._expr), ')'), $._term),
)),
_map_grep: $ => choice('map', 'grep'),
map_grep_expression: $ => prec.left(TERMPREC.LSTOP, choice(
seq($._map_grep, field('callback', $.block), field('list', $._listexpr)),
seq($._map_grep, field('callback', $._term), $._PERLY_COMMA, field('list', $._listexpr)),
seq($._map_grep, '(', $._NONASSOC, field('callback', $._term), $._PERLY_COMMA, field('list', $._listexpr), ')'),
seq($._map_grep, '(', $._NONASSOC, field('callback', $.block), field('list', $._listexpr), ')'),
)),
_sort_routine: $ => choice(prec(1, alias($._bareword, $.function)), $.block, prec(1, $.scalar)),
sort_expression: $ => prec.left(TERMPREC.LSTOP, choice(
seq('sort', optional(field('callback', $._sort_routine)), field('list', $._listexpr)),
seq('sort', '(', $._NONASSOC, optional(field('callback', $._sort_routine)), field('list', $._listexpr), ')'),
)),
_label_arg: $ => choice(alias($.identifier, $.label), $._term),
loopex_expression: $ =>
prec.left(TERMPREC.LOOPEX, seq(field('loopex', $._LOOPEX), optional($._label_arg))),
goto_expression: $ =>
prec.left(TERMPREC.LOOPEX, seq('goto', $._label_arg)),
return_expression: $ => prec(TERMPREC.LSTOP, seq('return', optional($._listexpr))),
undef_expression: $ => prec.left(TERMPREC.UNOP, seq('undef', optional($._term))),
_listop: $ => choice(
$.method_call_expression,
$.function_call_expression,
$.ambiguous_function_call_expression,
),
indirect_object: $ => choice(
alias($._builtin_filehandle, $.filehandle),
$.block,
seq($.scalar, optional($._no_search_slash_plz)),
),
_builtin_filehandle: $ => choice('STDIN', 'STDOUT', 'STDERR'),
_unambiguous_function: $ => alias(choice($._bareword, $._listop_keyword, $._indirob_listop, $.amper_sub), $.function),
function_call_expression: $ => choice(
seq(field('function', alias($.amper_sub, $.function))),
seq(field('function', $._unambiguous_function), '(', $._NONASSOC, optional(field('arguments', $._expr)), recoverParen($)),
seq(field('function', alias($._indirob_listop, $.function)), '(', $._NONASSOC, $.indirect_object, field('arguments', $._expr), recoverParen($)),
seq(field('function', $.function), '(', $._NONASSOC, $.indirect_object, field('arguments', $._expr), recoverParen($)),
),
_tricky_indirob_hashref: $ => seq($._PERLY_BRACE_OPEN, $._expr, $._PERLY_SEMICOLON, '}'),
ambiguous_function_call_expression: $ =>
prec.right(TERMPREC.LSTOP,
choice(
seq(field('function', alias($._listop_keyword, $.function)), field('arguments', $._listexpr)),
seq(field('function', alias($._indirob_listop, $.function)), field('arguments', $._listexpr)),
seq(field('function', $.function), optional($._no_search_slash_plz), field('arguments', $._listexpr)),
seq(field('function', $.function), $.indirect_object, field('arguments', $._listexpr)),
seq(field('function', alias($._indirob_listop, $.function)), $.indirect_object, field('arguments', $._listexpr)),
seq(field('function', $.function), alias($.block, $.indirect_object)),
seq(field('function', $.function), field('arguments', alias($._tricky_indirob_hashref, $.anonymous_hash_expression)), optseq($._PERLY_COMMA, field('arguments', $._listexpr)))
)
),
_indirob_listop: $ => choice('print', 'printf', 'say', 'exec', 'system'),
_listop_keyword: $ => choice(
'accept', 'atan2', 'bind', 'binmode', 'bless', 'crypt', 'chmod', 'chown',
'connect', 'die', 'dbmopen', 'fcntl', 'flock', 'getpriority',
'getprotobynumber', 'gethostbyaddr', 'getnetbyaddr', 'getservbyname',
'getservbyport', 'getsockopt', 'glob', 'index', 'ioctl', 'join', 'kill',
'link', 'listen', 'mkdir', 'msgctl', 'msgget', 'msgrcv', 'msgsend',
'opendir', 'push', 'pack', 'pipe', 'rename', 'rindex',
'read', 'recv', 'reverse', 'select', 'seek', 'semctl', 'semget',
'semop', 'send', 'setpgrp', 'setpriority', 'seekdir', 'setsockopt',
'shmctl', 'shmread', 'shmwrite', 'shutdown', 'socket', 'socketpair',
'split', 'sprintf', 'splice', 'substr', 'symlink', 'syscall',
'sysopen', 'sysseek', 'sysread', 'syswrite', 'tie', 'truncate', 'unlink',
'unpack', 'utime', 'unshift', 'vec', 'warn', 'waitpid', 'formline', 'open'
),
function: $ => $._bareword,
method_call_expression: $ => prec.left(TERMPREC.ARROW, seq(
field('invocant', $._term),
'->',
optional('&'),
field('method', $.method),
optional($._args_subscript)
)),
method: $ => choice($._bareword, $.scalar, $._RECOVER_ARROW),
_variables: $ => choice(
$.scalar,
$.array,
$.hash,
$.arraylen,
$.glob,
),
_signature_varname: $ => alias($._identifier, $.varname),
scalar: $ => seq('$', $._var_indirob),
_declare_scalar: $ => seq('$', choice($.varname, $._var_indirob_autoquote)),
_signature_scalar: $ => seq('$', $._signature_varname),
array: $ => seq('@', $._var_indirob),
_declare_array: $ => seq('@', choice($.varname, $._var_indirob_autoquote)),
_signature_array: $ => seq('@', $._signature_varname),
_HASH_PERCENT: $ => alias(token(prec(2, '%')), '%'), _SUB_AMPER: $ => alias(token(prec(2, '&')), '&'), _GLOB_STAR: $ => alias(token(prec(2, '*')), '*'),
hash: $ => seq($._HASH_PERCENT, $._var_indirob),
_declare_hash: $ => seq($._HASH_PERCENT, choice($.varname, $._var_indirob_autoquote)),
_signature_hash: $ => seq($._HASH_PERCENT, $._signature_varname),
arraylen: $ => seq('$#', $._var_indirob),
glob: $ => seq($._GLOB_STAR, choice(
alias($._amper_indirob, $.varname),
$._var_indirob_autoquote,
alias($._code_deref, $.glob_deref_expression),
)),
amper_sub: $ => seq($._SUB_AMPER, choice(
alias($._amper_indirob, $.varname),
$._var_indirob_autoquote,
alias($._code_deref, $.code_deref_expression),
)),
_amper_indirob: $ => choice($._bareword, $._ident_special, $.scalar),
_code_deref: $ => $.block,
_indirob: $ => choice(
$._bareword,
$._ident_special,
$.scalar,
$.block,
),
varname: $ => choice(
$._identifier,
$._ident_special ),
_var_indirob_autoquote: $ => seq(
$._PERLY_BRACE_OPEN,
alias(choice($._brace_autoquoted_token, $._bareword, $._special_var_name, /\^\w+/), $.varname),
$._brace_end_zw, '}'
),
_var_indirob: $ => choice(
alias($._indirob, $.varname),
$._var_indirob_autoquote
),
attrlist: $ => prec.left(0, seq(
$.attribute,
repeat(seq(optional(':'), $.attribute))
)),
attribute: $ => seq(
field('name', $.attribute_name),
optseq($._attribute_value_begin, '(', field('value', $.attribute_value), ')'),
),
attribute_name: $ => $._bareword,
_PERLY_COMMA: $ => choice(',', '=>'),
_KW_USE: $ => choice('use', 'no'),
_KW_FOR: $ => choice('for', 'foreach'),
_LOOPEX: $ => choice('last', 'next', 'redo'),
_PHASE_NAME: $ => choice('BEGIN', 'INIT', 'CHECK', 'UNITCHECK', 'END'),
_func0op: $ => choice(
'__FILE__', '__LINE__', '__PACKAGE__', '__SUB__',
'break', 'fork', 'getppid', 'time', 'times', 'wait', 'wantarray',
'continue'
),
_func1op: $ => choice(
'abs', 'alarm', 'chop', 'chdir', 'close', 'closedir', 'caller', 'chomp',
'chr', 'cos', 'chroot', 'defined', 'delete', 'dbmclose', 'exists', 'exit',
'eof', 'exp', 'each', 'fc', 'fileno', 'gmtime', 'getc', 'getpgrp',
'getprotobyname', 'getpwname', 'getpwuid', 'getpeername', 'getnetbyname',
'getsockname', 'getgrnam', 'getgrgid', 'hex', 'int', 'keys', 'lc',
'lcfirst', 'length', 'localtime', 'log', 'lock', 'lstat', 'oct', 'ord',
'prototype', 'pop', 'pos', 'quotemeta', 'reset', 'rand', 'rmdir',
'readdir', 'readline', 'readpipe', 'rewinddir', 'readlink', 'ref',
'scalar', 'shift', 'sin', 'sleep', 'sqrt', 'srand', 'stat', 'study',
'tell', 'telldir', 'tied', 'uc', 'ucfirst', 'untie', 'umask',
'values', 'write',
alias($._filetest, '-x')
),
comment: $ => /#.*/,
__DATA__: $ => seq(
choice(
seq(
alias(choice('__DATA__', '__END__'), $.eof_marker),
/.*/, alias($._gobbled_content, $.data_section)
),
seq(
alias(choice('\x04', $._ctrl_z_hack), $.eof_marker),
$._gobbled_content
)
),
),
_literal: $ => choice(
$.string_literal,
$.interpolated_string_literal,
$.command_string,
$.quoted_regexp,
$.match_regexp,
$.substitution_regexp,
$.transliteration_expression,
alias(token(prec(1, /v[0-9]+(?:\.[0-9]+)+/)), $.version),
),
_apostrophe: $ => alias($._single_quote, "'"),
_quotation_mark: $ => alias($._double_quote, "'"),
_backtick: $ => alias($._backtick_quote, "'"),
_search_slash: $ => alias($._search_slash_quote, "'"),
_quotelike_begin: $ => alias($._quotelike_begin_quote, "'"),
_quotelike_middle_close: $ => alias($._quotelike_middle_close_quote, "'"),
_quotelike_end: $ => alias($._quotelike_end_quote, "'"),
_quotelike_middle: $ => seq(
$._quotelike_middle_close,
choice($._quotelike_middle_skip, $._quotelike_begin),
),
string_literal: $ => seq(
choice(
seq('q', $._quotelike_begin),
$._apostrophe
),
optional(stringContent($, $._noninterpolated_string_content)),
$._quotelike_end
),
interpolated_string_literal: $ => seq(
choice(
seq('qq', $._quotelike_begin),
$._quotation_mark
),
optional(stringContent($, $._interpolated_string_content)),
$._quotelike_end
),
_subscripted_interpolations: $ => choice(
alias($._array_element_interpolation, $.array_element_expression),
alias($._hash_element_interpolation, $.hash_element_expression),
),
_braced_scalar: $ => seq(
'$', choice($.block, $._var_indirob_autoquote), $._NONASSOC
),
_braced_array: $ => seq(
'@', choice($.block, $._var_indirob_autoquote), $._NONASSOC
),
_interp_arrow: $ => token.immediate('->'),
_array_element_interpolation: $ => choice(
seq(field('array', alias($.scalar, $.container_variable)), token.immediate('['), field('index', $._expr), ']'),
prec.left(TERMPREC.ARROW, seq($.scalar, $._interp_arrow, '[', field('index', $._expr), ']')),
seq($._subscripted_interpolations, optional($._interp_arrow), token.immediate('['), field('index', $._expr), ']'),
),
_hash_element_interpolation: $ => choice(
seq(field('hash', alias($.scalar, $.container_variable)), token.immediate('{'), field('key', $._hash_key), '}'),
prec.left(TERMPREC.ARROW, seq($.scalar, $._interp_arrow, '{', field('key', $._hash_key), '}')),
seq($._subscripted_interpolations, optional($._interp_arrow), token.immediate('{'), field('key', $._hash_key), '}'),
),
_slice_expression_interpolation: $ => choice(
seq(field('array', alias($.array, $.slice_container_variable)), token.immediate('['), $._expr, ']'),
seq(field('hash', alias($.array, $.slice_container_variable)), token.immediate('{'), $._hash_key, '}'),
prec.left(TERMPREC.ARROW,
seq(field('arrayref', $.scalar), $._interp_arrow, '@', '[', $._expr, ']')),
prec.left(TERMPREC.ARROW,
seq(field('hashref', $.scalar), $._interp_arrow, '@', '{', $._hash_key, '}')),
),
_scalar_deref_interpolation: $ => prec.left(TERMPREC.ARROW, seq($.scalar, $._interp_arrow, token.immediate('$*'))),
_array_deref_interpolation: $ => prec.left(TERMPREC.ARROW, seq(field('arrayref', $.scalar), $._interp_arrow, token.immediate('@*'))),
_interpolations: $ => choice(
$.scalar,
$.arraylen,
$.array,
alias($._scalar_deref_interpolation, $.scalar_deref_expression),
alias($._array_deref_interpolation, $.array_deref_expression),
alias($._braced_scalar, $.scalar),
alias($._braced_array, $.array),
$._subscripted_interpolations,
alias($._slice_expression_interpolation, $.slice_expression),
),
_noninterpolated_string_content: $ => repeat1(
choice(
$._q_string_content,
$.escape_sequence,
$.escaped_delimiter,
)
),
_interpolation_fallbacks: $ => choice(
seq('@', $._no_interp_whitespace_zw),
seq('@', choice(/[^A-Za-z0-9_\$'+:-]/, $._quotelike_end_zw, $._quotelike_end)),
seq(
$.scalar,
alias(token.immediate('->'), 'not-interpolated'),
choice(
seq(optional(choice('@', '$')), $._no_interp_whitespace_zw),
$.scalar,
/[^@${\[]/
)
),
$._nonvar_interpolation_fallbacks
),
_nonvar_interpolation_fallbacks: $ => choice(
...aliasMany('not-interpolated', ['-', '{', '[',]),
),
_interpolated_string_content: $ => repeat1(
choice(
$._qq_string_content,
$._interpolation_fallbacks,
$.escape_sequence,
$.escaped_delimiter,
$._interpolations
)
),
quoted_word_list: $ => seq(
'qw',
$._quotelike_begin,
optional(stringContent($, $._noninterpolated_string_content)),
$._quotelike_end
),
command_string: $ => choice(
seq(
choice(
seq('qx', $._quotelike_begin),
$._backtick
),
optional(stringContent($, $._interpolated_string_content)),
$._quotelike_end
),
seq(
'qx',
$._apostrophe,
optional(stringContent($, $._noninterpolated_string_content)),
$._quotelike_end
)
),
quoted_regexp: $ => seq(
'qr',
choice(
seq(
$._quotelike_begin,
optional(regexpContent($, $._interpolated_regexp_content)),
),
seq(
$._apostrophe,
optional(regexpContent($, $._noninterpolated_string_content)), ),
),
$._quotelike_end,
optional(field('modifiers', $.quoted_regexp_modifiers))
),
match_regexp: $ => seq(
choice(
seq(
choice(
seq(
choice(
$._search_slash,
seq(field('operator', 'm'), $._quotelike_begin)
),
optional(regexpContent($, $._interpolated_regexp_content)),
),
seq(
field('operator', 'm'),
$._apostrophe,
optional(regexpContent($, $._noninterpolated_string_content)), ),
),
$._quotelike_end,
),
'//' ),
optional(field('modifiers', $.match_regexp_modifiers))
),
substitution_regexp: $ => seq(
field('operator', 's'),
choice(
seq(
$._quotelike_begin,
optional(regexpContent($, $._interpolated_regexp_content)),
$._quotelike_middle,
optional(replacement($, $._interpolated_string_content)),
),
seq(
$._apostrophe,
optional(regexpContent($, $._noninterpolated_string_content)),
$._quotelike_middle,
optional(replacement($, $._noninterpolated_string_content)),
),
),
$._quotelike_end,
optional(field('modifiers', $.substitution_regexp_modifiers))
),
_interpolated_regexp_content: $ => repeat1(
choice(
$._qq_string_content,
$.escape_sequence,
$.escaped_delimiter,
$._dollar_in_regexp,
alias($._regexp_open_bracket, '['),
alias($._regexp_open_brace, '{'),
$._interpolation_fallbacks,
$._interpolations,
seq('$', $._no_interp_whitespace_zw),
)
),
quoted_regexp_modifiers: $ => token.immediate(prec(2, /[msixpodualn]+/)),
match_regexp_modifiers: $ => token.immediate(prec(2, /[msixpogcdualn]+/)),
substitution_regexp_modifiers: $ => token.immediate(prec(2, /[msixpogcedualr]+/)),
transliteration_modifiers: $ => token.immediate(prec(2, /[cdsr]+/)),
_interpolated_transliteration_content: $ => repeat1(
choice(
$._qq_string_content,
$._nonvar_interpolation_fallbacks,
seq(choice('$', '@'), /./), $.escape_sequence,
$.escaped_delimiter,
)
),
transliteration_expression: $ => seq(
field('operator', choice('tr', 'y')),
choice(
seq(
$._quotelike_begin,
optional(trContent($, $._interpolated_transliteration_content)),
$._quotelike_middle,
optional(replacement($, $._interpolated_transliteration_content)),
),
seq(
$._apostrophe,
optional(trContent($, $._noninterpolated_string_content)),
$._quotelike_middle,
optional(replacement($, $._noninterpolated_string_content)),
),
),
$._quotelike_end,
optional(field('modifiers', $.transliteration_modifiers))
),
heredoc_token: $ => seq($._PERLY_HEREDOC, $._heredoc_delimiter),
command_heredoc_token: $ => seq($._PERLY_HEREDOC, $._command_heredoc_delimiter),
heredoc_content: $ => seq(
$._heredoc_start,
repeat(choice(
$._heredoc_middle,
$.escape_sequence,
$._interpolations,
$._interpolation_fallbacks
)),
$.heredoc_end
),
package: $ => $._bareword,
_version: $ => prec(1, choice($.number, $.version)),
version: $ => token(prec(2, /v[0-9]+(?:\.[0-9]+)*|[0-9]+(?:\.[0-9]+){2,}/)),
_conditionals: $ => choice('if', 'unless'),
_loops: $ => choice('while', 'until'),
autoquoted_bareword: $ => choice(
prec.dynamic(20,
prec(TERMPREC.PAREN, seq('-', $._bareword)),
),
seq(optional('-'), $._fat_comma_autoquoted)
),
_brace_autoquoted: $ => alias($._brace_autoquoted_token, $.autoquoted_bareword),
identifier: $ => prec(2, $._identifier),
_identifier: $ => unicode_ranges.identifier,
_special_var_name: $ => /[0-9]+|\^([A-Z[?\^_]|])|\S/,
_ident_special: $ => choice($._special_var_name, seq('$', $._dollar_ident_zw)),
bareword: $ => prec.dynamic(1, $._bareword),
_bareword: $ => choice($._identifier, /((::)|([a-zA-Z_]\w*))+/),
...primitives,
}
})