mirror of https://github.com/Wilfred/difftastic/
1520 lines
37 KiB
JavaScript
1520 lines
37 KiB
JavaScript
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,
|
|
$.encapsed_string_chars_heredoc,
|
|
$.encapsed_string_chars_after_variable_heredoc,
|
|
$._eof,
|
|
$.heredoc_start,
|
|
$.heredoc_end,
|
|
$.nowdoc_string,
|
|
$.sentinel_error, // Unused token used to indicate error recovery mode
|
|
],
|
|
|
|
supertypes: $ => [
|
|
$._statement,
|
|
$._expression,
|
|
$._primary_expression,
|
|
$._type,
|
|
$._literal,
|
|
],
|
|
|
|
word: $ => $.name,
|
|
|
|
conflicts: $ => [
|
|
[$.simple_parameter, $.name],
|
|
[$.variadic_parameter, $.name],
|
|
[$.static_modifier, $._reserved_identifier],
|
|
|
|
[$._array_destructing, $.array_creation_expression],
|
|
[$._array_destructing_element, $.array_element_initializer],
|
|
[$._primary_expression, $._array_destructing_element],
|
|
|
|
[$.union_type, $._return_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,
|
|
$._intrinsic,
|
|
$._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(':', $._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))),
|
|
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(
|
|
field('visibility', $.visibility_modifier),
|
|
field('readonly', optional($.readonly_modifier)),
|
|
field('type', optional($._type)), // Note: callable is not a valid type here, but instead of complicating the parser, we defer this checking to any intelligence using the parser
|
|
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: $ => $.union_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)),
|
|
|
|
primitive_type: $ => choice(
|
|
'array',
|
|
keyword('callable'), // not legal in property types
|
|
'iterable',
|
|
'bool',
|
|
'float',
|
|
'int',
|
|
'string',
|
|
'void',
|
|
'mixed',
|
|
'static', // only legal as a return type
|
|
'false', // only legal in unions
|
|
'null', // only legal in unions
|
|
),
|
|
|
|
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(
|
|
'{',
|
|
commaSep1(
|
|
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)
|
|
)),
|
|
|
|
shell_command_expression: $ => token(seq(
|
|
'`', backtick_chars(), '`'
|
|
)),
|
|
|
|
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( // TODO: Ternay is non-assossiative after PHP 8
|
|
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
|
|
),
|
|
|
|
parenthesized_expression: $ => seq('(', $._expression, ')'),
|
|
|
|
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: $ => token('...'),
|
|
|
|
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),
|
|
),
|
|
|
|
// Note: remember to also update the is_escapable_sequence method in the
|
|
// external scanner whenever changing these rules
|
|
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) // Needed to avoid the edge case "$b'" from breaking parsing by trying to apply the $.string rule for some reason
|
|
),
|
|
),
|
|
|
|
_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), // Needed to avoid the edge case "$b'" from breaking parsing by trying to apply the $.string rule for some reason
|
|
alias('<?', $.string_value),
|
|
alias(token(prec(1, '?>')), $.string_value),
|
|
),
|
|
),
|
|
|
|
encapsed_string: $ => prec.right(seq(
|
|
choice(
|
|
/[bB]"/,
|
|
'"',
|
|
),
|
|
optional($._interpolated_string_body),
|
|
'"',
|
|
)),
|
|
|
|
string: $ => seq(
|
|
choice(
|
|
/[bB]'/,
|
|
"'"
|
|
),
|
|
$.string_value,
|
|
"'",
|
|
),
|
|
|
|
string_value: $ => token(prec(1, repeat(/\\'|\\\\|\\?[^'\\]/))), // prec(1, ...) is needed to avoid conflict with $.comment
|
|
|
|
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: $ => choice(token(/\r?\n/), token(/\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),
|
|
)
|
|
),
|
|
),
|
|
|
|
boolean: $ => /[Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]/,
|
|
|
|
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 pattern = ''
|
|
for (const letter of word) {
|
|
pattern += `[${letter}${letter.toLocaleUpperCase()}]`
|
|
}
|
|
let result = new RegExp(pattern)
|
|
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 pipeSep(rule) {
|
|
return optional(commaSep1(rule));
|
|
}
|
|
|
|
function backtick_chars() {
|
|
const dq_simple_escapes = /\\"|\\\\|\\\$|\\e|\\f|\\n|\\r|\\t|\\v/
|
|
const octal_digit = /[0-7]/
|
|
const dq_octal_escapes = seq('\\', octal_digit, optional(octal_digit), optional(octal_digit))
|
|
const hex_digit = /\d|a-f|A-F/
|
|
const dq_hex_escapes = seq(
|
|
/\\[xX]/,
|
|
hex_digit,
|
|
optional(hex_digit)
|
|
)
|
|
|
|
const dq_unicode_escapes = seq('\\u{', repeat1(hex_digit), '}')
|
|
const dq_escapes = choice(dq_simple_escapes, dq_octal_escapes, dq_hex_escapes, dq_unicode_escapes)
|
|
return repeat(choice(dq_escapes, /[^`\\]|\\[^`\\$efnrtv0-7]/))
|
|
}
|