tree-sitter-c-sharp/grammar.js
2024-03-25 23:16:07 +01:00

2004 lines
51 KiB
JavaScript

const PREC = {
GENERIC: 19,
DOT: 18,
INVOCATION: 18,
POSTFIX: 18,
PREFIX: 17,
UNARY: 17,
CAST: 17,
RANGE: 16,
SWITCH: 15,
WITH: 14,
MULT: 13,
ADD: 12,
SHIFT: 11,
REL: 10,
EQUAL: 9,
AND: 8,
XOR: 7,
OR: 6,
LOGAND: 5,
LOGOR: 4,
COALESCING: 3,
COND: 2,
ASSIGN: 1,
SELECT: 0,
};
const decimalDigitSequence = /([0-9][0-9_]*[0-9]|[0-9])/;
const stringEncoding = /(u|U)8/;
module.exports = grammar({
name: 'c_sharp',
extras: $ => [
$.comment,
/[\s\u00A0\uFEFF\u3000]+/,
$._preprocessor_call
],
supertypes: $ => [
$._declaration,
$._expression,
$._statement,
$._type,
],
externals: $ => [
$._preproc_directive_end,
$._opt_semi,
],
conflicts: $ => [
[$.block, $.initializer_expression],
[$.event_declaration, $.variable_declarator],
[$.type_pattern, $.tuple_element],
[$._name, $._lvalue_expression],
[$._simple_name, $.type_parameter],
[$._simple_name, $.generic_name],
[$._simple_name, $.constructor_declaration],
[$._simple_name, $.name_colon],
[$._simple_name, $.tuple_pattern],
[$._simple_name, $.implicit_parameter],
[$.qualified_name, $.explicit_interface_specifier],
[$.qualified_name, $.member_access_expression],
[$._contextual_keywords, $.from_clause],
[$._contextual_keywords, $.global],
[$._contextual_keywords, $.type_parameter_constraint],
[$._contextual_keywords, $.modifier],
[$._contextual_keywords, $.scoped_type],
[$._contextual_keywords, $.scoped_type, $._parameter_type_with_modifiers],
[$._contextual_keywords, $._parameter_type_with_modifiers],
[$._contextual_keywords, $.implicit_type],
[$._type, $.attribute],
[$._type, $._nullable_base_type],
[$._type, $._array_base_type],
[$._type, $._pointer_base_type],
[$._type, $._ref_base_type],
[$._nullable_base_type, $.stack_alloc_array_creation_expression],
[$._array_base_type, $.stack_alloc_array_creation_expression],
[$._ref_base_type, $._array_base_type],
[$._ref_base_type, $._nullable_base_type],
[$._ref_base_type, $._pointer_base_type],
[$._ref_base_type, $._scoped_base_type],
[$._object_creation_type, $._array_base_type],
[$._object_creation_type, $._nullable_base_type],
[$._object_creation_type, $._pointer_base_type],
[$.array_creation_expression, $._array_base_type],
[$.array_creation_expression, $._nullable_base_type],
[$._parameter_type_with_modifiers, $.this_expression],
[$._parameter_type_with_modifiers, $.ref_type],
[$.parameter, $._simple_name],
[$.parameter, $.tuple_pattern],
[$.tuple_element, $.declaration_expression],
[$.tuple_element, $.variable_declarator],
[$.constant_pattern, $._name],
[$.constant_pattern, $._name, $._lvalue_expression],
[$.constant_pattern, $._non_lvalue_expression],
[$.constant_pattern, $._lvalue_expression],
[$.constant_pattern, $._expression_statement_expression],
[$.assignment_expression, $._expression],
],
inline: $ => [
$._identifier_or_global,
],
word: $ => $._identifier_token,
rules: {
compilation_unit: $ => seq(
repeat($.extern_alias_directive),
repeat($.using_directive),
repeat($.global_attribute_list),
choice(
seq(repeat($.global_statement), repeat($._namespace_member_declaration)),
$.file_scoped_namespace_declaration
)
),
global_statement: $ => $._statement,
_declaration: $ => choice(
$.class_declaration,
$.constructor_declaration,
$.conversion_operator_declaration,
$.delegate_declaration,
$.destructor_declaration,
$.enum_declaration,
$.event_declaration,
$.event_field_declaration,
$.field_declaration,
$.indexer_declaration,
$.interface_declaration,
$.method_declaration,
$.namespace_declaration,
$.operator_declaration,
$.property_declaration,
$.record_declaration,
$.record_struct_declaration,
$.struct_declaration,
$.using_directive,
),
_namespace_member_declaration: $ => choice(
$.namespace_declaration,
$._type_declaration
),
_type_declaration: $ => choice(
$.class_declaration,
$.struct_declaration,
$.interface_declaration,
$.enum_declaration,
$.delegate_declaration,
$.record_declaration,
$.record_struct_declaration
),
extern_alias_directive: $ => seq('extern', 'alias', field('name', $.identifier), ';'),
using_directive: $ => seq(
optional('global'),
'using',
optional(choice(
'static',
field('alias', $.name_equals)
)),
field('name', $._name),
';'
),
name_equals: $ => prec(1, seq($._identifier_or_global, '=')),
_name: $ => choice(
$.alias_qualified_name,
$.qualified_name,
$._simple_name
),
alias_qualified_name: $ => seq(field('alias', $._identifier_or_global), '::', field('name', $._simple_name)),
_simple_name: $ => choice(
$.generic_name,
$._identifier_or_global
),
generic_name: $ => seq(field('name', $.identifier), field('type_arguments', $.type_argument_list)),
// Intentionally different from Roslyn to avoid non-matching
// omitted_type_argument in a lot of unnecessary places.
type_argument_list: $ => prec.dynamic(PREC.GENERIC, seq(
'<',
choice(
repeat(','),
commaSep1($._type),
),
'>'
)),
qualified_name: $ => prec(PREC.DOT, seq(field('qualifier', $._name), '.', field('name', $._simple_name))),
attribute_list: $ => seq(
'[',
optional($.attribute_target_specifier),
commaSep1($.attribute),
optional(','),
']'
),
attribute_target_specifier: $ => seq(
choice('field', 'event', 'method', 'param', 'property', 'return', 'type'),
':'
),
attribute: $ => seq(
field('name', $._type_name),
optional($.attribute_argument_list)
),
attribute_argument_list: $ => seq(
'(',
commaSep($.attribute_argument),
')'
),
attribute_argument: $ => seq(
optional(choice($.name_equals, $.name_colon)),
$._expression
),
global_attribute_list: $ => seq(
'[',
choice('assembly', 'module'),
':',
commaSep($.attribute),
']'
),
name_colon: $ => seq(field('name', $._identifier_or_global), ':'),
event_field_declaration: $ => prec.dynamic(1, seq(
repeat($.attribute_list),
repeat($.modifier),
'event',
$.variable_declaration,
';'
)),
modifier: $ => prec.right(choice(
'abstract',
'async',
'const',
'extern',
'file',
'fixed',
'internal',
'new',
'override',
'partial',
'private',
'protected',
'public',
'readonly',
'required',
// 'ref', // `ref` as a modifier can only be used on struct declarations. Other than that it's a ref type or a ref parameter in a declaration.
// 'scoped', // `scoped` is either part of a scoped type or a scoped parameter. Both of which are handled outside of `modifier`.
'sealed',
'static',
'unsafe',
'virtual',
'volatile'
)),
variable_declaration: $ => seq(
field('type', $._type),
commaSep1($.variable_declarator)
),
variable_declarator: $ => seq(
choice(field('name', $.identifier), $.tuple_pattern),
optional($.bracketed_argument_list),
optional($.equals_value_clause)
),
bracketed_argument_list: $ => seq(
'[',
commaSep1($.argument),
']'
),
tuple_pattern: $ => seq(
'(',
commaSep1(choice(field('name', $.identifier), $.discard, $.tuple_pattern)),
')'
),
argument: $ => prec(1, seq(
optional(field('name', $.name_colon)),
optional(choice('ref', 'out', 'in')),
choice(
$._expression,
$.declaration_expression
)
)),
equals_value_clause: $ => seq('=', $._expression),
field_declaration: $ => seq(
repeat($.attribute_list),
repeat($.modifier),
$.variable_declaration,
';'
),
constructor_declaration: $ => seq(
repeat($.attribute_list),
repeat($.modifier),
field('name', $.identifier),
field('parameters', $.parameter_list),
optional($.constructor_initializer),
$._function_body
),
// Params varies quite a lot from grammar.txt as that handles neither 'out' nor 'params' or arrays...
parameter_list: $ => seq(
'(',
optional($._formal_parameter_list),
')'
),
implicit_parameter_list: $ => alias($.implicit_parameter, $.parameter),
implicit_parameter: $ => field('name', $.identifier),
_formal_parameter_list: $ => commaSep1(choice(
$.parameter,
$._parameter_array
)),
_parameter_type_with_modifiers: $ => seq(
alias(optional('this'), $.parameter_modifier),
alias(optional('scoped'), $.parameter_modifier),
alias(optional(choice('ref', 'out', 'in')), $.parameter_modifier),
field('type', $._ref_base_type),
),
parameter: $ => seq(
repeat($.attribute_list),
optional($._parameter_type_with_modifiers),
field('name', $.identifier),
optional($.equals_value_clause)
),
parameter_modifier: $ => choice('ref', 'out', 'this', 'in', 'scoped'),
_parameter_array: $ => seq(
repeat($.attribute_list),
'params',
field('type', choice($.array_type, $.nullable_type)),
field('name', $.identifier),
),
constructor_initializer: $ => seq(
':',
choice('base', 'this'),
$.argument_list
),
argument_list: $ => seq('(', commaSep($.argument), ')'),
block: $ => seq('{', repeat($._statement), '}'),
arrow_expression_clause: $ => seq('=>', $._expression),
conversion_operator_declaration: $ => seq(
repeat($.attribute_list),
repeat($.modifier),
choice(
'implicit',
'explicit'
),
optional($.explicit_interface_specifier),
'operator',
optional('checked'),
field('type', $._type),
field('parameters', $.parameter_list),
$._function_body,
),
_function_body: $ => choice(
field('body', $.block),
seq(field('body', $.arrow_expression_clause), ';'),
';' // Only applies to interfaces
),
destructor_declaration: $ => seq(
repeat($.attribute_list),
optional('extern'),
'~',
field('name', $.identifier),
field('parameters', $.parameter_list),
$._function_body
),
method_declaration: $ => seq(
repeat($.attribute_list),
repeat($.modifier),
field('type', $._type),
optional($.explicit_interface_specifier),
field('name', $.identifier),
field('type_parameters', optional($.type_parameter_list)),
field('parameters', $.parameter_list),
repeat($.type_parameter_constraints_clause),
$._function_body,
),
explicit_interface_specifier: $ => prec(PREC.DOT, seq(field('name', $._name), '.')),
type_parameter_list: $ => seq('<', commaSep1($.type_parameter), '>'),
type_parameter: $ => seq(
repeat($.attribute_list),
optional(choice('in', 'out')),
field('name', $.identifier),
),
type_parameter_constraints_clause: $ => seq(
'where',
field('target', $._identifier_or_global),
':',
field('constraints', commaSep1($.type_parameter_constraint)),
),
type_parameter_constraint: $ => choice(
seq('class', optional('?')),
'struct',
'notnull',
'unmanaged',
$.constructor_constraint,
$.type_constraint
),
constructor_constraint: $ => seq('new', '(', ')'),
type_constraint: $ => field('type', $._type),
operator_declaration: $ => seq(
repeat($.attribute_list),
repeat($.modifier),
field('type', $._type),
optional($.explicit_interface_specifier),
'operator',
optional('checked'),
field('operator', $._overloadable_operator),
field('parameters', $.parameter_list),
$._function_body,
),
_overloadable_operator: $ => choice(
'!',
'~',
'++',
'--',
'true',
'false',
'+', '-',
'*', '/',
'%', '^',
'|', '&',
'<<', '>>', '>>>',
'==', '!=',
'>', '<',
'>=', '<='
),
event_declaration: $ => seq(
repeat($.attribute_list),
repeat($.modifier),
'event',
field('type', $._type),
optional($.explicit_interface_specifier),
field('name', $.identifier),
choice(
field('accessors', $.accessor_list),
';'
)
),
accessor_list: $ => seq(
'{',
repeat($.accessor_declaration),
'}'
),
accessor_declaration: $ => seq(
repeat($.attribute_list),
repeat($.modifier),
field('name', choice('get', 'set', 'add', 'remove', 'init', $.identifier)),
$._function_body
),
indexer_declaration: $ => seq(
repeat($.attribute_list),
repeat($.modifier),
field('type', $._type),
optional($.explicit_interface_specifier),
'this',
field('parameters', $.bracketed_parameter_list),
choice(
field('accessors', $.accessor_list),
seq(field('value', $.arrow_expression_clause), ';')
)
),
bracketed_parameter_list: $ => seq('[', $._formal_parameter_list, ']'),
property_declaration: $ => seq(
repeat($.attribute_list),
repeat($.modifier),
field('type', $._type),
optional($.explicit_interface_specifier),
field('name', $.identifier),
choice(
seq(
field('accessors', $.accessor_list),
optional(seq('=', field('value', $._expression), ';'))
), // grammar.txt does not allow bodyless properties.
seq(
field('value', $.arrow_expression_clause),
';'
)
),
),
enum_declaration: $ => seq(
repeat($.attribute_list),
repeat($.modifier),
'enum',
field('name', $.identifier),
field('bases', optional($.base_list)),
field('body', $.enum_member_declaration_list),
$._opt_semi,
),
base_list: $ => seq(':', commaSep1($._type)),
enum_member_declaration_list: $ => seq(
'{',
commaSep($.enum_member_declaration),
optional(','),
'}',
),
enum_member_declaration: $ => seq(
repeat($.attribute_list),
field('name', $.identifier),
optional(seq('=', field('value', $._expression)))
),
class_declaration: $ => seq(
repeat($.attribute_list),
repeat($.modifier),
'class',
field('name', $.identifier),
field('type_parameters', optional($.type_parameter_list)),
field('bases', optional($.base_list)),
repeat($.type_parameter_constraints_clause),
field('body', $.declaration_list),
$._opt_semi,
),
declaration_list: $ => seq(
'{',
repeat($._declaration),
'}'
),
interface_declaration: $ => seq(
repeat($.attribute_list),
repeat($.modifier),
'interface',
field('name', $.identifier),
field('type_parameters', optional($.type_parameter_list)),
field('bases', optional($.base_list)),
repeat($.type_parameter_constraints_clause),
field('body', $.declaration_list),
$._opt_semi,
),
struct_declaration: $ => seq(
repeat($.attribute_list),
repeat($.modifier),
optional(alias('ref', $.modifier)),
'struct',
field('name', $.identifier),
field('type_parameters', optional($.type_parameter_list)),
field('bases', optional($.base_list)),
repeat($.type_parameter_constraints_clause),
field('body', $.declaration_list),
$._opt_semi,
),
delegate_declaration: $ => seq(
repeat($.attribute_list),
repeat($.modifier),
'delegate',
field('type', $._type),
field('name', $.identifier),
field('type_parameters', optional($.type_parameter_list)),
field('parameters', $.parameter_list),
repeat($.type_parameter_constraints_clause),
';'
),
record_declaration: $ => seq(
repeat($.attribute_list),
repeat($.modifier),
'record',
optional('class'),
field('name', $.identifier),
field('type_parameters', optional($.type_parameter_list)),
field('parameters', optional($.parameter_list)),
field('bases', optional(alias($.record_base, $.base_list))),
repeat($.type_parameter_constraints_clause),
field('body', $._record_body),
$._opt_semi,
),
record_struct_declaration: $ => seq(
repeat($.attribute_list),
repeat($.modifier),
'record',
'struct',
field('name', $.identifier),
field('type_parameters', optional($.type_parameter_list)),
field('parameters', optional($.parameter_list)),
field('bases', optional(alias($.record_base, $.base_list))),
repeat($.type_parameter_constraints_clause),
field('body', $._record_body),
$._opt_semi,
),
record_base: $ => choice(
seq(':', commaSep1($._type_name)),
seq(':', $.primary_constructor_base_type, optional(seq(',', commaSep1($._type_name)))),
),
primary_constructor_base_type: $ => seq(
field('type', $._type_name),
$.argument_list
),
_record_body: $ => choice(
$.declaration_list,
';'
),
namespace_declaration: $ => seq(
'namespace',
field('name', $._name),
field('body', $.declaration_list),
$._opt_semi,
),
file_scoped_namespace_declaration: $ => seq(
'namespace',
field('name', $._name),
';',
repeat($.extern_alias_directive),
repeat($.using_directive),
repeat($._type_declaration)
),
_type: $ => choice(
$.implicit_type,
$.array_type,
$._type_name,
$.nullable_type,
$.pointer_type,
$.function_pointer_type,
$.predefined_type,
$.tuple_type,
$.ref_type,
$.scoped_type,
),
_type_name: $ => prec.dynamic(0, // `var` could be `_type_name`, but we prefer `implicit_type`
$._name
),
implicit_type: $ => prec.dynamic(1, // Higher precedence than `_type_name`
'var'),
array_type: $ => seq(
field('type', $._array_base_type),
field('rank', $.array_rank_specifier)
),
_array_base_type: $ => choice(
$.array_type,
$._type_name,
$.nullable_type,
$.pointer_type,
$.function_pointer_type,
$.predefined_type,
$.tuple_type,
),
// grammar.txt marks this non-optional and includes omitted_array_size_expression in
// expression but we can't match empty rules.
array_rank_specifier: $ => seq('[', commaSep(optional($._expression)), ']'),
nullable_type: $ => seq(field('type', $._nullable_base_type), '?'),
_nullable_base_type: $ => choice(
$.array_type,
$._type_name,
$.predefined_type,
$.tuple_type
),
pointer_type: $ => seq(field('type', $._pointer_base_type), '*'),
_pointer_base_type: $ => choice(
$._type_name,
$.nullable_type,
$.pointer_type,
$.function_pointer_type,
$.predefined_type,
$.tuple_type,
),
function_pointer_type: $ => seq(
'delegate',
'*',
optional($.function_pointer_calling_convention),
'<',
repeat(seq($.function_pointer_parameter, ',')),
alias($.function_pointer_return_type, $.function_pointer_parameter),
'>'
),
function_pointer_calling_convention: $ => choice(
'managed',
seq(
'unmanaged',
optional($.function_pointer_unmanaged_calling_convention_list)
)
),
function_pointer_unmanaged_calling_convention_list: $ => seq(
'[', commaSep1($.function_pointer_unmanaged_calling_convention), ']'
),
function_pointer_unmanaged_calling_convention: $ => choice(
'Cdecl',
'Stdcall',
'Thiscall',
'Fastcall',
$.identifier
),
function_pointer_parameter: $ => seq(
optional(alias(choice('ref', 'out', 'in'), $.parameter_modifier)),
field('type', $._ref_base_type)
),
function_pointer_return_type: $ => field('type', $._type),
predefined_type: $ => token(choice(
'bool',
'byte',
'char',
'decimal',
'double',
'float',
'int',
'long',
'object',
'sbyte',
'short',
'string',
'uint',
'ulong',
'ushort',
'nint',
'nuint',
'void'
)),
ref_type: $ => seq(
'ref',
optional('readonly'),
field('type', $._ref_base_type)
),
_ref_base_type: $ => choice(
$.implicit_type,
$.array_type,
$._type_name,
$.nullable_type,
$.pointer_type,
$.function_pointer_type,
$.predefined_type,
$.tuple_type
),
scoped_type: $ => seq(
'scoped',
field('type', $._scoped_base_type)
),
_scoped_base_type: $ => choice(
$._type_name,
$.ref_type,
),
tuple_type: $ => seq(
'(',
commaSep2($.tuple_element),
')'
),
tuple_element: $ => prec.left(seq(
field('type', $._type),
field('name', optional($.identifier))
)),
_statement: $ => choice(
$.block,
$.break_statement,
$.checked_statement,
$.continue_statement,
$.do_statement,
$.empty_statement,
$.expression_statement,
$.fixed_statement,
$.for_each_statement,
$.for_statement,
$.goto_statement,
$.if_statement,
$.labeled_statement,
$.local_declaration_statement,
$.local_function_statement,
$.lock_statement,
$.return_statement,
$.switch_statement,
$.throw_statement,
$.try_statement,
$.unsafe_statement,
$.using_statement,
$.while_statement,
$.yield_statement,
),
break_statement: $ => seq('break', ';'),
checked_statement: $ => seq(choice('checked', 'unchecked'), $.block),
continue_statement: $ => seq('continue', ';'),
do_statement: $ => seq(
'do',
field('body', $._statement),
'while',
'(',
field('condition', $._expression),
')',
';'
),
empty_statement: $ => ';',
expression_statement: $ => seq($._expression_statement_expression, ';'),
fixed_statement: $ => seq('fixed', '(', $.variable_declaration, ')', $._statement),
for_statement: $ => seq(
'for',
'(',
field('initializer', optional(choice($.variable_declaration, commaSep1($._expression)))),
';',
field('condition', optional($._expression)),
';',
field('update', optional(commaSep1($._expression))),
')',
field('body', $._statement)
),
// Combines for_each_statement and for_each_variable_statement from grammar.txt
for_each_statement: $ => seq(
optional('await'),
'foreach',
'(',
choice(
seq(
field('type', $._type),
field('left', choice($.identifier, $.tuple_pattern)),
), // for_each_statement
field('left', $._expression), // for_each_variable_statement
),
'in',
field('right', $._expression),
')',
field('body', $._statement)
),
goto_statement: $ => seq(
'goto',
optional(choice('case', 'default')),
optional($._expression),
';'
),
if_statement: $ => prec.right(seq(
'if',
'(',
field('condition', $._expression),
')',
field('consequence', $._statement),
optional(seq(
'else',
field('alternative', $._statement)
))
)),
labeled_statement: $ => seq(
$.identifier,
':',
$._statement
),
local_declaration_statement: $ => seq(
optional('await'),
optional('using'),
repeat($.modifier),
$.variable_declaration,
';'
),
local_function_statement: $ => seq(
repeat($.attribute_list),
repeat($.modifier),
field('type', $._type),
field('name', $.identifier),
field('type_parameters', optional($.type_parameter_list)),
field('parameters', $.parameter_list),
repeat($.type_parameter_constraints_clause),
$._function_body
),
lock_statement: $ => seq('lock', '(', $._expression, ')', $._statement),
return_statement: $ => seq('return', optional($._expression), ';'),
switch_statement: $ => seq(
'switch',
choice(
seq(
'(',
field('value', $._expression),
')',
),
field('value', $.tuple_expression)
),
field('body', $.switch_body)
),
switch_body: $ => seq(
'{',
repeat($.switch_section),
'}'
),
switch_section: $ => prec.left(seq(
repeat1(choice( // switch_label
$.case_switch_label,
$.case_pattern_switch_label,
$.default_switch_label
)),
repeat1($._statement)
)),
case_pattern_switch_label: $ => seq(
'case',
$._pattern,
optional($.when_clause),
':'
),
_pattern: $ => choice(
$.constant_pattern,
$.declaration_pattern,
$.discard,
$.recursive_pattern,
$.var_pattern,
$.negated_pattern,
$.parenthesized_pattern,
$.relational_pattern,
$.or_pattern,
$.and_pattern,
$.list_pattern,
$.type_pattern
),
type_pattern: $ => field('type', $._type),
list_pattern: $ => seq(
'[',
optional(seq(commaSep1(choice($._pattern, $.slice_pattern)), optional(','))),
']'
),
slice_pattern: $ => '..',
parenthesized_pattern: $ => seq('(', $._pattern, ')'),
relational_pattern: $ => prec.left(choice(
seq('<', $._expression),
seq('<=', $._expression),
seq('>', $._expression),
seq('>=', $._expression)
)),
negated_pattern: $ => seq('not', $._pattern),
and_pattern: $ => prec.left(PREC.AND, seq(
field('left', $._pattern),
field('operator', 'and'),
field('right', $._pattern)
)),
or_pattern: $ => prec.left(PREC.OR, seq(
field('left', $._pattern),
field('operator', 'or'),
field('right', $._pattern)
)),
//We may need to expand this list if more things can be evaluated at compile time
constant_pattern: $ => choice(
$.binary_expression,
$.default_expression,
$.interpolated_string_expression,
$.parenthesized_expression,
$.postfix_unary_expression,
$.prefix_unary_expression,
$.size_of_expression,
$.tuple_expression,
$.type_of_expression,
$.member_access_expression,
$.invocation_expression,
$.cast_expression,
$._simple_name,
$._literal
),
declaration_pattern: $ => seq(
field('type', $._type),
$._variable_designation
),
_variable_designation: $ => prec(1, choice(
$.discard,
$.parenthesized_variable_designation,
field('name', $.identifier),
)),
discard: $ => '_',
parenthesized_variable_designation: $ => seq(
'(',
commaSep($._variable_designation),
')'
),
recursive_pattern: $ => prec.left(seq(
optional(field('type', $._type)),
choice(
seq(
$.positional_pattern_clause,
optional($.property_pattern_clause)
),
$.property_pattern_clause
),
optional($._variable_designation)
)),
positional_pattern_clause: $ => prec(1, seq(
'(',
optional(commaSep2($.subpattern)),// we really should allow single sub patterns, but that causes conficts, and will rarely be used
')',
)),
expression_colon: $ => seq($._expression, ':'),
subpattern: $ => seq(
optional($.expression_colon),
$._pattern
),
property_pattern_clause: $ => prec(1, seq(
'{',
commaSep($.subpattern),
optional(','),
'}',
)),
var_pattern: $ => prec(1, seq('var', $._variable_designation)),
when_clause: $ => seq('when', $._expression),
case_switch_label: $ => prec.left(1, seq('case', $._expression, ':')),
default_switch_label: $ => prec.left(1, seq('default', ':')),
throw_statement: $ => seq('throw', optional($._expression), ';'),
try_statement: $ => seq(
'try',
field('body', $.block),
repeat($.catch_clause),
optional($.finally_clause),
),
catch_clause: $ => seq(
'catch',
optional($.catch_declaration),
optional($.catch_filter_clause),
field('body', $.block)
),
catch_declaration: $ => seq(
'(',
field('type', $._type),
optional(field('name', $.identifier)),
')'
),
catch_filter_clause: $ => seq('when', '(', $._expression, ')'),
finally_clause: $ => seq('finally', $.block),
unsafe_statement: $ => seq('unsafe', $.block),
using_statement: $ => seq(
optional('await'),
'using',
'(',
choice($.variable_declaration, $._expression),
')',
field('body', $._statement)
),
while_statement: $ => seq(
'while',
'(',
field('condition', $._expression),
')',
field('body', $._statement)
),
yield_statement: $ => seq(
'yield',
choice( // grammar.txt incorrectly allows "break expression", we do not.
seq('return', $._expression),
'break'
),
';'
),
anonymous_method_expression: $ => seq(
optional(alias(choice('async', 'static', seq('async', 'static'), seq('static', 'async')), $.modifier)),
'delegate',
optional(field('parameters', $.parameter_list)),
$.block
),
lambda_expression: $ => prec(-1, seq(
repeat($.attribute_list),
optional(alias(choice('async', 'static', seq('async', 'static'), seq('static', 'async')), $.modifier)),
optional(field('type', $._type)),
field('parameters', choice($.parameter_list, $.implicit_parameter_list)),
'=>',
field('body', choice($.block, $._expression))
)),
anonymous_object_creation_expression: $ => seq(
'new',
'{',
commaSep($._anonymous_object_member_declarator),
optional(','),
'}'
),
implicit_object_creation_expression: $ => seq(
'new',
$.argument_list,
optional($.initializer_expression)
),
_anonymous_object_member_declarator: $ => choice(
prec.dynamic(PREC.ASSIGN, seq($.name_equals, $._expression)),
$._expression
),
array_creation_expression: $ => prec.dynamic(PREC.UNARY, seq(
'new',
field('type', $.array_type),
optional($.initializer_expression)
)),
initializer_expression: $ => seq(
'{',
commaSep($._expression),
optional(','),
'}'
),
assignment_expression: $ => prec.right(seq(
field('left', $._lvalue_expression),
$.assignment_operator,
field('right', $._expression)
)),
assignment_operator: $ => choice('=', '+=', '-=', '*=', '/=', '%=', '&=', '^=', '|=', '<<=', '>>=', '>>>=', '??='),
await_expression: $ => prec.right(PREC.UNARY, seq('await', $._expression)),
cast_expression: $ => prec.right(PREC.CAST, prec.dynamic(1, seq( // higher than invocation, lower than binary
'(',
field('type', $._type),
')',
field('value', $._expression)
))),
checked_expression: $ => choice(
seq('checked', '(', $._expression, ')'),
seq('unchecked', '(', $._expression, ')')
),
conditional_access_expression: $ => prec.right(PREC.COND, seq(
field('condition', $._expression),
'?',
choice($.member_binding_expression, $.element_binding_expression)
)),
conditional_expression: $ => prec.right(PREC.COND, seq(
field('condition', $._expression),
'?',
field('consequence', $._expression),
':',
field('alternative', $._expression)
)),
declaration_expression: $ => seq(
field('type', $._type),
field('name', $.identifier)
),
default_expression: $ => prec.right(seq(
'default',
optional(seq(
'(',
field('type', $._type),
')'
))
)),
element_access_expression: $ => prec.right(PREC.POSTFIX, seq(
field('expression', $._expression),
field('subscript', $.bracketed_argument_list)
)),
element_binding_expression: $ => $.bracketed_argument_list,
implicit_array_creation_expression: $ => seq(
'new',
'[',
repeat(','),
']',
$.initializer_expression
),
implicit_stack_alloc_array_creation_expression: $ => seq(
'stackalloc',
'[',
']',
$.initializer_expression
),
base_expression: $ => 'base',
this_expression: $ => 'this',
interpolated_string_expression: $ => choice(
seq('$"', repeat($._interpolated_string_content), '"'),
seq('$@"', repeat($._interpolated_verbatim_string_content), '"'),
seq('@$"', repeat($._interpolated_verbatim_string_content), '"'),
seq('$"""', repeat($._interpolated_raw_string_content), '"""'),
),
_interpolated_string_content: $ => choice(
$.interpolated_string_text,
$.interpolation
),
_interpolated_verbatim_string_content: $ => choice(
$.interpolated_verbatim_string_text,
$.interpolation
),
_interpolated_raw_string_content: $ => choice(
$.interpolated_raw_string_text,
$.interpolation
),
interpolated_string_text: $ => choice(
'{{',
$._interpolated_string_text_fragment,
$.escape_sequence
),
_interpolated_string_text_fragment: $ => token.immediate(prec(1, /[^{"\\\n]+/)),
interpolated_verbatim_string_text: $ => choice(
'{{',
$._interpolated_verbatim_string_text_fragment,
'""'
),
interpolated_raw_string_text: $ => choice(
$._interpolated_verbatim_string_text_fragment,
'"',
'""',
),
_interpolated_verbatim_string_text_fragment: $ => token.immediate(prec(1, /[^{"]+/)),
interpolation: $ => seq(
'{',
$._expression,
optional($.interpolation_alignment_clause),
optional($.interpolation_format_clause),
'}'
),
interpolation_alignment_clause: $ => seq(',', $._expression),
interpolation_format_clause: $ => seq(':', /[^}"]+/),
invocation_expression: $ => prec(PREC.INVOCATION, seq(
field('function', $._expression),
field('arguments', $.argument_list)
)),
is_pattern_expression: $ => prec.left(PREC.REL, seq(
field('expression', $._expression),
'is',
field('pattern', $._pattern)
)),
make_ref_expression: $ => seq(
'__makeref',
'(',
$._expression,
')'
),
member_access_expression: $ => prec(PREC.DOT, seq(
field('expression', choice($._expression, $.predefined_type, $._name)),
choice('.', '->'),
field('name', $._simple_name)
)),
member_binding_expression: $ => seq(
'.',
field('name', $._simple_name),
),
object_creation_expression: $ => prec.right(seq(
'new',
field('type', $._object_creation_type),
field('arguments', optional($.argument_list)),
field('initializer', optional($.initializer_expression))
)),
_object_creation_type: $ => choice(
$._type_name,
$.nullable_type,
$.predefined_type,
),
parenthesized_expression: $ => seq('(', $._non_lvalue_expression, ')'),
_parenthesized_lvalue_expression: $ => seq('(', $._lvalue_expression, ')'),
postfix_unary_expression: $ => prec.left(PREC.POSTFIX, choice(
seq($._expression, '++'),
seq($._expression, '--'),
seq($._expression, '!')
)),
prefix_unary_expression: $ => prec.right(PREC.UNARY, choice(
...[
'!',
'&',
// '*', Handled separately in _pointer_indirection_expression
'+',
'++',
'-',
'--',
'^',
'~'
].map(operator => seq(operator, $._expression)))),
_pointer_indirection_expression: $ => prec.right(PREC.UNARY, seq('*', $._expression)),
query_expression: $ => seq($.from_clause, $._query_body),
from_clause: $ => seq(
'from',
optional(field('type', $._type)),
field('name', $.identifier),
'in',
$._expression
),
_query_body: $ => prec.right(seq(
repeat($._query_clause), // grammar.txt is incorrect with '+'
$._select_or_group_clause,
optional($.query_continuation)
)),
_query_clause: $ => choice(
$.from_clause,
$.join_clause,
$.let_clause,
$.order_by_clause,
$.where_clause
),
join_clause: $ => seq(
'join',
optional(field('type', $._type)),
field('name', $.identifier),
'in',
$._expression,
'on',
$._expression,
'equals',
$._expression,
optional($.join_into_clause)
),
join_into_clause: $ => seq('into', $.identifier),
let_clause: $ => seq(
'let',
$.identifier,
'=',
$._expression
),
order_by_clause: $ => seq(
'orderby',
commaSep1($._ordering)
),
_ordering: $ => seq(
$._expression,
optional(choice('ascending', 'descending'))
),
where_clause: $ => seq('where', $._expression),
_select_or_group_clause: $ => choice(
$.group_clause,
$.select_clause
),
group_clause: $ => prec.right(PREC.SELECT, seq(
'group',
$._expression,
'by',
$._expression
)),
select_clause: $ => prec.right(PREC.SELECT, seq('select', $._expression)),
query_continuation: $ => seq('into', field('name', $.identifier), $._query_body),
range_expression: $ => prec.right(PREC.RANGE, seq(
optional($._expression),
'..',
optional($._expression)
)),
ref_expression: $ => prec.right(seq('ref', $._expression)),
ref_type_expression: $ => seq(
'__reftype',
'(',
$._expression,
')'
),
ref_value_expression: $ => seq(
'__refvalue',
'(',
field('value', $._expression),
',',
field('type', $._type),
')'
),
size_of_expression: $ => seq(
'sizeof',
'(',
field('type', $._type),
')'
),
stack_alloc_array_creation_expression: $ => seq(
'stackalloc',
field('type', $.array_type),
optional($.initializer_expression)
),
switch_expression: $ => prec(PREC.SWITCH, seq(
$._expression,
'switch',
'{',
commaSep($.switch_expression_arm),
optional(','),
'}',
)),
switch_expression_arm: $ => seq(
$._pattern,
optional($.when_clause),
'=>',
$._expression
),
throw_expression: $ => prec.right(seq('throw', $._expression)),
tuple_expression: $ => seq(
'(',
commaSep2($.argument),
')'
),
type_of_expression: $ => seq('typeof', '(', field('type', $._type), ')'),
with_expression: $ => prec.left(PREC.WITH,
seq($._expression, 'with', '{', optional($.with_initializer_expression), '}')),
with_initializer_expression: $ => commaSep1($.simple_assignment_expression),
simple_assignment_expression: $ => seq($.identifier, '=', $._expression),
_expression: $ => choice(
$._non_lvalue_expression,
$._lvalue_expression,
),
_non_lvalue_expression: $ => choice(
$.anonymous_method_expression,
$.anonymous_object_creation_expression,
$.array_creation_expression,
$.as_expression,
$.base_expression,
$.binary_expression,
$.cast_expression,
$.checked_expression,
$.conditional_access_expression,
$.conditional_expression,
$.default_expression,
$.implicit_array_creation_expression,
$.implicit_object_creation_expression,
$.implicit_stack_alloc_array_creation_expression,
$.initializer_expression,
$.interpolated_string_expression,
$.is_expression,
$.is_pattern_expression,
$.lambda_expression,
$.make_ref_expression,
// $.member_binding_expression, // Not needed as handled directly in $.conditional_access_expression
$.query_expression,
$.range_expression,
$.ref_expression,
$.ref_type_expression,
$.ref_value_expression,
$.size_of_expression,
$.stack_alloc_array_creation_expression,
$.switch_expression,
$.throw_expression,
$.type_of_expression,
$.with_expression,
$._literal,
$._expression_statement_expression,
),
// Covers error CS0131: The left-hand side of an assignment must be a variable, property or indexer
_lvalue_expression: $ => choice(
$.this_expression,
$.member_access_expression,
$.tuple_expression,
$._simple_name,
$.element_access_expression,
$.element_binding_expression,
alias($._pointer_indirection_expression, $.prefix_unary_expression),
alias($._parenthesized_lvalue_expression, $.parenthesized_expression),
),
// Covers error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement
_expression_statement_expression: $ => choice(
$.assignment_expression,
$.invocation_expression,
$.postfix_unary_expression,
$.prefix_unary_expression,
$.await_expression,
$.object_creation_expression,
$.parenthesized_expression,
),
binary_expression: $ => choice(
...[
['&&', PREC.LOGAND], // logical_and_expression
['||', PREC.LOGOR], // logical_or_expression
['>>', PREC.SHIFT], // right_shift_expression
['>>>', PREC.SHIFT], // right_shift_expression
['<<', PREC.SHIFT], // left_shift_expression
['&', PREC.AND], // bitwise_and_expression
['^', PREC.XOR], // exclusive_or_expression
['|', PREC.OR], // bitwise_or_expression
['+', PREC.ADD], // add_expression
['-', PREC.ADD], // subtract_expression
['*', PREC.MULT], // multiply_expression
['/', PREC.MULT], // divide_expression
['%', PREC.MULT], // modulo_expression
['<', PREC.REL], // less_than_expression
['<=', PREC.REL], // less_than_or_equal_expression
['==', PREC.EQUAL], // equals_expression
['!=', PREC.EQUAL], // not_equals_expression
['>=', PREC.REL], // greater_than_or_equal_expression
['>', PREC.REL] // greater_than_expression
].map(([operator, precedence]) =>
prec.left(precedence, prec.dynamic(2, seq( // higher than cast
field('left', $._expression),
field('operator', operator),
field('right', $._expression)
)))
),
prec.right(PREC.COALESCING, seq(
field('left', $._expression),
field('operator', '??'), // coalesce_expression
field('right', $._expression)
))
),
as_expression: $ => prec.left(PREC.REL, seq(
field('left', $._expression),
field('operator', 'as'),
field('right', $._type)
)),
is_expression: $ => prec.left(PREC.REL, seq(
field('left', $._expression),
field('operator', 'is'),
field('right', $._type)
)),
// Unicode categories: L = Letter, Nl Letter_Number, = Nd = Decimal_Number, Pc = Connector_Punctuation, Cf = Format, Mn = Nonspacing_Mark, Mc = Spacing_Mark
_identifier_token: $ => token(seq(optional('@'), /[\p{L}\p{Nl}_][\p{L}\p{Nl}\p{Nd}\p{Pc}\p{Cf}\p{Mn}\p{Mc}]*/)),
identifier: $ => choice($._identifier_token, $._contextual_keywords),
global: $ => 'global',
_identifier_or_global: $ => choice($.global, $.identifier),
// Literals - grammar.txt is useless here as it just refs to lexical specification
_literal: $ => choice(
$.null_literal,
$.boolean_literal,
$.character_literal,
// Don't combine real and integer literals together
$.real_literal,
$.integer_literal,
// Or strings and verbatim strings
$.string_literal,
$.verbatim_string_literal,
$.raw_string_literal,
),
boolean_literal: $ => choice(
'true',
'false'
),
character_literal: $ => seq(
"'",
choice($.character_literal_unescaped, $.escape_sequence),
"'"
),
character_literal_unescaped: $ => token.immediate(/[^'\\]/),
escape_sequence: $ => token(choice(
/\\x[0-9a-fA-F][0-9a-fA-F]?[0-9a-fA-F]?[0-9a-fA-F]?/,
/\\u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]/,
/\\U[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]/,
/\\[^xuU]/,
)),
integer_literal: $ => token(seq(
choice(
decimalDigitSequence, // Decimal
(/0[xX][0-9a-fA-F_]*[0-9a-fA-F]+/), // Hex
(/0[bB][01_]*[01]+/) // Binary
),
optional(/u|U|l|L|ul|UL|uL|Ul|lu|LU|Lu|lU/)
)),
null_literal: $ => 'null',
real_literal: $ => {
const suffix = /[fFdDmM]/;
const exponent = /[eE][+-]?[0-9][0-9_]*/;
return token(choice(
seq(
decimalDigitSequence,
'.',
decimalDigitSequence,
optional(exponent),
optional(suffix)
),
seq(
'.',
decimalDigitSequence,
optional(exponent),
optional(suffix)
),
seq(
decimalDigitSequence,
exponent,
optional(suffix)
),
seq(
decimalDigitSequence,
suffix
)
))
},
string_literal: $ => seq(
'"',
repeat(choice(
$.string_literal_fragment,
$.escape_sequence
)),
'"',
optional($.string_literal_encoding)
),
string_literal_fragment: $ => token.immediate(prec(1, /[^"\\\n]+/)),
string_literal_encoding: $ => token.immediate(stringEncoding),
verbatim_string_literal: $ => token(seq(
'@"',
repeat(choice(
/[^"]/,
'""',
)),
'"',
optional(stringEncoding)
)),
raw_string_literal: $ => token(seq(
/""["]+/,
optional(/([^"]|("[^"])|(""[^"]))+/),
/""["]+/,
optional(stringEncoding)
)),
// Comments
comment: $ => token(choice(
seq('//', /[^\n\r]*/),
seq(
'/*',
/[^*]*\*+([^/*][^*]*\*+)*/,
'/'
)
)),
// Custom non-Roslyn additions beyond this point that will not sync up with grammar.txt
// Contextual keywords - keywords that can also be identifiers...
// The below list contains all contextual keywords listed in https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/#contextual-keywords
// Tree-sitter performs context-aware lexing, so some of these are not going to be needed and can be commented out. The list should be kept for completeness.
// Currently keywords are commented out based on whether they were in the grammar already or not.
_contextual_keywords: $ => choice(
// 'add',
'alias',
// 'and',
// 'args',
'ascending',
// 'async',
// 'await',
'by',
'descending',
// 'dynamic',
'equals',
'file',
'from',
// 'get',
'global',
'group',
// 'init',
'into',
'join',
'let',
// 'managed',
// 'nameof',
// 'nint',
// 'not',
'notnull',
// 'nuint',
'on',
// 'or',
'orderby',
// 'partial',
// 'record',
// 'remove',
// 'required',
'scoped',
'select',
// 'set',
'unmanaged',
// 'value',
'var',
'when',
'where',
// 'with',
'yield',
),
// Preprocessor
_preprocessor_call: $ => seq(
$._preproc_directive_start,
choice(
$.nullable_directive,
$.define_directive,
$.undef_directive,
$.if_directive,
$.else_directive,
$.elif_directive,
$.endif_directive,
$.region_directive,
$.endregion_directive,
$.error_directive,
$.warning_directive,
$.line_directive,
$.pragma_directive,
$.reference_directive,
$.load_directive,
$.shebang_directive
),
$._preproc_directive_end
),
_preproc_directive_start: $ => /#[ \t]*/,
nullable_directive: $ => seq(
'nullable',
choice('disable', 'enable', 'restore'),
optional(choice('annotations', 'warnings'))
),
define_directive: $ => seq('define', $.identifier),
undef_directive: $ => seq('undef', $.identifier),
if_directive: $ => seq('if', $._preproc_expression),
else_directive: $ => 'else',
elif_directive: $ => seq('elif', $._preproc_expression),
endif_directive: $ => 'endif',
region_directive: $ => seq('region', optional($.preproc_message)),
endregion_directive: $ => seq('endregion', optional($.preproc_message)),
error_directive: $ => seq('error', $.preproc_message),
warning_directive: $ => seq('warning', $.preproc_message),
line_directive: $ => seq('line',
choice(
'default',
'hidden',
seq($.preproc_integer_literal, optional($.preproc_string_literal)),
seq(
'(', $.preproc_integer_literal, ',', $.preproc_integer_literal, ')',
'-',
'(', $.preproc_integer_literal, ',', $.preproc_integer_literal, ')',
optional($.preproc_integer_literal), $.preproc_string_literal),
)
),
pragma_directive: $ => seq('pragma',
choice(
seq('warning',
choice('disable', 'restore'),
commaSep(
choice(
$.identifier,
alias($.preproc_integer_literal, $.integer_literal),
))),
seq('checksum', $.preproc_string_literal, $.preproc_string_literal, $.preproc_string_literal)
)
),
reference_directive: $ => seq('r', $.preproc_string_literal),
load_directive: $ => seq('load', $.preproc_string_literal),
shebang_directive: $ => seq('!', /[^\n\r]*/),
preproc_message: $ => /[^\n\r]+/,
preproc_integer_literal: $ => /[0-9]+/,
preproc_string_literal: $ => /"[^"]*"/,
_preproc_expression: $ => choice(
$.identifier,
$.boolean_literal,
alias($.preproc_integer_literal, $.integer_literal),
alias($.preproc_string_literal, $.verbatim_string_literal),
alias($.preproc_unary_expression, $.prefix_unary_expression),
alias($.preproc_binary_expression, $.binary_expression),
alias($.preproc_parenthesized_expression, $.parenthesized_expression)
),
preproc_parenthesized_expression: $ => seq(
'(',
$._preproc_expression,
')'
),
preproc_unary_expression: $ => prec.left(PREC.UNARY, seq(
field('operator', '!'),
field('argument', $._preproc_expression)
)),
preproc_binary_expression: $ => {
const table = [
['||', PREC.LOGOR],
['&&', PREC.LOGAND],
['==', PREC.EQUAL],
['!=', PREC.EQUAL],
];
return choice(...table.map(([operator, precedence]) => {
return prec.left(precedence, seq(
field('left', $._preproc_expression),
field('operator', operator),
field('right', $._preproc_expression)
))
}));
},
}
})
function commaSep(rule) {
return optional(commaSep1(rule))
}
function commaSep1(rule) {
return seq(
rule,
repeat(seq(
',',
rule
))
)
}
function commaSep2(rule) {
return seq(
rule,
',',
commaSep1(rule)
)
}