mirror of https://github.com/Wilfred/difftastic/
1283 lines
30 KiB
JavaScript
1283 lines
30 KiB
JavaScript
'use strict';
|
|
|
|
// Precedence based on order. Indirection overkill but I couldn't help myself.
|
|
// https://docs.hhvm.com/hack/expressions-and-operators/operator-precedence
|
|
[
|
|
[prec.left, 'qualified'],
|
|
[prec.left, 'subscript'],
|
|
[prec.left, 'select'],
|
|
[prec, 'new'],
|
|
[prec.left, 'paren'],
|
|
[prec, 'clone'],
|
|
[prec.right, 'await', 'postfix'],
|
|
[prec.right, '**', 'cast', 'error', 'prefix'],
|
|
[prec.left, 'is', 'as'],
|
|
[prec.right, 'unary'],
|
|
[prec.left, '*', '/', '%'],
|
|
[prec.left, '+', '-', '.'],
|
|
[prec.left, '<<', '>>'],
|
|
[prec.left, '<', '>', '<=', '>=', '<=>'],
|
|
[prec.left, '==', '!=', '===', '!=='],
|
|
[prec.left, '&&'],
|
|
[prec.left, '^'],
|
|
[prec.left, '||'],
|
|
[prec.left, '&'],
|
|
[prec.left, '|'],
|
|
[prec.right, '??'],
|
|
[prec.left, 'ternary', '?:'],
|
|
[prec.left, '|>'],
|
|
// prettier-ignore
|
|
[prec.right,
|
|
'=', '??=', '.=', '|=', '^=', '&=', '<<=', '>>=', '+=', '-=', '*=', '/=', '%=', '**='],
|
|
[prec.right, 'print'],
|
|
[prec.left, 'include', 'require'],
|
|
]
|
|
.reverse()
|
|
.forEach(([_prec, ...names], index) =>
|
|
names.forEach(name => {
|
|
prec[name] = rule => _prec(index, rule);
|
|
}),
|
|
);
|
|
|
|
const identifier = /[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*/;
|
|
|
|
const rules = {
|
|
script: $ => seq(opt(/<\?[hH][hH]/), rep($._statement)),
|
|
|
|
identifier: $ => identifier,
|
|
|
|
variable: $ => token(seq('$', identifier)),
|
|
|
|
pipe_variable: $ => '$$',
|
|
|
|
// TODO: More types?
|
|
_keyword: $ =>
|
|
choice(
|
|
'type',
|
|
'newtype',
|
|
'shape',
|
|
'tuple',
|
|
'clone',
|
|
'new',
|
|
'print',
|
|
'namespace',
|
|
$._primitive_type,
|
|
$._collection_type,
|
|
),
|
|
|
|
qualified_identifier: $ =>
|
|
choice(
|
|
seq(
|
|
opt(choice($.identifier, 'namespace')),
|
|
rep1(seq('\\', $.identifier)),
|
|
),
|
|
$.identifier,
|
|
),
|
|
|
|
scoped_identifier: $ =>
|
|
seq(
|
|
choice(
|
|
$.qualified_identifier,
|
|
$.variable,
|
|
$.scope_identifier,
|
|
$._xhp_identifier,
|
|
$.pipe_variable,
|
|
),
|
|
'::',
|
|
choice($.identifier, $.variable),
|
|
),
|
|
|
|
scope_identifier: $ => choice('self', 'parent', 'static'),
|
|
|
|
_variablish: $ =>
|
|
choice(
|
|
$.variable,
|
|
$.pipe_variable,
|
|
$.list_expression,
|
|
$.subscript_expression,
|
|
$.qualified_identifier,
|
|
$.parenthesized_expression,
|
|
$.call_expression,
|
|
$.scoped_identifier,
|
|
$.scope_identifier,
|
|
$.selection_expression,
|
|
$._xhp_identifier,
|
|
),
|
|
|
|
_statement: $ =>
|
|
choice(
|
|
$._declaration,
|
|
|
|
$.compound_statement,
|
|
$.empty_statement,
|
|
$.expression_statement,
|
|
|
|
$.return_statement,
|
|
$.break_statement,
|
|
$.continue_statement,
|
|
$.throw_statement,
|
|
$.echo_statement,
|
|
$.unset_statement,
|
|
|
|
$.use_statement,
|
|
$.if_statement,
|
|
$.while_statement,
|
|
$.do_statement,
|
|
$.for_statement,
|
|
$.switch_statement,
|
|
$.foreach_statement,
|
|
$.try_statement,
|
|
$.concurrent_statement,
|
|
$.using_statement,
|
|
),
|
|
|
|
_declaration: $ =>
|
|
choice(
|
|
$.function_declaration,
|
|
$.class_declaration,
|
|
$.interface_declaration,
|
|
$.trait_declaration,
|
|
$.alias_declaration,
|
|
$.enum_declaration,
|
|
$.abstract_enum_class_declaration,
|
|
$.enum_class_declaration,
|
|
$.namespace_declaration,
|
|
$.const_declaration,
|
|
),
|
|
|
|
heredoc: $ =>
|
|
seq(
|
|
'<<<',
|
|
$._heredoc_start,
|
|
opt(alias($._heredoc_start_newline, '\n')),
|
|
rep(choice($._heredoc_body, $.variable, $.embedded_braced_expression)),
|
|
opt(alias($._heredoc_end_newline, '\n')),
|
|
$._heredoc_end,
|
|
),
|
|
|
|
embedded_braced_expression: $ =>
|
|
seq(
|
|
// Use an external scanner for the opening brace so we can restrict embedded braced
|
|
// expressions to ones that start with a $.variable.
|
|
alias($._embedded_opening_brace, '{'),
|
|
choice(
|
|
$.variable,
|
|
$.call_expression,
|
|
$.subscript_expression,
|
|
$.selection_expression,
|
|
),
|
|
'}',
|
|
),
|
|
|
|
braced_expression: $ => seq('{', $._expression, '}'),
|
|
|
|
_expression: $ =>
|
|
choice(
|
|
$.heredoc,
|
|
$.array,
|
|
$.tuple,
|
|
$.shape,
|
|
$.collection,
|
|
$._literal,
|
|
$._variablish,
|
|
$.prefixed_string,
|
|
$.parenthesized_expression,
|
|
$.binary_expression,
|
|
$.prefix_unary_expression,
|
|
$.postfix_unary_expression,
|
|
$.is_expression,
|
|
$.as_expression,
|
|
$.awaitable_expression,
|
|
$.yield_expression,
|
|
$.cast_expression,
|
|
$.ternary_expression,
|
|
$.lambda_expression,
|
|
$.call_expression,
|
|
$.selection_expression,
|
|
$.new_expression,
|
|
$.include_expression,
|
|
$.require_expression,
|
|
$.anonymous_function_expression,
|
|
$.xhp_expression,
|
|
$.function_pointer,
|
|
$.enum_class_label,
|
|
),
|
|
|
|
// Statements
|
|
|
|
empty_statement: $ => ';',
|
|
|
|
expression_statement: $ => seq($._expression, ';'),
|
|
|
|
compound_statement: $ => seq('{', rep($._statement), '}'),
|
|
|
|
return_statement: $ => seq('return', opt($._expression), ';'),
|
|
|
|
break_statement: $ => seq('break', opt($._expression), ';'),
|
|
|
|
continue_statement: $ => seq('continue', opt($._expression), ';'),
|
|
|
|
throw_statement: $ => seq('throw', $._expression, ';'),
|
|
|
|
echo_statement: $ => seq('echo', com($._expression), ';'),
|
|
|
|
unset_statement: $ => seq('unset', '(', opt(com($._variablish)), ')', ';'),
|
|
|
|
concurrent_statement: $ => seq('concurrent', $.compound_statement),
|
|
|
|
use_statement: $ =>
|
|
seq(
|
|
'use',
|
|
choice(
|
|
com($.use_clause, ','),
|
|
seq(
|
|
opt($.use_type),
|
|
$._namespace_identifier,
|
|
'{',
|
|
com($.use_clause, ','),
|
|
'}',
|
|
),
|
|
),
|
|
';',
|
|
),
|
|
|
|
use_type: $ => choice('namespace', 'function', 'type', 'const'),
|
|
|
|
use_clause: $ =>
|
|
seq(
|
|
opt($.use_type),
|
|
$._namespace_identifier,
|
|
field('alias', opt(seq('as', $.identifier))),
|
|
),
|
|
|
|
_namespace_identifier: $ =>
|
|
choice(seq($.qualified_identifier, opt('\\')), '\\'),
|
|
|
|
if_statement: $ =>
|
|
prec.right(
|
|
-1,
|
|
seq(
|
|
'if',
|
|
field('condition', $.parenthesized_expression),
|
|
field('body', $._statement),
|
|
rep(
|
|
seq(
|
|
// Match else-if and elseif so long if-statements don't result in deeply nested
|
|
// nodes. Are there drawbacks?
|
|
choice('elseif', seq('else', 'if')),
|
|
field('condition', $.parenthesized_expression),
|
|
field('body', $._statement),
|
|
),
|
|
),
|
|
field('else', opt(seq('else', $._statement))),
|
|
),
|
|
),
|
|
|
|
switch_statement: $ =>
|
|
seq(
|
|
'switch',
|
|
field('value', $.parenthesized_expression),
|
|
seq('{', rep(choice($.switch_case, $.switch_default)), '}'),
|
|
),
|
|
|
|
switch_case: $ =>
|
|
seq('case', field('value', $._expression), ':', rep($._statement)),
|
|
|
|
switch_default: $ => seq('default', ':', rep($._statement)),
|
|
|
|
foreach_statement: $ =>
|
|
seq(
|
|
'foreach',
|
|
'(',
|
|
field('collection', $._expression),
|
|
opt($.await_modifier),
|
|
token(prec.dynamic(1, 'as')),
|
|
field('key', opt(seq($._variablish, '=>'))),
|
|
field('value', $._variablish),
|
|
')',
|
|
field('body', $._statement),
|
|
),
|
|
|
|
while_statement: $ =>
|
|
seq(
|
|
'while',
|
|
field('condition', $.parenthesized_expression),
|
|
field('body', $._statement),
|
|
),
|
|
|
|
do_statement: $ =>
|
|
seq(
|
|
'do',
|
|
field('body', $._statement),
|
|
'while',
|
|
field('condition', $.parenthesized_expression),
|
|
';',
|
|
),
|
|
|
|
for_statement: $ =>
|
|
seq(
|
|
'for',
|
|
'(',
|
|
opt(com($._expression)),
|
|
';',
|
|
opt(com($._expression)),
|
|
';',
|
|
opt(com($._expression)),
|
|
')',
|
|
field('body', $._statement),
|
|
),
|
|
|
|
try_statement: $ =>
|
|
seq(
|
|
'try',
|
|
field('body', $.compound_statement),
|
|
rep($.catch_clause),
|
|
choice($.catch_clause, $.finally_clause),
|
|
),
|
|
|
|
catch_clause: $ =>
|
|
seq(
|
|
'catch',
|
|
'(',
|
|
field('type', $._type),
|
|
field('name', $.variable),
|
|
')',
|
|
field('body', $.compound_statement),
|
|
),
|
|
|
|
finally_clause: $ => seq('finally', field('body', $.compound_statement)),
|
|
|
|
using_statement: $ =>
|
|
prec.right(
|
|
-1,
|
|
seq(
|
|
opt($.await_modifier),
|
|
'using',
|
|
choice(
|
|
$.expression_statement,
|
|
seq('(', com($._expression), ')', choice($.compound_statement, ';')),
|
|
),
|
|
),
|
|
),
|
|
|
|
// Literals
|
|
|
|
_literal: $ => choice($.string, $.integer, $.float, $.true, $.false, $.null),
|
|
|
|
float: $ =>
|
|
token(
|
|
choice(
|
|
// 1., 1.0 , 1.0E1, 1.E1
|
|
/\d+\.\d*([eE][+-]?\d+)?/,
|
|
|
|
// .1, 0.1 , 0.1E1, .1E1
|
|
/\d*\.\d+([eE][+-]?\d+)?/,
|
|
|
|
// 1E1
|
|
/\d+[eE][+-]?\d+/,
|
|
),
|
|
),
|
|
|
|
integer: $ =>
|
|
token(
|
|
choice(
|
|
// Integer
|
|
/[1-9]\d*/,
|
|
// Octal - Hack seems to accept non octal literals like 09. Intentional?
|
|
/0[0-7]*/,
|
|
// Hex
|
|
/0[xX][0-9a-fA-F]+/,
|
|
// Binary
|
|
/0[bB][01]+/,
|
|
),
|
|
),
|
|
|
|
// Tree-sitter confuses boolean /(true|false)/ with identifier. Don't know why.
|
|
true: $ => choice('true', 'True', 'TRUE'),
|
|
false: $ => choice('false', 'False', 'FALSE'),
|
|
|
|
null: $ => choice('null', 'Null', 'NULL'),
|
|
|
|
// prettier-ignore
|
|
string: $ =>
|
|
token(
|
|
choice(
|
|
/'(\\'|\\\\|\\?[^'\\])*'/,
|
|
/"(\\"|\\\\|\\?[^"\\])*"/,
|
|
),
|
|
),
|
|
|
|
prefixed_string: $ => seq(field('prefix', $.identifier), $.string),
|
|
|
|
// Types
|
|
|
|
_type: $ =>
|
|
choice(
|
|
$.type_specifier,
|
|
$.type_constant,
|
|
$.shape_type_specifier,
|
|
$.function_type_specifier,
|
|
$.tuple_type_specifier,
|
|
),
|
|
|
|
type_specifier: $ =>
|
|
seq(
|
|
rep($._type_modifier),
|
|
choice(
|
|
$._primitive_type,
|
|
$.qualified_identifier,
|
|
$._collection_type,
|
|
$._xhp_identifier,
|
|
),
|
|
opt($.type_arguments),
|
|
),
|
|
|
|
_type_modifier: $ =>
|
|
choice(
|
|
alias('@', $.soft_modifier),
|
|
alias('?', $.nullable_modifier),
|
|
alias('~', $.like_modifier),
|
|
),
|
|
|
|
tuple_type_specifier: $ =>
|
|
seq(rep($._type_modifier), '(', com($._type, ','), ')'),
|
|
|
|
function_type_specifier: $ =>
|
|
seq(
|
|
rep($._type_modifier),
|
|
'(',
|
|
'function',
|
|
/\s*/,
|
|
'(',
|
|
opt(com(opt($.inout_modifier), $._type, opt($.variadic_modifier), ',')),
|
|
')',
|
|
opt($.capability_list),
|
|
':',
|
|
field('return_type', $._type),
|
|
')',
|
|
),
|
|
|
|
shape_type_specifier: $ =>
|
|
seq(
|
|
rep($._type_modifier),
|
|
'shape',
|
|
'(',
|
|
opt(com(choice($.field_specifier, alias('...', $.open_modifier)), ',')),
|
|
')',
|
|
),
|
|
|
|
field_specifier: $ =>
|
|
seq(opt(alias('?', $.optional_modifier)), $._expression, '=>', $._type),
|
|
|
|
type_constant: $ =>
|
|
seq(rep($._type_modifier), alias($._type_constant, $.type_constant)),
|
|
|
|
_type_constant: $ =>
|
|
seq(
|
|
choice($.qualified_identifier, alias($._type_constant, $.type_constant)),
|
|
'::',
|
|
$.identifier,
|
|
),
|
|
|
|
_collection_type: $ =>
|
|
choice('array', 'varray', 'darray', 'vec', 'dict', 'keyset'),
|
|
|
|
_primitive_type: $ =>
|
|
choice(
|
|
'bool',
|
|
'float',
|
|
'int',
|
|
'string',
|
|
'arraykey',
|
|
'void',
|
|
'nonnull',
|
|
$.null,
|
|
'mixed',
|
|
'dynamic',
|
|
'noreturn',
|
|
),
|
|
|
|
type_arguments: $ => seq('<', opt(com($._type, ',')), '>'),
|
|
|
|
type_parameters: $ => seq('<', com($.type_parameter, ','), '>'),
|
|
|
|
type_parameter: $ =>
|
|
seq(
|
|
opt($.attribute_modifier),
|
|
opt(
|
|
choice(
|
|
alias('+', $.covariant_modifier),
|
|
alias('-', $.contravariant_modifier),
|
|
alias('reify', $.reify_modifier),
|
|
),
|
|
),
|
|
field('name', $.identifier),
|
|
rep(
|
|
seq(
|
|
field('constraint_operator', choice('as', 'super')),
|
|
field('constraint_type', $._type),
|
|
),
|
|
),
|
|
),
|
|
|
|
where_clause: $ => seq('where', rep1(seq($.where_constraint, opt(',')))),
|
|
|
|
where_constraint: $ =>
|
|
seq(
|
|
// Weird that Hack allows the left operand to be a full on type.
|
|
field('constraint_left_type', $._type),
|
|
field('constraint_operator', choice('as', 'super', '=')),
|
|
field('constraint_right_type', $._type),
|
|
),
|
|
|
|
// Collections
|
|
|
|
array: $ =>
|
|
seq(
|
|
alias($._collection_type, $.array_type),
|
|
opt($.type_arguments),
|
|
'[',
|
|
opt(com(choice($._expression, $.element_initializer), ',')),
|
|
']',
|
|
),
|
|
|
|
element_initializer: $ => prec.right(seq($._expression, '=>', $._expression)),
|
|
|
|
tuple: $ => seq('tuple', '(', opt(com($._expression, ',')), ')'),
|
|
|
|
shape: $ => seq('shape', '(', opt(com($.field_initializer, ',')), ')'),
|
|
|
|
field_initializer: $ =>
|
|
seq(choice($.string, $.scoped_identifier), '=>', $._expression),
|
|
|
|
collection: $ =>
|
|
seq(
|
|
$.qualified_identifier,
|
|
'{',
|
|
opt(com(choice($._expression, $.element_initializer), ',')),
|
|
'}',
|
|
),
|
|
|
|
// Expressions
|
|
|
|
include_expression: $ =>
|
|
prec.include(seq(choice('include', 'include_once'), $._expression)),
|
|
|
|
require_expression: $ =>
|
|
prec.include(seq(choice('require', 'require_once'), $._expression)),
|
|
|
|
parenthesized_expression: $ => seq('(', $._expression, ')'),
|
|
|
|
subscript_expression: $ =>
|
|
prec.subscript(seq($._expression, '[', opt($._expression), ']')),
|
|
|
|
list_expression: $ => seq('list', '(', com(opt($._expression), ','), ')'),
|
|
|
|
binary_expression: $ =>
|
|
choice(
|
|
...[
|
|
'|>',
|
|
'??',
|
|
'||',
|
|
'&&',
|
|
'|',
|
|
'^',
|
|
'&',
|
|
'==',
|
|
'!=',
|
|
'===',
|
|
'!==',
|
|
'<',
|
|
'>',
|
|
'<=',
|
|
'>=',
|
|
'<=>',
|
|
'<<',
|
|
'>>',
|
|
'+',
|
|
'-',
|
|
'.',
|
|
'*',
|
|
'/',
|
|
'%',
|
|
'**',
|
|
|
|
'?:',
|
|
|
|
'=',
|
|
'??=',
|
|
'.=',
|
|
'|=',
|
|
'^=',
|
|
'&=',
|
|
'<<=',
|
|
'>>=',
|
|
'+=',
|
|
'-=',
|
|
'*=',
|
|
'/=',
|
|
'%=',
|
|
'**=',
|
|
].map(operator =>
|
|
prec[operator](
|
|
seq(
|
|
field('left', $._expression),
|
|
field('operator', operator),
|
|
field('right', $._expression),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
prefix_unary_expression: $ =>
|
|
choice(
|
|
...[
|
|
['!', prec.unary],
|
|
['~', prec.unary],
|
|
['-', prec.unary],
|
|
['+', prec.unary],
|
|
['++', prec.prefix],
|
|
['--', prec.prefix],
|
|
['print', prec.print],
|
|
['clone', prec.clone],
|
|
['await', prec.await],
|
|
['@', prec.error],
|
|
].map(([operator, prec]) =>
|
|
prec(seq(field('operator', operator), field('operand', $._expression))),
|
|
),
|
|
),
|
|
|
|
postfix_unary_expression: $ =>
|
|
prec.postfix(seq($._expression, choice('++', '--'))),
|
|
|
|
is_expression: $ =>
|
|
prec.is(seq(field('left', $._expression), 'is', field('right', $._type))),
|
|
|
|
as_expression: $ =>
|
|
prec.as(
|
|
seq(
|
|
field('left', $._expression),
|
|
choice(token(prec.dynamic(-1, 'as')), '?as'),
|
|
field('right', $._type),
|
|
),
|
|
),
|
|
|
|
awaitable_expression: $ => seq('async', $.compound_statement),
|
|
|
|
yield_expression: $ =>
|
|
prec.right(seq('yield', choice($._expression, $.element_initializer))),
|
|
|
|
cast_expression: $ =>
|
|
prec.cast(
|
|
seq(
|
|
'(',
|
|
field('type', choice('array', 'int', 'float', 'string', 'bool')),
|
|
')',
|
|
field('value', $._expression),
|
|
),
|
|
),
|
|
|
|
ternary_expression: $ =>
|
|
prec.ternary(
|
|
seq(
|
|
field('condition', $._expression),
|
|
'?',
|
|
field('consequence', $._expression),
|
|
':',
|
|
field('alternative', $._expression),
|
|
),
|
|
),
|
|
|
|
lambda_expression: $ =>
|
|
seq(
|
|
opt($.attribute_modifier),
|
|
opt($.async_modifier),
|
|
choice(
|
|
// Make a single-parameter lambda node look like any other lambda node.
|
|
alias($._single_parameter_parameters, $.parameters),
|
|
seq(
|
|
$.parameters,
|
|
opt($.capability_list),
|
|
opt(seq(':', field('return_type', $._type))),
|
|
),
|
|
),
|
|
'==>',
|
|
field('body', choice($._expression, $.compound_statement)),
|
|
),
|
|
|
|
_single_parameter_parameters: $ => alias($._single_parameter, $.parameter),
|
|
|
|
_single_parameter: $ => field('name', $.variable),
|
|
|
|
call_expression: $ =>
|
|
prec.paren(
|
|
seq(
|
|
field('function', choice($._expression, $._collection_type)),
|
|
opt($.type_arguments),
|
|
$.arguments,
|
|
),
|
|
),
|
|
|
|
new_expression: $ =>
|
|
prec.new(seq('new', $._variablish, opt($.type_arguments), $.arguments)),
|
|
|
|
arguments: $ => seq('(', opt(com($.argument, ',')), ')'),
|
|
|
|
argument: $ =>
|
|
seq(opt(choice($.inout_modifier, $.variadic_modifier)), $._expression),
|
|
|
|
selection_expression: $ =>
|
|
prec.select(
|
|
seq(
|
|
choice($._variablish, $.as_expression),
|
|
field('selection_operator', choice('?->', '->')),
|
|
choice(
|
|
$._variablish,
|
|
$.braced_expression,
|
|
alias($._keyword, $.identifier),
|
|
),
|
|
),
|
|
),
|
|
|
|
// Declarations
|
|
|
|
alias_declaration: $ =>
|
|
seq(
|
|
opt($.attribute_modifier),
|
|
choice('type', 'newtype'),
|
|
$.identifier,
|
|
opt($.type_parameters),
|
|
field('as', opt(seq('as', $._type))),
|
|
'=',
|
|
$._type,
|
|
';',
|
|
),
|
|
|
|
function_declaration: $ =>
|
|
seq(
|
|
opt($.attribute_modifier),
|
|
$._function_declaration_header,
|
|
choice(field('body', $.compound_statement), ';'),
|
|
),
|
|
|
|
_function_declaration_header: $ =>
|
|
seq(
|
|
opt($.async_modifier),
|
|
'function',
|
|
field('name', $.identifier),
|
|
opt($.type_parameters),
|
|
$.parameters,
|
|
opt($.capability_list),
|
|
opt(seq(':', opt($.attribute_modifier), field('return_type', $._type))),
|
|
opt($.where_clause),
|
|
),
|
|
|
|
capability_list: $ => seq('[', opt(com($.capability)), ']'),
|
|
|
|
capability: $ =>
|
|
choice(
|
|
seq($.identifier, opt($.type_parameters)),
|
|
$.scoped_identifier,
|
|
seq('ctx', $.variable),
|
|
),
|
|
|
|
parameters: $ =>
|
|
prec.paren(
|
|
seq('(', opt(choice($.variadic_modifier, com($.parameter, ','))), ')'),
|
|
),
|
|
|
|
parameter: $ =>
|
|
seq(
|
|
opt($.attribute_modifier),
|
|
opt($.visibility_modifier),
|
|
opt($.inout_modifier),
|
|
field('type', opt($._type)),
|
|
opt($.variadic_modifier),
|
|
field('name', $.variable),
|
|
opt(seq('=', field('default_value', $._expression))),
|
|
),
|
|
|
|
trait_declaration: $ =>
|
|
seq(
|
|
opt($.attribute_modifier),
|
|
'trait',
|
|
field('name', $.identifier),
|
|
opt($.type_parameters),
|
|
opt($.implements_clause),
|
|
opt($.where_clause),
|
|
field('body', $.member_declarations),
|
|
),
|
|
|
|
interface_declaration: $ =>
|
|
seq(
|
|
opt($.attribute_modifier),
|
|
'interface',
|
|
field('name', $.identifier),
|
|
opt($.type_parameters),
|
|
opt($.extends_clause),
|
|
opt($.where_clause),
|
|
field('body', $.member_declarations),
|
|
),
|
|
|
|
class_declaration: $ =>
|
|
seq(
|
|
opt($.attribute_modifier),
|
|
opt($._class_modifier),
|
|
opt($._class_modifier),
|
|
opt($.xhp_modifier),
|
|
'class',
|
|
field('name', choice($.identifier, $._xhp_identifier)),
|
|
opt($.type_parameters),
|
|
opt($.extends_clause),
|
|
opt($.implements_clause),
|
|
opt($.where_clause),
|
|
field('body', $.member_declarations),
|
|
),
|
|
|
|
member_declarations: $ =>
|
|
seq(
|
|
'{',
|
|
rep(
|
|
choice(
|
|
alias($._class_const_declaration, $.const_declaration),
|
|
$.method_declaration,
|
|
$.property_declaration,
|
|
$.type_const_declaration,
|
|
$.context_const_declaration,
|
|
$.trait_use_clause,
|
|
$.require_implements_clause,
|
|
$.require_extends_clause,
|
|
$.xhp_attribute_declaration,
|
|
$.xhp_children_declaration,
|
|
$.xhp_category_declaration,
|
|
),
|
|
),
|
|
'}',
|
|
),
|
|
|
|
trait_use_clause: $ =>
|
|
seq(
|
|
'use',
|
|
com($._type),
|
|
choice(
|
|
seq(
|
|
'{',
|
|
rep(seq(choice($.trait_select_clause, $.trait_alias_clause), ';')),
|
|
'}',
|
|
),
|
|
';',
|
|
),
|
|
),
|
|
|
|
trait_select_clause: $ =>
|
|
seq(
|
|
$.qualified_identifier,
|
|
'::',
|
|
$.identifier,
|
|
'insteadof',
|
|
com($.qualified_identifier),
|
|
),
|
|
|
|
trait_alias_clause: $ =>
|
|
seq(
|
|
$.identifier,
|
|
'as',
|
|
choice(
|
|
seq($.visibility_modifier, opt($.identifier)),
|
|
seq(opt($.visibility_modifier), $.identifier),
|
|
),
|
|
),
|
|
|
|
extends_clause: $ => seq('extends', com($._type)),
|
|
|
|
implements_clause: $ => seq('implements', com($._type)),
|
|
|
|
require_extends_clause: $ => seq('require', 'extends', com($._type), ';'),
|
|
|
|
require_implements_clause: $ =>
|
|
seq('require', 'implements', com($._type), ';'),
|
|
|
|
method_declaration: $ =>
|
|
seq(
|
|
opt($.attribute_modifier),
|
|
rep($._member_modifier),
|
|
$._function_declaration_header,
|
|
choice(field('body', $.compound_statement), ';'),
|
|
),
|
|
|
|
_class_const_declaration: $ =>
|
|
seq(
|
|
rep($._member_modifier),
|
|
'const',
|
|
field('type', opt($._type)),
|
|
com(alias($._class_const_declarator, $.const_declarator)),
|
|
';',
|
|
),
|
|
|
|
_class_const_declarator: $ =>
|
|
seq(
|
|
field('name', choice($.identifier, alias($._keyword, $.identifier))),
|
|
field(
|
|
'value',
|
|
// The only reason we need a separate const declarator for classes is that
|
|
// the assignment expression is optional.
|
|
opt(seq('=', $._expression)),
|
|
),
|
|
),
|
|
|
|
type_const_declaration: $ =>
|
|
seq(
|
|
opt($.attribute_modifier),
|
|
rep($._member_modifier),
|
|
'const',
|
|
'type',
|
|
field('name', $.identifier),
|
|
opt($.type_parameters),
|
|
field('as', opt(seq('as', $._type))),
|
|
field('type', opt(seq('=', $._type))),
|
|
';',
|
|
),
|
|
|
|
context_const_declaration: $ =>
|
|
seq(
|
|
opt($.abstract_modifier),
|
|
'const',
|
|
'ctx',
|
|
field('name', $.identifier),
|
|
field('super', opt(seq('super', $.capability_list))),
|
|
field('as', opt(seq('as', $.capability_list))),
|
|
field('context', opt(seq('=', $.capability_list))),
|
|
';',
|
|
),
|
|
|
|
const_declaration: $ =>
|
|
seq('const', field('type', opt($._type)), com($.const_declarator), ';'),
|
|
|
|
const_declarator: $ =>
|
|
seq(
|
|
field('name', choice($.identifier, alias($._keyword, $.identifier))),
|
|
field('value', seq('=', $._expression)),
|
|
),
|
|
|
|
property_declaration: $ =>
|
|
seq(
|
|
opt($.attribute_modifier),
|
|
rep($._member_modifier),
|
|
field('type', opt($._type)),
|
|
com($.property_declarator),
|
|
';',
|
|
),
|
|
|
|
property_declarator: $ =>
|
|
seq(
|
|
field('name', $.variable),
|
|
field('value', opt(seq('=', $._expression))),
|
|
),
|
|
|
|
enum_declaration: $ =>
|
|
seq(
|
|
opt($.attribute_modifier),
|
|
'enum',
|
|
field('name', $.identifier),
|
|
':',
|
|
field('type', $._type),
|
|
field('as', opt(seq('as', $._type))),
|
|
'{',
|
|
rep($.enumerator),
|
|
'}',
|
|
),
|
|
/**
|
|
* Abstract enum classes have their own oddities. In particular they
|
|
* can contain abstract members.
|
|
*/
|
|
abstract_enum_class_declaration: $ =>
|
|
seq(
|
|
opt($.attribute_modifier),
|
|
'abstract',
|
|
'enum',
|
|
'class',
|
|
field('name', $.identifier),
|
|
':',
|
|
opt($.extends_clause),
|
|
field('type', $._type),
|
|
'{',
|
|
rep(
|
|
choice($.typed_enumerator, seq('abstract', $._type, $.identifier, ';')),
|
|
),
|
|
'}',
|
|
),
|
|
/**
|
|
* Enum classes require their own rule, as they contain somewhat different
|
|
* syntax from enums. In particular cannot contain the `as` keyword.
|
|
*/
|
|
enum_class_declaration: $ =>
|
|
seq(
|
|
opt($.attribute_modifier),
|
|
'enum',
|
|
'class',
|
|
field('name', $.identifier),
|
|
':',
|
|
field('type', $._type),
|
|
opt($.extends_clause),
|
|
'{',
|
|
rep($.typed_enumerator),
|
|
'}',
|
|
),
|
|
|
|
enum_class_label: $ =>
|
|
seq(field('enum_class', opt($.qualified_identifier)), '#', $.identifier),
|
|
|
|
enumerator: $ => seq($.identifier, '=', $._expression, ';'),
|
|
|
|
typed_enumerator: $ => seq($._type, $.enumerator),
|
|
|
|
namespace_declaration: $ =>
|
|
prec.right(
|
|
seq(
|
|
'namespace',
|
|
opt(
|
|
choice(
|
|
seq(field('name', $.qualified_identifier), ';'),
|
|
seq(
|
|
opt(field('name', $.qualified_identifier)),
|
|
field('body', $.compound_statement),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
// Modifiers
|
|
|
|
_member_modifier: $ =>
|
|
choice(
|
|
$.visibility_modifier,
|
|
$.static_modifier,
|
|
$.abstract_modifier,
|
|
$.final_modifier,
|
|
),
|
|
|
|
_class_modifier: $ => choice($.abstract_modifier, $.final_modifier),
|
|
|
|
final_modifier: $ => 'final',
|
|
|
|
abstract_modifier: $ => 'abstract',
|
|
|
|
xhp_modifier: $ => 'xhp',
|
|
|
|
static_modifier: $ => 'static',
|
|
|
|
visibility_modifier: $ => choice('public', 'protected', 'private'),
|
|
|
|
attribute_modifier: $ =>
|
|
seq('<<', com($.qualified_identifier, opt($.arguments), ','), '>>'),
|
|
|
|
inout_modifier: $ => 'inout',
|
|
|
|
variadic_modifier: $ => '...',
|
|
|
|
async_modifier: $ => 'async',
|
|
|
|
await_modifier: $ => 'await',
|
|
|
|
// XHP
|
|
|
|
xhp_identifier: $ => /[a-zA-Z_][a-zA-Z0-9_]*([-:][a-zA-Z0-9_]+)*/,
|
|
|
|
xhp_class_identifier: $ => /:[a-zA-Z_][a-zA-Z0-9_]*([-:][a-zA-Z0-9_]+)*/,
|
|
|
|
_xhp_identifier: $ => choice($.xhp_identifier, $.xhp_class_identifier),
|
|
|
|
xhp_category_identifier: $ => /%[a-zA-Z_][a-zA-Z0-9_]*([-:][a-zA-Z0-9_]+)*/,
|
|
|
|
xhp_expression: $ =>
|
|
choice(
|
|
$.xhp_open_close,
|
|
seq(
|
|
$.xhp_open,
|
|
rep(
|
|
choice(
|
|
$.xhp_string,
|
|
$.xhp_comment,
|
|
$.braced_expression,
|
|
$.xhp_expression,
|
|
),
|
|
),
|
|
$.xhp_close,
|
|
),
|
|
),
|
|
|
|
xhp_comment: $ => token(seq('<!--', /(-?>)?([^>]|[^-]>|[^-]->)*/, '-->')),
|
|
|
|
xhp_string: $ => token(prec(1, /[^<{]+/)),
|
|
|
|
xhp_open: $ => seq('<', $._xhp_identifier, rep($.xhp_attribute), '>'),
|
|
|
|
xhp_open_close: $ => seq('<', $._xhp_identifier, rep($.xhp_attribute), '/>'),
|
|
|
|
xhp_close: $ => seq('</', $._xhp_identifier, '>'),
|
|
|
|
xhp_attribute: $ =>
|
|
choice(
|
|
seq($.xhp_identifier, '=', choice($.string, $.braced_expression)),
|
|
choice($.braced_expression, $.xhp_spread_expression),
|
|
),
|
|
|
|
xhp_spread_expression: $ => seq('{', '...', $._expression, '}'),
|
|
|
|
xhp_attribute_declaration: $ =>
|
|
seq('attribute', com($.xhp_class_attribute), ';'),
|
|
|
|
xhp_class_attribute: $ =>
|
|
seq(
|
|
field('type', choice($._type, $.xhp_enum_type)),
|
|
field('name', opt($.xhp_identifier)),
|
|
opt(seq('=', field('default', $._expression))),
|
|
opt(choice('@required', '@lateinit')),
|
|
),
|
|
|
|
xhp_enum_type: $ =>
|
|
seq('enum', '{', com(choice($.string, $.integer), ','), '}'),
|
|
|
|
_xhp_attribute_expression: $ =>
|
|
choice(
|
|
$.xhp_identifier,
|
|
$.xhp_class_identifier,
|
|
$.xhp_category_identifier,
|
|
alias($._xhp_binary_expression, $.binary_expression),
|
|
alias($._xhp_postfix_unary_expression, $.postfix_unary_expression),
|
|
alias($._xhp_parenthesized_expression, $.parenthesized_expression),
|
|
),
|
|
|
|
// We want function pointers to parse only as a "last resort" as there is
|
|
// ambiguity in expressions like
|
|
// ```
|
|
// foo<int, string>()
|
|
// ```
|
|
// which should parse as a function call, but could also parse as a function
|
|
// pointer _being_ called.
|
|
function_pointer: $ =>
|
|
prec.dynamic(
|
|
-1,
|
|
seq(
|
|
choice($.scoped_identifier, $.qualified_identifier),
|
|
$.type_arguments,
|
|
),
|
|
),
|
|
|
|
// Misc
|
|
|
|
comment: $ =>
|
|
token(choice(seq('//', /.*/), seq('/*', /[^*]*\*+([^/*][^*]*\*+)*/, '/'))),
|
|
|
|
// Future Deprecations
|
|
|
|
anonymous_function_expression: $ =>
|
|
seq(
|
|
opt($.async_modifier),
|
|
'function',
|
|
$.parameters,
|
|
opt($.capability_list),
|
|
opt(seq(':', field('return_type', $._type))),
|
|
opt(alias($._anonymous_function_use_clause, $.use_clause)),
|
|
field('body', $.compound_statement),
|
|
),
|
|
|
|
_anonymous_function_use_clause: $ =>
|
|
seq('use', '(', com($.variable, ','), ')'),
|
|
|
|
// See https://github.com/hhvm/xhp-lib/pull/276 for deprecation info
|
|
xhp_children_declaration: $ =>
|
|
seq('children', com($._xhp_attribute_expression), ';'),
|
|
|
|
xhp_category_declaration: $ =>
|
|
seq('category', com($.xhp_category_identifier), ';'),
|
|
|
|
_xhp_binary_expression: $ =>
|
|
prec.left(
|
|
seq($._xhp_attribute_expression, '|', $._xhp_attribute_expression),
|
|
),
|
|
|
|
_xhp_postfix_unary_expression: $ =>
|
|
prec(1, seq($._xhp_attribute_expression, choice('+', '*', '?'))),
|
|
|
|
_xhp_parenthesized_expression: $ =>
|
|
seq('(', com($._xhp_attribute_expression), ')'),
|
|
};
|
|
|
|
/**
|
|
* Comma separated rules. A ',' as the last argument indicates an optional trailing comma.
|
|
*/
|
|
function com(...rules) {
|
|
if (rules[rules.length - 1] == ',') {
|
|
rules.splice(-1, 1);
|
|
return seq(...rules, repeat(seq(',', ...rules)), optional(','));
|
|
} else {
|
|
return seq(...rules, repeat(seq(',', ...rules)));
|
|
}
|
|
}
|
|
|
|
const opt = optional;
|
|
const rep = repeat;
|
|
const rep1 = repeat1;
|
|
|
|
module.exports = grammar({
|
|
rules,
|
|
|
|
name: 'hack',
|
|
|
|
word: $ => $.identifier,
|
|
|
|
extras: $ => [/\s/, $.comment],
|
|
|
|
externals: $ => [
|
|
$._heredoc_start,
|
|
$._heredoc_start_newline,
|
|
$._heredoc_body,
|
|
$._heredoc_end_newline,
|
|
$._heredoc_end,
|
|
$._embedded_opening_brace,
|
|
],
|
|
|
|
supertypes: $ => [
|
|
$._statement,
|
|
$._declaration,
|
|
$._expression,
|
|
$._literal,
|
|
$._type,
|
|
],
|
|
|
|
inline: $ => [
|
|
$._statement,
|
|
$._declaration,
|
|
$._literal,
|
|
$._variablish,
|
|
$._class_modifier,
|
|
$._type,
|
|
$._primitive_type,
|
|
$._collection_type,
|
|
$._xhp_attribute_expression,
|
|
$._keyword,
|
|
$._xhp_identifier,
|
|
],
|
|
|
|
conflicts: $ => [
|
|
[$.binary_expression, $.call_expression],
|
|
[$.binary_expression, $.cast_expression, $.call_expression],
|
|
[$.binary_expression, $.prefix_unary_expression, $.call_expression],
|
|
[$._expression, $.parameter],
|
|
[$._expression, $.type_specifier],
|
|
[$._expression, $.type_specifier, $.function_pointer],
|
|
[$._expression, $.field_initializer],
|
|
[$._expression, $.function_pointer],
|
|
[$.scoped_identifier, $._type_constant],
|
|
[$.context_const_declaration, $._member_modifier],
|
|
[$.type_specifier],
|
|
[$.type_specifier, $.function_pointer],
|
|
[$.shape_type_specifier, $.shape],
|
|
[$.qualified_identifier],
|
|
[$.qualified_identifier, $.use_type],
|
|
[$.list_expression],
|
|
],
|
|
});
|