const PREC = {
COMMA: -1,
CAST: -1,
LOGICAL_OR_2: 1,
LOGICAL_XOR: 2,
LOGICAL_AND_2: 3,
ASSIGNMENT: 4,
TERNARY: 5,
NULL_COALESCE: 6,
LOGICAL_OR_1: 7,
LOGICAL_AND_1: 8,
BITWISE_OR: 9,
BITWISE_XOR: 10,
BITWISE_AND: 11,
EQUALITY: 12,
INEQUALITY: 13,
CONCAT: 14,
SHIFT: 15,
PLUS: 16,
TIMES: 17,
EXPONENTIAL: 18,
NEG: 19,
INSTANCEOF: 20,
INC: 21,
SCOPE: 22,
NEW: 23,
CALL: 24,
MEMBER: 25,
DEREF: 26,
};
module.exports = grammar({
name: 'php',
externals: $ => [
$._automatic_semicolon,
$.encapsed_string_chars,
$.encapsed_string_chars_after_variable,
$.execution_string_chars,
$.execution_string_chars_after_variable,
$.encapsed_string_chars_heredoc,
$.encapsed_string_chars_after_variable_heredoc,
$._eof,
$.heredoc_start,
$.heredoc_end,
$.nowdoc_string,
$.sentinel_error, ],
supertypes: $ => [
$._statement,
$._expression,
$._primary_expression,
$._type,
$._literal,
],
word: $ => $.name,
conflicts: $ => [
[$._array_destructing, $.array_creation_expression],
[$._array_destructing_element, $.array_element_initializer],
[$._primary_expression, $._array_destructing_element],
[$.union_type, $.intersection_type],
[$.intersection_type],
[$.if_statement],
[$.namespace_name],
[$.heredoc_body],
[$.namespace_name_as_prefix],
[$.namespace_use_declaration, $.namespace_name_as_prefix],
],
inline: $ => [
$._statement,
$._semicolon,
$._member_name,
$._variable,
$._callable_variable,
$._callable_expression,
$._foreach_value,
$._literal,
$._class_type_designator,
$._variable_name,
],
extras: $ => [
$.comment,
/[\s\uFEFF\u2060\u200B\u00A0]/,
$.text_interpolation,
],
rules: {
program: $ => seq(
optional($.text),
optional(seq(
$.php_tag,
repeat($._statement),
)),
),
php_tag: _ => /<\?([pP][hH][pP]|=)?/,
text_interpolation: $ => seq(
'?>',
optional($.text),
choice($.php_tag, $._eof),
),
text: _ => repeat1(choice(
token(prec(-1, /</)),
/[^\s<][^<]*/,
)),
_statement: $ => choice(
$.empty_statement,
$.compound_statement,
$.named_label_statement,
$.expression_statement,
$.if_statement,
$.switch_statement,
$.while_statement,
$.do_statement,
$.for_statement,
$.foreach_statement,
$.goto_statement,
$.continue_statement,
$.break_statement,
$.return_statement,
$.try_statement,
$.declare_statement,
$.echo_statement,
$.unset_statement,
$.const_declaration,
$.function_definition,
$.class_declaration,
$.interface_declaration,
$.trait_declaration,
$.enum_declaration,
$.namespace_definition,
$.namespace_use_declaration,
$.global_declaration,
$.function_static_declaration,
),
empty_statement: _ => prec(-1, ';'),
reference_modifier: _ => '&',
function_static_declaration: $ => seq(
keyword('static'),
commaSep1($.static_variable_declaration),
$._semicolon,
),
static_variable_declaration: $ => seq(
field('name', $.variable_name),
optional(seq(
'=',
field('value', $._expression),
)),
),
global_declaration: $ => seq(
keyword('global'),
commaSep1($._variable_name),
$._semicolon,
),
namespace_definition: $ => seq(
keyword('namespace'),
choice(
seq(
field('name', $.namespace_name),
$._semicolon,
),
seq(
field('name', optional($.namespace_name)),
field('body', $.compound_statement),
),
),
),
namespace_use_declaration: $ => seq(
keyword('use'),
optional(choice(keyword('function'), keyword('const'))),
choice(
seq(
commaSep1($.namespace_use_clause),
),
seq(
optional('\\'),
$.namespace_name,
'\\',
$.namespace_use_group,
),
),
$._semicolon,
),
namespace_use_clause: $ => seq(
choice($.name, alias($._reserved_identifier, $.name), $.qualified_name), optional($.namespace_aliasing_clause),
),
qualified_name: $ => seq(
$.namespace_name_as_prefix,
$.name,
),
namespace_name_as_prefix: $ => choice(
'\\',
seq(optional('\\'), $.namespace_name, '\\'),
seq(keyword('namespace'), '\\'),
seq(keyword('namespace'), optional('\\'), $.namespace_name, '\\'),
),
namespace_name: $ => seq($.name, repeat(seq('\\', $.name))),
namespace_aliasing_clause: $ => seq(
keyword('as'),
$.name,
),
namespace_use_group: $ => seq(
'{',
commaSep1($.namespace_use_group_clause),
'}',
),
namespace_use_group_clause: $ => seq(
optional(choice(keyword('function'), keyword('const'))),
$.namespace_name,
optional($.namespace_aliasing_clause),
),
trait_declaration: $ => seq(
keyword('trait'),
field('name', $.name),
field('body', $.declaration_list),
),
interface_declaration: $ => seq(
keyword('interface'),
field('name', $.name),
optional($.base_clause),
field('body', $.declaration_list),
),
base_clause: $ => seq(
keyword('extends'),
commaSep1(choice($.name, alias($._reserved_identifier, $.name), $.qualified_name)),
),
enum_declaration: $ => prec.right(seq(
optional(field('attributes', $.attribute_list)),
keyword('enum'),
field('name', $.name),
optional(seq(':', alias(choice('string', 'int'), $.primitive_type))),
optional($.class_interface_clause),
field('body', $.enum_declaration_list),
)),
enum_declaration_list: $ => seq(
'{',
repeat($._enum_member_declaration),
'}',
),
_enum_member_declaration: $ => choice(
$.enum_case,
$.method_declaration,
$.use_declaration,
),
enum_case: $ => seq(
optional(field('attributes', $.attribute_list)),
keyword('case'),
field('name', $.name),
optional(seq('=', field('value', choice($.string, $.integer)))),
$._semicolon,
),
class_declaration: $ => prec.right(seq(
optional(field('attributes', $.attribute_list)),
optional(field('modifier', choice($.final_modifier, $.abstract_modifier))),
optional(field('modifier', $.readonly_modifier)),
keyword('class'),
field('name', $.name),
optional($.base_clause),
optional($.class_interface_clause),
field('body', $.declaration_list),
optional($._semicolon),
)),
declaration_list: $ => seq(
'{',
repeat($._member_declaration),
'}',
),
final_modifier: _ => keyword('final'),
abstract_modifier: _ => keyword('abstract'),
readonly_modifier: _ => keyword('readonly'),
class_interface_clause: $ => seq(
keyword('implements'),
commaSep1(choice($.name, alias($._reserved_identifier, $.name), $.qualified_name)),
),
_member_declaration: $ => choice(
alias($._class_const_declaration, $.const_declaration),
$.property_declaration,
$.method_declaration,
$.use_declaration,
),
const_declaration: $ => $._const_declaration,
_class_const_declaration: $ => seq(
optional(field('attributes', $.attribute_list)),
optional(field('modifier', $.final_modifier)),
$._const_declaration,
),
_const_declaration: $ => seq(
optional($.visibility_modifier),
keyword('const'),
commaSep1($.const_element),
$._semicolon,
),
property_declaration: $ => seq(
optional(field('attributes', $.attribute_list)),
repeat1($._modifier),
optional(field('type', $._type)),
commaSep1($.property_element),
$._semicolon,
),
_modifier: $ => prec.left(choice(
$.var_modifier,
$.visibility_modifier,
$.static_modifier,
$.final_modifier,
$.abstract_modifier,
$.readonly_modifier,
)),
property_element: $ => seq(
$.variable_name, optional($.property_initializer),
),
property_initializer: $ => seq(
'=', $._expression,
),
method_declaration: $ => seq(
optional(field('attributes', $.attribute_list)),
repeat($._modifier),
$._function_definition_header,
choice(
field('body', $.compound_statement),
$._semicolon,
),
),
var_modifier: _ => keyword('var', false),
static_modifier: _ => keyword('static'),
use_declaration: $ => seq(
keyword('use'),
commaSep1(choice($.name, alias($._reserved_identifier, $.name), $.qualified_name)),
choice($.use_list, $._semicolon),
),
use_list: $ => seq(
'{',
repeat(seq(
choice(
$.use_instead_of_clause,
$.use_as_clause,
),
$._semicolon,
)),
'}',
),
use_instead_of_clause: $ => prec.left(seq(
choice($.class_constant_access_expression, $.name),
keyword('insteadof'),
$.name,
)),
use_as_clause: $ => seq(
choice($.class_constant_access_expression, $.name),
keyword('as'),
choice(
seq(
optional($.visibility_modifier),
$.name,
),
seq(
$.visibility_modifier,
optional($.name),
),
),
),
visibility_modifier: _ => choice(
keyword('public'),
keyword('protected'),
keyword('private'),
),
function_definition: $ => seq(
optional(field('attributes', $.attribute_list)),
$._function_definition_header,
field('body', $.compound_statement),
),
_function_definition_header: $ => seq(
keyword('function'),
optional(field('reference_modifier', $.reference_modifier)),
field('name', choice($.name, alias($._reserved_identifier, $.name))),
field('parameters', $.formal_parameters),
optional($._return_type),
),
_arrow_function_header: $ => seq(
optional(field('attributes', $.attribute_list)),
optional($.static_modifier),
keyword('fn'),
optional(field('reference_modifier', $.reference_modifier)),
field('parameters', $.formal_parameters),
optional($._return_type),
),
arrow_function: $ => seq(
$._arrow_function_header,
'=>',
field('body', $._expression),
),
formal_parameters: $ => seq(
'(',
commaSep(choice($.simple_parameter, $.variadic_parameter, $.property_promotion_parameter)),
optional(','),
')',
),
property_promotion_parameter: $ => seq(
optional(field('attributes', $.attribute_list)),
field('visibility', $.visibility_modifier),
field('readonly', optional($.readonly_modifier)),
field('type', optional($._type)), field('name', $.variable_name),
optional(seq(
'=',
field('default_value', $._expression),
)),
),
simple_parameter: $ => seq(
optional(field('attributes', $.attribute_list)),
field('type', optional($._type)),
optional(field('reference_modifier', $.reference_modifier)),
field('name', $.variable_name),
optional(seq(
'=',
field('default_value', $._expression),
)),
),
variadic_parameter: $ => seq(
optional(field('attributes', $.attribute_list)),
field('type', optional($._type)),
optional(field('reference_modifier', $.reference_modifier)),
'...',
field('name', $.variable_name),
),
_type: $ => choice($.union_type, $.intersection_type),
_types: $ => choice(
$.optional_type,
$.named_type,
$.primitive_type,
),
named_type: $ => choice($.name, $.qualified_name),
optional_type: $ => seq(
'?',
choice(
$.named_type,
$.primitive_type,
),
),
bottom_type: _ => 'never',
union_type: $ => prec.right(pipeSep1($._types)),
intersection_type: $ => ampSep1($._types),
primitive_type: _ => choice(
'array',
keyword('callable'), 'iterable',
'bool',
'float',
'int',
'string',
'void',
'mixed',
'static', 'false',
'null',
'true',
),
cast_type: _ => choice(
keyword('array', false),
keyword('binary', false),
keyword('bool', false),
keyword('boolean', false),
keyword('double', false),
keyword('int', false),
keyword('integer', false),
keyword('float', false),
keyword('object', false),
keyword('real', false),
keyword('string', false),
keyword('unset', false),
),
_return_type: $ => seq(':', field('return_type', choice($._type, $.bottom_type))),
const_element: $ => seq(
choice($.name, alias($._reserved_identifier, $.name)), '=', $._expression,
),
echo_statement: $ => seq(
keyword('echo'), $._expressions, $._semicolon,
),
unset_statement: $ => seq(
'unset', '(', commaSep1($._variable), ')', $._semicolon,
),
declare_statement: $ => seq(
keyword('declare'), '(', $.declare_directive, ')',
choice(
$._statement,
seq(':', repeat($._statement), keyword('enddeclare'), $._semicolon),
$._semicolon),
),
declare_directive: $ => seq(
choice('ticks', 'encoding', 'strict_types'),
'=',
$._literal,
),
_literal: $ => choice(
$.integer,
$.float,
$._string,
$.boolean,
$.null,
),
float: _ => /\d*(_\d+)*((\.\d*(_\d+)*)?([eE][\+-]?\d+(_\d+)*)|(\.\d*(_\d+)*)([eE][\+-]?\d+(_\d+)*)?)/,
try_statement: $ => seq(
keyword('try'),
field('body', $.compound_statement),
repeat1(choice($.catch_clause, $.finally_clause)),
),
catch_clause: $ => seq(
keyword('catch'),
'(',
field('type', $.type_list),
optional(field('name', $.variable_name)),
')',
field('body', $.compound_statement),
),
type_list: $ => pipeSep1($.named_type),
finally_clause: $ => seq(
keyword('finally'),
field('body', $.compound_statement),
),
goto_statement: $ => seq(
keyword('goto'), $.name, $._semicolon,
),
continue_statement: $ => seq(
keyword('continue'), optional($._expression), $._semicolon,
),
break_statement: $ => seq(
keyword('break'), optional($._expression), $._semicolon,
),
integer: _ => {
const decimal = /[1-9]\d*(_\d+)*/;
const octal = /0[oO]?[0-7]*(_[0-7]+)*/;
const hex = /0[xX][0-9a-fA-F]+(_[0-9a-fA-F]+)*/;
const binary = /0[bB][01]+(_[01]+)*/;
return token(choice(
decimal,
octal,
hex,
binary,
));
},
return_statement: $ => seq(
keyword('return'), optional($._expression), $._semicolon,
),
throw_expression: $ => seq(
keyword('throw'),
$._expression,
),
while_statement: $ => seq(
keyword('while'),
field('condition', $.parenthesized_expression),
choice(
field('body', $._statement),
seq(
field('body', $.colon_block),
keyword('endwhile'),
$._semicolon,
),
),
),
do_statement: $ => seq(
keyword('do'),
field('body', $._statement),
keyword('while'),
field('condition', $.parenthesized_expression),
$._semicolon,
),
for_statement: $ => seq(
keyword('for'),
'(',
optional($._expressions),
';',
optional($._expressions),
';',
optional($._expressions),
')',
choice(
$._semicolon,
$._statement,
seq(':', repeat($._statement), keyword('endfor'), $._semicolon),
),
),
_expressions: $ => choice(
$._expression,
$.sequence_expression,
),
sequence_expression: $ => prec(PREC.COMMA, seq(
$._expression, ',', choice($.sequence_expression, $._expression)),
),
foreach_statement: $ => seq(
keyword('foreach'),
'(',
$._expression,
keyword('as'),
choice(
alias($.foreach_pair, $.pair),
$._foreach_value,
),
')',
choice(
$._semicolon,
field('body', $._statement),
seq(
field('body', $.colon_block),
keyword('endforeach'),
$._semicolon,
),
),
),
foreach_pair: $ => seq($._expression, '=>', $._foreach_value),
_foreach_value: $ => choice(
$.by_ref,
$._expression,
$.list_literal,
),
if_statement: $ => seq(
keyword('if'),
field('condition', $.parenthesized_expression),
choice(
seq(
field('body', $._statement),
repeat(field('alternative', $.else_if_clause)),
optional(field('alternative', $.else_clause)),
),
seq(
field('body', $.colon_block),
repeat(field('alternative', alias($.else_if_clause_2, $.else_if_clause))),
optional(field('alternative', alias($.else_clause_2, $.else_clause))),
keyword('endif'),
$._semicolon,
),
),
),
colon_block: $ => seq(
':',
repeat($._statement),
),
else_if_clause: $ => seq(
keyword('elseif'),
field('condition', $.parenthesized_expression),
field('body', $._statement),
),
else_clause: $ => seq(
keyword('else'),
field('body', $._statement),
),
else_if_clause_2: $ => seq(
keyword('elseif'),
field('condition', $.parenthesized_expression),
field('body', $.colon_block),
),
else_clause_2: $ => seq(
keyword('else'),
field('body', $.colon_block),
),
match_expression: $ => seq(
keyword('match'),
field('condition', $.parenthesized_expression),
field('body', $.match_block),
),
match_block: $ => prec.left(
seq(
'{',
commaSep(
choice(
$.match_conditional_expression,
$.match_default_expression,
),
),
optional(','),
'}',
),
),
match_condition_list: $ => commaSep1($._expression),
match_conditional_expression: $ => seq(
field('conditional_expressions', $.match_condition_list),
'=>',
field('return_expression', $._expression),
),
match_default_expression: $ => seq(
keyword('default'),
'=>',
field('return_expression', $._expression),
),
switch_statement: $ => seq(
keyword('switch'),
field('condition', $.parenthesized_expression),
field('body', $.switch_block),
),
switch_block: $ => choice(
seq(
'{',
repeat(choice($.case_statement, $.default_statement)),
'}',
),
seq(
':',
repeat(choice($.case_statement, $.default_statement)),
keyword('endswitch'),
$._semicolon,
),
),
case_statement: $ => seq(
keyword('case'),
field('value', $._expression),
choice(':', ';'),
repeat($._statement),
),
default_statement: $ => seq(
keyword('default'),
choice(':', ';'),
repeat($._statement),
),
compound_statement: $ => seq(
'{',
repeat($._statement),
'}',
),
named_label_statement: $ => seq(
$.name,
':',
),
expression_statement: $ => seq(
$._expression,
$._semicolon,
),
_expression: $ => choice(
$.conditional_expression,
$.match_expression,
$.augmented_assignment_expression,
$.assignment_expression,
$.reference_assignment_expression,
$.yield_expression,
$._unary_expression,
$.binary_expression,
$.include_expression,
$.include_once_expression,
$.require_expression,
$.require_once_expression,
),
_unary_expression: $ => choice(
$.clone_expression,
$._primary_expression,
$.exponentiation_expression,
$.unary_op_expression,
$.cast_expression,
),
unary_op_expression: $ => choice(
prec(PREC.INC, seq('@', $._expression)),
prec.left(PREC.NEG, seq(choice('+', '-', '~', '!'), $._expression)),
),
exponentiation_expression: $ => prec.right(PREC.EXPONENTIAL, seq(
field('left', choice($.clone_expression, $._primary_expression, $.unary_op_expression, $.match_expression)),
'**',
field('right', choice($.exponentiation_expression, $.clone_expression, $.unary_op_expression, $._primary_expression, $.augmented_assignment_expression, $.assignment_expression, $.match_expression, $.cast_expression)),
)),
clone_expression: $ => seq(
keyword('clone'), $._primary_expression,
),
_primary_expression: $ => choice(
$._variable,
$._literal,
$.class_constant_access_expression,
$.qualified_name,
$.name,
$.array_creation_expression,
$.print_intrinsic,
$.anonymous_function_creation_expression,
$.arrow_function,
$.object_creation_expression,
$.update_expression,
$.shell_command_expression,
$.parenthesized_expression,
$.throw_expression,
$.arrow_function,
),
parenthesized_expression: $ => seq('(', $._expression, ')'),
class_constant_access_expression: $ => seq(
$._scope_resolution_qualifier,
'::',
choice($.name, alias($._reserved_identifier, $.name)),
),
print_intrinsic: $ => seq(
keyword('print'), $._expression,
),
anonymous_function_creation_expression: $ => seq(
optional(field('attributes', $.attribute_list)),
optional(keyword('static')),
keyword('function'),
optional(field('reference_modifier', $.reference_modifier)),
field('parameters', $.formal_parameters),
optional($.anonymous_function_use_clause),
optional($._return_type),
field('body', $.compound_statement),
),
anonymous_function_use_clause: $ => seq(
keyword('use'),
'(',
commaSep1(choice(alias($.variable_reference, $.by_ref), $.variable_name)),
optional(','),
')',
),
object_creation_expression: $ => prec.right(PREC.NEW, choice(
seq(
keyword('new'),
$._class_type_designator,
optional($.arguments),
),
seq(
keyword('new'),
optional(field('attributes', $.attribute_list)),
keyword('class'),
optional($.arguments),
optional($.base_clause),
optional($.class_interface_clause),
$.declaration_list,
),
)),
_class_type_designator: $ => choice(
$.qualified_name,
$.name,
alias($._reserved_identifier, $.name),
$.subscript_expression,
$.member_access_expression,
$.nullsafe_member_access_expression,
$.scoped_property_access_expression,
$._variable_name,
),
update_expression: $ => prec.left(PREC.INC, choice(
seq($._variable, '++'),
seq($._variable, '--'),
seq('++', $._variable),
seq('--', $._variable),
)),
cast_expression: $ => prec(PREC.CAST, seq(
'(', field('type', $.cast_type), ')',
field('value', choice($._unary_expression, $.include_expression, $.include_once_expression)),
)),
cast_variable: $ => prec(PREC.CAST, seq(
'(', field('type', $.cast_type), ')',
field('value', $._variable),
)),
assignment_expression: $ => prec.right(PREC.ASSIGNMENT, seq(
field('left', choice(
$._variable,
$.list_literal,
)),
'=',
field('right', $._expression),
)),
reference_assignment_expression: $ => prec.right(PREC.ASSIGNMENT, seq(
field('left', choice(
$._variable,
$.list_literal,
)),
'=',
'&',
field('right', $._expression),
)),
conditional_expression: $ => prec.left(PREC.TERNARY, seq( field('condition', $._expression),
'?',
field('body', optional($._expression)),
':',
field('alternative', $._expression),
)),
augmented_assignment_expression: $ => prec.right(PREC.ASSIGNMENT, seq(
field('left', $._variable),
field('operator', choice(
'**=',
'*=',
'/=',
'%=',
'+=',
'-=',
'.=',
'<<=',
'>>=',
'&=',
'^=',
'|=',
'??=',
)),
field('right', $._expression),
)),
_variable: $ => choice(
alias($.cast_variable, $.cast_expression),
$._callable_variable,
$.scoped_property_access_expression,
$.member_access_expression,
$.nullsafe_member_access_expression,
),
member_access_expression: $ => prec(PREC.MEMBER, seq(
field('object', $._dereferencable_expression),
'->',
$._member_name,
)),
nullsafe_member_access_expression: $ => prec(PREC.MEMBER, seq(
field('object', $._dereferencable_expression),
'?->',
$._member_name,
)),
scoped_property_access_expression: $ => prec(PREC.MEMBER, seq(
field('scope', $._scope_resolution_qualifier),
'::',
field('name', $._variable_name),
)),
list_literal: $ => choice($._list_destructing, $._array_destructing),
_list_destructing: $ => seq(
keyword('list'),
'(',
commaSep1(optional(choice(
choice(alias($._list_destructing, $.list_literal), $._variable, $.by_ref),
seq($._expression, '=>', choice(alias($._list_destructing, $.list_literal), $._variable, $.by_ref)),
))),
')',
),
_array_destructing: $ => seq(
'[',
commaSep1(optional($._array_destructing_element)),
']',
),
_array_destructing_element: $ => choice(
choice(alias($._array_destructing, $.list_literal), $._variable, $.by_ref),
seq($._expression, '=>', choice(alias($._array_destructing, $.list_literal), $._variable, $.by_ref)),
),
_callable_variable: $ => choice(
$._variable_name,
$.subscript_expression,
$.member_call_expression,
$.nullsafe_member_call_expression,
$.scoped_call_expression,
$.function_call_expression,
),
function_call_expression: $ => prec(PREC.CALL, seq(
field('function', choice($.name, alias($._reserved_identifier, $.name), $.qualified_name, $._callable_expression)),
field('arguments', $.arguments),
)),
_callable_expression: $ => choice(
$._callable_variable,
$.parenthesized_expression,
$.array_creation_expression,
$._string,
),
scoped_call_expression: $ => prec(PREC.CALL, seq(
field('scope', $._scope_resolution_qualifier),
'::',
$._member_name,
field('arguments', $.arguments),
)),
_scope_resolution_qualifier: $ => choice(
$.relative_scope,
$.name,
alias($._reserved_identifier, $.name),
$.qualified_name,
$._dereferencable_expression,
),
relative_scope: _ => prec(PREC.SCOPE, choice(
'self',
'parent',
keyword('static'),
)),
variadic_placeholder: _ => '...',
arguments: $ => seq(
'(',
choice(
seq(
commaSep($.argument),
optional(','),
),
$.variadic_placeholder,
),
')',
),
argument: $ => seq(
optional(seq(field('name', $.name), ':')),
optional(field('reference_modifier', $.reference_modifier)),
choice(alias($._reserved_identifier, $.name), $.variadic_unpacking, $._expression),
),
member_call_expression: $ => prec(PREC.CALL, seq(
field('object', $._dereferencable_expression),
'->',
$._member_name,
field('arguments', $.arguments),
)),
nullsafe_member_call_expression: $ => prec(PREC.CALL, seq(
field('object', $._dereferencable_expression),
'?->',
$._member_name,
field('arguments', $.arguments),
)),
variadic_unpacking: $ => seq('...', $._expression),
_member_name: $ => choice(
field('name', choice(
alias($._reserved_identifier, $.name),
$.name,
$._variable_name,
)),
seq(
'{',
field('name', $._expression),
'}',
),
),
subscript_expression: $ => seq(
$._dereferencable_expression,
choice(
seq('[', optional($._expression), ']'),
seq('{', $._expression, '}'),
),
),
_dereferencable_expression: $ => prec(PREC.DEREF, choice(
$._variable,
$.class_constant_access_expression,
$.parenthesized_expression,
$.array_creation_expression,
$.name,
alias($._reserved_identifier, $.name),
$.qualified_name,
$._string,
)),
array_creation_expression: $ => choice(
seq(keyword('array'), '(', commaSep($.array_element_initializer), optional(','), ')'),
seq('[', commaSep($.array_element_initializer), optional(','), ']'),
),
attribute_group: $ => seq(
'#[',
commaSep1($.attribute),
optional(','),
']',
),
attribute_list: $ => repeat1($.attribute_group),
attribute: $ => seq(
choice($.name, alias($._reserved_identifier, $.name), $.qualified_name),
optional(field('parameters', $.arguments)),
),
_complex_string_part: $ => seq(
'{',
$._expression,
'}',
),
_simple_string_member_access_expression: $ => prec(PREC.MEMBER, seq(
field('object', $.variable_name),
'->',
field('name', $.name),
)),
_simple_string_subscript_unary_expression: $ => prec.left(seq('-', $.integer)),
_simple_string_array_access_argument: $ => choice(
$.integer,
alias($._simple_string_subscript_unary_expression, $.unary_op_expression),
$.name,
$.variable_name,
),
_simple_string_subscript_expression: $ => prec(PREC.DEREF, seq(
$.variable_name,
seq('[', $._simple_string_array_access_argument, ']'),
)),
_simple_string_part: $ => choice(
alias($._simple_string_member_access_expression, $.member_access_expression),
$._variable_name,
alias($._simple_string_subscript_expression, $.subscript_expression),
),
escape_sequence: _ => token.immediate(seq(
'\\',
choice(
'n',
'r',
't',
'v',
'e',
'f',
'\\',
/\$/,
'"',
'`',
/[0-7]{1,3}/,
/x[0-9A-Fa-f]{1,2}/,
/u{[0-9A-Fa-f]+}/,
),
)),
_interpolated_string_body: $ => repeat1(
choice(
$.escape_sequence,
seq($.variable_name, alias($.encapsed_string_chars_after_variable, $.string_value)),
alias($.encapsed_string_chars, $.string_value),
$._simple_string_part,
$._complex_string_part,
alias('\\u', $.string_value),
alias('\'', $.string_value), ),
),
_interpolated_string_body_heredoc: $ => repeat1(
choice(
$.escape_sequence,
seq($.variable_name, alias($.encapsed_string_chars_after_variable_heredoc, $.string_value)),
alias($.encapsed_string_chars_heredoc, $.string_value),
$._simple_string_part,
$._complex_string_part,
alias('\\u', $.string_value),
alias('\'', $.string_value), alias('<?', $.string_value),
alias(token(prec(1, '?>')), $.string_value),
),
),
encapsed_string: $ => prec.right(seq(
choice(
/[bB]"/,
'"',
),
optional($._interpolated_string_body),
'"',
)),
string: $ => seq(
choice(
/[bB]'/,
'\'',
),
optional($.string_value),
'\'',
),
string_value: _ => token(prec(1, repeat1(/\\'|\\\\|\\?[^'\\]/))),
heredoc_body: $ => seq($._new_line,
repeat1(prec.right(
seq(optional($._new_line), $._interpolated_string_body_heredoc),
)),
),
heredoc: $ => seq(
token('<<<'),
field('identifier', choice(
$.heredoc_start,
seq('"', $.heredoc_start, token.immediate('"')),
)),
choice(
seq(
field('value', $.heredoc_body),
$._new_line,
field('end_tag', $.heredoc_end),
),
seq(
field('value', optional($.heredoc_body)),
field('end_tag', $.heredoc_end),
),
),
),
_new_line: _ => /\r?\n|\r/,
nowdoc_body: $ => seq($._new_line,
choice(
repeat1(
$.nowdoc_string,
),
alias('', $.nowdoc_string),
),
),
nowdoc: $ => seq(
token('<<<'),
'\'',
field('identifier', $.heredoc_start),
token.immediate('\''),
choice(
seq(
field('value', $.nowdoc_body),
$._new_line,
field('end_tag', $.heredoc_end),
),
seq(
field('value', optional($.nowdoc_body)),
field('end_tag', $.heredoc_end),
),
),
),
_interpolated_execution_operator_body: $ => repeat1(
choice(
$.escape_sequence,
seq($.variable_name, alias($.execution_string_chars_after_variable, $.string_value)),
alias($.execution_string_chars, $.string_value),
$._simple_string_part,
$._complex_string_part,
alias('\\u', $.string_value),
),
),
shell_command_expression: $ => seq(
'`',
optional($._interpolated_execution_operator_body),
'`',
),
boolean: _ => /true|false/i,
null: _ => keyword('null', false),
_string: $ => choice($.encapsed_string, $.string, $.heredoc, $.nowdoc),
dynamic_variable_name: $ => choice(
seq('$', $._variable_name),
seq('$', '{', $._expression, '}'),
),
_variable_name: $ => choice($.dynamic_variable_name, $.variable_name),
variable_name: $ => seq('$', $.name),
variable_reference: $ => seq('&', $.variable_name),
by_ref: $ => seq(
'&',
choice(
$._callable_variable,
$.member_access_expression,
$.nullsafe_member_access_expression,
),
),
yield_expression: $ => prec.right(seq(
keyword('yield'),
optional(choice(
$.array_element_initializer,
seq(keyword('from'), $._expression),
)),
)),
array_element_initializer: $ => prec.right(choice(
choice($.by_ref, $._expression),
seq($._expression, '=>', choice($.by_ref, $._expression)),
$.variadic_unpacking,
)),
binary_expression: $ => choice(
prec(PREC.INSTANCEOF, seq(
field('left', $._unary_expression),
field('operator', keyword('instanceof')),
field('right', $._class_type_designator),
)),
prec.right(PREC.NULL_COALESCE, seq(
field('left', $._expression),
field('operator', '??'),
field('right', $._expression),
)),
...[
[keyword('and'), PREC.LOGICAL_AND_2],
[keyword('or'), PREC.LOGICAL_OR_2],
[keyword('xor'), PREC.LOGICAL_XOR],
['||', PREC.LOGICAL_OR_1],
['&&', PREC.LOGICAL_AND_1],
['|', PREC.BITWISE_OR],
['^', PREC.BITWISE_XOR],
['&', PREC.BITWISE_AND],
['==', PREC.EQUALITY],
['!=', PREC.EQUALITY],
['<>', PREC.EQUALITY],
['===', PREC.EQUALITY],
['!==', PREC.EQUALITY],
['<', PREC.INEQUALITY],
['>', PREC.INEQUALITY],
['<=', PREC.INEQUALITY],
['>=', PREC.INEQUALITY],
['<=>', PREC.EQUALITY],
['<<', PREC.SHIFT],
['>>', PREC.SHIFT],
['+', PREC.PLUS],
['-', PREC.PLUS],
['.', PREC.CONCAT],
['*', PREC.TIMES],
['/', PREC.TIMES],
['%', PREC.TIMES],
].map(([op, p]) => prec.left(p, seq(
field('left', $._expression),
field('operator', op),
field('right', $._expression),
))),
),
include_expression: $ => seq(
keyword('include'),
$._expression,
),
include_once_expression: $ => seq(
keyword('include_once'),
$._expression,
),
require_expression: $ => seq(
keyword('require'),
$._expression,
),
require_once_expression: $ => seq(
keyword('require_once'),
$._expression,
),
name: _ => /[_a-zA-Z\u00A1-\u00ff][_a-zA-Z\u00A1-\u00ff\d]*/,
_reserved_identifier: _ => choice(
'self',
'parent',
keyword('static'),
),
comment: _ => token(choice(
seq(
choice('//', /#[^?\[?\r?\n]/),
repeat(/[^?\r?\n]|\?[^>\r\n]/),
optional(/\?\r?\n/),
),
'#',
seq(
'/*',
/[^*]*\*+([^/*][^*]*\*+)*/,
'/',
),
)),
_semicolon: $ => choice($._automatic_semicolon, ';'),
},
});
function keyword(word, aliasAsWord = true) {
let result = new RegExp(word, 'i');
if (aliasAsWord) result = alias(result, word);
return result;
}
function commaSep1(rule) {
return seq(rule, repeat(seq(',', rule)));
}
function commaSep(rule) {
return optional(commaSep1(rule));
}
function pipeSep1(rule) {
return seq(rule, repeat(seq('|', rule)));
}
function ampSep1(rule) {
return seq(rule, repeat(seq(token('&'), rule)));
}