const PREC = { DOT: 17, INVOCATION: 16, POSTFIX: 16, PREFIX: 15, UNARY: 15, CAST: 14, MULT: 13, ADD: 12, SHIFT: 11, REL: 10, EQUAL: 9, AND: 8, XOR: 7, OR: 6, LOGAND: 5, LOGOR: 4, COND: 3, ASSIGN: 2, SEQ: 1, TERNARY: 1, SELECT: 0, TYPE_PATTERN: -2, }; const decimalDigitSequence = /([0-9][0-9_]*[0-9]|[0-9])/; module.exports = grammar({ name: 'c_sharp', extras: $ => [ $.comment, /[\s\u00A0\uFEFF\u3000]+/, $.preprocessor_call ], supertypes: $ => [ $._declaration, $._expression, $._statement, $._type, ], externals: $ => [ $._preproc_directive_end, ], conflicts: $ => [ [$.block, $.initializer_expression], [$.event_declaration, $.variable_declarator], [$.nullable_type, $.as_expression], [$.nullable_type, $.is_expression, $.type_pattern], [$.nullable_type, $.as_expression, $.type_pattern], [$._name, $._expression], [$._simple_name, $.type_parameter], [$._simple_name, $.generic_name], [$._simple_name, $.constructor_declaration], [$.qualified_name, $.explicit_interface_specifier], [$.qualified_name, $.member_access_expression], [$._contextual_keywords, $.from_clause], [$._contextual_keywords, $.global], [$._contextual_keywords, $.accessor_declaration], [$._contextual_keywords, $.type_parameter_constraint], [$._type, $.array_creation_expression], [$._type, $.stack_alloc_array_creation_expression], [$.parameter_modifier, $.this_expression], [$.parameter, $._simple_name], [$.parameter, $.tuple_element], [$.parameter, $.tuple_element, $.declaration_expression], [$.parameter, $._pattern], [$.parameter, $.declaration_expression], [$.tuple_element], [$.tuple_element, $.declaration_expression], [$.tuple_element, $.variable_declarator] ], inline: $ => [ $.return_type, $._identifier_or_global, ], word: $ => $._identifier_token, rules: { compilation_unit: $ => seq( repeat($.extern_alias_directive), repeat($.using_directive), repeat($.global_attribute_list), repeat($.global_statement), repeat($._namespace_member_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, $.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 ), extern_alias_directive: $ => seq('extern', 'alias', $.identifier, ';'), using_directive: $ => seq( 'using', optional(choice( 'static', $.name_equals )), $._name, ';' ), name_equals: $ => prec(1, seq($._identifier_or_global, '=')), _name: $ => choice( $.alias_qualified_name, $.qualified_name, $._simple_name ), alias_qualified_name: $ => seq($._identifier_or_global, '::', $._simple_name), _simple_name: $ => choice( $.generic_name, $._identifier_or_global ), generic_name: $ => seq($.identifier, $.type_argument_list), // Intentionally different from Roslyn to avoid non-matching // omitted_type_argument in a lot of unnecessary places. type_argument_list: $ => seq( '<', choice( repeat(','), commaSep1($._type), ), '>' ), qualified_name: $ => prec(PREC.DOT, seq($._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', $._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($._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', 'fixed', 'internal', 'new', 'override', 'partial', 'private', 'protected', 'public', 'readonly', prec(1, 'ref'), //make sure that 'ref' is treated as a modifier for local variable declarations instead of as a ref expression 'sealed', 'static', 'unsafe', 'virtual', 'volatile' )), variable_declaration: $ => seq( field('type', $._type), commaSep1($.variable_declarator) ), variable_declarator: $ => seq( choice($.identifier, $.tuple_pattern), optional($.bracketed_argument_list), optional($.equals_value_clause) ), bracketed_argument_list: $ => seq( '[', commaSep1($.argument), ']' ), tuple_pattern: $ => seq( '(', commaSep1(choice($.identifier, $.discard, $.tuple_pattern)), ')' ), argument: $ => prec(1, seq( optional($.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), ')' ), _formal_parameter_list: $ => commaSep1(choice( $.parameter, $.parameter_array )), parameter: $ => seq( repeat($.attribute_list), optional($.parameter_modifier), optional(field('type', $._type)), field('name', $.identifier), optional($.equals_value_clause) ), parameter_modifier: $ => prec.right(choice('ref', 'out', 'this', 'in')), parameter_array: $ => seq( repeat($.attribute_list), 'params', choice($.array_type, $.nullable_type), $.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' ), 'operator', 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'), '~', $.identifier, $.parameter_list, $._function_body ), method_declaration: $ => seq( repeat($.attribute_list), repeat($.modifier), field('type', $.return_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($._name, '.')), type_parameter_list: $ => seq('<', commaSep1($.type_parameter), '>'), type_parameter: $ => seq( repeat($.attribute_list), optional(choice('in', 'out')), $.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), 'operator', 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), 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('[', commaSep1($.parameter), ']'), 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), optional(';') ), 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), optional(';') ), 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), optional(';') ), struct_declaration: $ => seq( repeat($.attribute_list), repeat($.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), optional(';') ), delegate_declaration: $ => seq( repeat($.attribute_list), repeat($.modifier), 'delegate', field('type', $.return_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', 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_base), ), record_base: $ => choice( seq(':', commaSep1($.identifier)), seq(':', $.primary_constructor_base_type, optional(seq(',', commaSep1($.identifier)))), ), primary_constructor_base_type: $ => seq( $.identifier, $.argument_list ), _record_base: $ => choice( $.declaration_list, ';' ), namespace_declaration: $ => seq( 'namespace', field('name', $._name), field('body', $.declaration_list), optional(';') ), _type: $ => choice( $.implicit_type, $.array_type, $._name, $.nullable_type, $.pointer_type, $.function_pointer_type, $.predefined_type, $.tuple_type, ), implicit_type: $ => 'var', array_type: $ => prec(PREC.POSTFIX, seq( field('type', $._type), field('rank', $.array_rank_specifier) )), // 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)), ']'), // When used in a nullable type, the '?' operator binds tighter than the // binary operators `as` and `is`. But in a conditional expression, the `?` // binds *looser*. This weird double precedence is required in order to // preserve the conflict, so that `?` can be used in both ways, depending // on what follows. nullable_type: $ => choice( prec(PREC.EQUAL + 1, seq($._type, '?')), prec(PREC.COND - 1, seq($._type, '?')) ), pointer_type: $ => prec(PREC.POSTFIX, seq($._type, '*')), function_pointer_type: $ => seq( 'delegate', '*', optional($.function_pointer_calling_convention), '<', commaSep1($.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(choice('ref', 'out', 'in', seq('ref', 'readonly'))), choice($._type, $.void_keyword) ), predefined_type: $ => token(choice( 'bool', 'byte', 'char', 'decimal', 'double', 'float', 'int', 'long', 'object', 'sbyte', 'short', 'string', 'uint', 'ulong', 'ushort', 'nint', 'nuint' // void is handled in return_type for better matching )), ref_type: $ => seq( 'ref', optional('readonly'), $._type ), tuple_type: $ => seq( '(', $.tuple_element, ',', commaSep1($.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', $._statement, 'while', '(', $._expression, ')', ';'), empty_statement: $ => ';', expression_statement: $ => seq($._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) ), // grammar.txt one doesn't seem to make sense so we do this instead goto_statement: $ => seq( 'goto', choice( alias($.identifier, $.label_name), seq('case', $._expression), 'default' ), ';' ), if_statement: $ => prec.right(seq( 'if', '(', field('condition', $._expression), ')', field('consequence', $._statement), optional(seq( 'else', field('alternative', $._statement) )) )), labeled_statement: $ => seq( alias($.identifier, $.label_name), ':', $._statement ), local_declaration_statement: $ => seq( optional('await'), optional('using'), repeat($.modifier), $.variable_declaration, ';' ), local_function_statement: $ => seq( repeat($.attribute_list), repeat($.modifier), field('type', $.return_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, $.binary_pattern, $.type_pattern ), type_pattern: $ => prec(PREC.TYPE_PATTERN, $._type), parenthesized_pattern: $ => seq('(', $._pattern, ')'), relational_pattern: $ => prec.left(choice( seq('<', $._expression), seq('<=', $._expression), seq('>', $._expression), seq('>=', $._expression) )), negated_pattern: $ => seq('not', $._pattern), binary_pattern: $ => choice( prec.left(PREC.AND, seq( field('left', $._pattern), field('operator', 'and'), field('right', $._pattern) )), prec.left(PREC.OR, seq( field('left', $._pattern), field('operator', 'or'), field('right', $._pattern) )), ), constant_pattern: $ => prec.right($._expression), declaration_pattern: $ => seq( field('type', $._type), $._variable_designation ), _variable_designation: $ => prec(1, choice( $.discard, $.parenthesized_variable_designation, $.identifier )), discard: $ => '_', parenthesized_variable_designation: $ => seq( '(', commaSep($._variable_designation), ')' ), recursive_pattern: $ => prec.left(seq( optional($._type), choice( seq( $.positional_pattern_clause, optional($.property_pattern_clause) ), $.property_pattern_clause ), optional($._variable_designation) )), positional_pattern_clause: $ => prec(1, seq( '(', optional(seq($.subpattern, ',', commaSep1($.subpattern))),// we really should allow single sub patterns, but that causes conficts, and will rarely be used ')', )), subpattern: $ => seq( optional($.name_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), field('name', optional($.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', '(', $._expression, ')', $._statement), yield_statement: $ => seq( 'yield', choice( // grammar.txt incorrectly allows "break expression", we do not. seq('return', $._expression), 'break' ), ';' ), anonymous_method_expression: $ => seq( optional('async'), 'delegate', optional($.parameter_list), $.block ), lambda_expression: $ => prec(-1, seq( optional('async'), optional('static'), choice($.parameter_list, $.identifier), '=>', 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: $ => seq( 'new', $.array_type, optional($.initializer_expression) ), initializer_expression: $ => seq( '{', commaSep($._expression), optional(','), '}' ), assignment_expression: $ => prec.right(seq( field('left', $._expression), $.assignment_operator, field('right', $._expression) )), assignment_operator: $ => choice('=', '+=', '-=', '*=', '/=', '%=', '&=', '^=', '|=', '<<=', '>>=', '??='), await_expression: $ => prec.right(PREC.UNARY, seq('await', $._expression)), cast_expression: $ => prec.right(PREC.CAST, seq( '(', 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.UNARY, 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), '"'), ), _interpolated_string_content: $ => choice( $.interpolated_string_text, $.interpolation ), _interpolated_verbatim_string_content: $ => choice( $.interpolated_verbatim_string_text, $.interpolation ), interpolated_string_text: $ => choice( '{{', token.immediate(prec(1, /[^{"\\\n]+/)), $.escape_sequence ), interpolated_verbatim_string_text: $ => choice( '{{', /[^{"]+/, '""' ), 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.EQUAL, 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', $._type), field('arguments', optional($.argument_list)), field('initializer', optional($.initializer_expression)) )), parenthesized_expression: $ => seq('(', $._expression, ')'), postfix_unary_expression: $ => prec.left(PREC.POSTFIX, choice( seq($._expression, '++'), seq($._expression, '--'), seq($._expression, '!') )), prefix_unary_expression: $ => prec.right(PREC.UNARY, choice( ...[ '!', '&', '*', '+', '++', '-', '--', '^', '~' ].map(operator => seq(operator, $._expression)))), query_expression: $ => seq($.from_clause, $._query_body), from_clause: $ => seq( 'from', optional($._type), $.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($._type), $.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', $.identifier, $._query_body), range_expression: $ => prec.right(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', '(', $._type, ')' ), stack_alloc_array_creation_expression: $ => seq( 'stackalloc', $.array_type, optional($.initializer_expression) ), switch_expression: $ => prec(PREC.UNARY, 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( '(', $.argument, repeat1(seq( ',', $.argument, )), ')' ), type_of_expression: $ => seq('typeof', '(', $._type, ')'), with_expression: $ => seq($._expression, 'with', '{', optional($.with_initializer_expression), '}'), with_initializer_expression: $ => commaSep1($.simple_assignment_expression), simple_assignment_expression: $ => seq($.identifier, '=', $._expression), _expression: $ => choice( $.anonymous_method_expression, $.anonymous_object_creation_expression, $.array_creation_expression, $.as_expression, $.assignment_expression, $.await_expression, $.base_expression, $.binary_expression, $.cast_expression, $.checked_expression, $.conditional_access_expression, $.conditional_expression, $.default_expression, $.element_access_expression, $.element_binding_expression, $.implicit_array_creation_expression, $.implicit_object_creation_expression, $.implicit_stack_alloc_array_creation_expression, $.initializer_expression, $.interpolated_string_expression, $.invocation_expression, $.is_expression, $.is_pattern_expression, $.lambda_expression, $.make_ref_expression, $.member_access_expression, // $.member_binding_expression, // Not needed as handled directly in $.conditional_access_expression $.object_creation_expression, $.parenthesized_expression, $.postfix_unary_expression, $.prefix_unary_expression, $.query_expression, $.range_expression, $.ref_expression, $.ref_type_expression, $.ref_value_expression, $.size_of_expression, $.stack_alloc_array_creation_expression, $.switch_expression, $.this_expression, $.throw_expression, $.tuple_expression, $.type_of_expression, $.with_expression, $._simple_name, $._literal ), binary_expression: $ => choice( ...[ ['&&', PREC.LOGAND], // logical_and_expression ['||', PREC.LOGOR], // logical_or_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 ['??', PREC.TERNARY], // coalesce_expression ].map(([operator, precedence]) => prec.left(precedence, seq( field('left', $._expression), field('operator', operator), field('right', $._expression) )) ) ), as_expression: $ => prec.left(PREC.EQUAL, seq( field('left', $._expression), field('operator', 'as'), field('right', $._type) )), is_expression: $ => prec.left(PREC.EQUAL, seq( field('left', $._expression), field('operator', 'is'), field('right', $._type) )), _identifier_token: $ => token(seq(optional('@'), /[a-zA-Zα-ωΑ-Ωµ_][a-zA-Zα-ωΑ-Ωµ_0-9]*/)), // identifier_token in Roslyn 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 ), boolean_literal: $ => choice( 'true', 'false' ), character_literal: $ => seq( "'", choice(token.immediate(/[^'\\]/), $.escape_sequence), "'" ), 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( token.immediate(prec(1, /[^"\\\n]+/)), $.escape_sequence )), '"' ), verbatim_string_literal: $ => token(seq( '@"', repeat(choice( /[^"]/, '""', )), '"' )), // Comments comment: $ => token(choice( seq('//', /.*/), seq( '/*', repeat(choice( /[^*]/, /\*[^/]/ )), '*/' ) )), // Custom non-Roslyn additions beyond this point that will not sync up with grammar.txt // Contextual keywords - keywords that can also be identifiers... _contextual_keywords: $ => choice( // LINQ comprehension syntax 'ascending', 'by', 'descending', 'equals', 'from', 'group', 'into', 'join', 'let', 'on', 'orderby', 'select', 'where', // Property/event handlers 'add', 'get', 'remove', 'set', // Async - These need to be more contextual // 'async', // 'await', // Misc 'global', 'alias', 'dynamic', 'nameof', 'notnull', 'unmanaged', 'when', 'yield' ), // We use this instead of type so 'void' is only treated as type in the right contexts return_type: $ => choice($._type, $.void_keyword), void_keyword: $ => 'void', preprocessor_call: $ => seq( $.preprocessor_directive, repeat(choice( $.identifier, $._literal, token(prec(-1, /[^\s]+/)) )), $._preproc_directive_end ), preprocessor_directive: $ => /#[ \t]*[a-z]\w*/, } }) function commaSep(rule) { return optional(commaSep1(rule)) } function commaSep1(rule) { return seq( rule, repeat(seq( ',', rule )) ) }