module.exports = grammar({
name: 'vb_dotnet',
extras: $ => [
$.comment,
/[ \t\f\u00A0]+/, $._line_continuation
],
conflicts: $ => [
[$.type, $.invocation],
[$.type] ,
[$.new_expression] ,
[$.type_argument_list] ,
[$.property_declaration] ,
[$.constructor_declaration],
[$.method_declaration],
[$.left_hand_side, $.expression],
[$.label_statement, $.expression],
[$.event_declaration],
[$.if_statement],
[$.type, $.array_type],
[$.if_statement, $.binary_expression],
[$.empty_statement, $.if_statement],
[$.namespace_name, $.attribute],
[$.namespace_name],
],
rules: {
source_file: $ => seq(
optional($.option_statements),
repeat($.imports_statement),
repeat(choice(
$.attribute_block,
$.namespace_block,
$.type_declaration,
alias($._terminator, $.blank_line) ))
),
option_statements: $ => repeat1(
seq(
kw('Option'),
choice(
seq(kw('Explicit'), choice(kw('On'), kw('Off'))),
seq(kw('Strict'), choice(kw('On'), kw('Off'))),
seq(kw('Infer'), choice(kw('On'), kw('Off'))),
seq(kw('Compare'), choice(kw('Binary'), kw('Text')))
),
$._terminator
)
),
imports_statement: $ => seq(
kw('Imports'),
commaSep1(field('namespace', $.namespace_name)),
$._terminator
),
namespace_name: $ => seq($.identifier, repeat(seq('.', $.identifier))),
type_declaration: $ => choice(
$.class_block,
$.module_block,
$.structure_block,
$.interface_block,
$.enum_block,
$.delegate_declaration
),
namespace_block: $ => seq(
kw('Namespace'),
field('name', $.namespace_name),
$._terminator,
repeat(choice(
$.attribute_block,
$.type_declaration,
$.namespace_block,
$.imports_statement, alias($._terminator, $.blank_line) )),
kw('End'), kw('Namespace'), $._terminator
),
class_block: $ => seq(
field('modifiers', optional($.modifiers)),
kw('Class'),
field('name', $.identifier),
optional($.type_parameters),
optional(field('inherits', $.inherits_clause)),
optional(field('implements', $.implements_clause)),
$._terminator,
repeat($._member_declaration),
kw('End'), kw('Class'), $._terminator
),
module_block: $ => seq(
field('modifiers', optional($.modifiers)),
kw('Module'),
field('name', $.identifier),
$._terminator,
repeat($._member_declaration),
kw('End'), kw('Module'), $._terminator
),
structure_block: $ => seq(
field('modifiers', optional($.modifiers)),
kw('Structure'),
field('name', $.identifier),
optional($.type_parameters),
optional(field('implements', $.implements_clause)),
$._terminator,
repeat($._member_declaration),
kw('End'), kw('Structure'), $._terminator
),
interface_block: $ => seq(
field('modifiers', optional($.modifiers)),
kw('Interface'),
field('name', $.identifier),
optional($.type_parameters),
optional(field('inherits', $.inherits_clause)), $._terminator,
repeat($._member_declaration),
kw('End'), kw('Interface'), $._terminator
),
enum_block: $ => seq(
field('modifiers', optional($.modifiers)),
kw('Enum'),
field('name', $.identifier),
$._terminator,
repeat($.enum_member),
kw('End'), kw('Enum'), $._terminator
),
enum_member: $ => seq(
field('name', $.identifier),
optional(seq('=', field('value', $.expression))),
$._terminator
),
delegate_declaration: $ => seq(
field('modifiers', optional($.modifiers)),
kw('Delegate'),
choice(kw('Sub'), kw('Function')),
field('name', $.identifier),
optional($.type_parameters),
field('parameters', $.parameter_list),
optional(seq(kw('As'), field('return_type', $.type))), $._terminator
),
inherits_clause: $ => seq(kw('Inherits'), commaSep1($.type)),
implements_clause: $ => seq(kw('Implements'), commaSep1($.type)),
type_parameters: $ => seq(
kw('Of'),
commaSep1($.type_parameter)
),
type_parameter: $ => seq(
field('name', $.identifier),
optional(seq(kw('As'), field('constraint', $.type_constraint)))
),
type_constraint: $ => choice(
$.type,
kw('Structure'),
kw('Class'),
kw('New') ),
attribute_block: $ => seq(
'<',
commaSep1($.attribute),
'>',
$._terminator
),
attribute: $ => seq(
optional(seq(field('target', $.identifier), ':')),
field('name', choice($.identifier, $.namespace_name)),
optional($.argument_list)
),
modifiers: $ => repeat1($.modifier),
modifier: $ => token(choice(
kw('Public'), kw('Private'), kw('Protected'), kw('Friend'),
kw('Shared'), kw('Shadows'), kw('Static'),
kw('Overloads'), kw('Overrides'), kw('Overridable'), kw('NotOverridable'), kw('MustOverride'),
kw('MustInherit'), kw('NotInheritable'),
kw('Partial'), kw('Narrowing'), kw('Widening'),
kw('Default'), kw('ReadOnly'), kw('WriteOnly'),
kw('WithEvents'), kw('Async'), kw('Iterator')
)),
_member_declaration: $ => choice(
alias($._terminator, $.blank_line),
$.const_declaration,
$.field_declaration,
$.method_declaration,
$.constructor_declaration,
$.property_declaration,
$.event_declaration,
$.delegate_declaration ),
const_declaration: $ => seq(
optional(field('attributes', $.attribute_block)),
field('modifiers', optional($.modifiers)),
kw('Const'),
commaSep1(seq(
field('name', $.identifier),
optional($.as_clause),
'=',
field('value', $.expression)
)),
$._terminator
),
field_declaration: $ => seq(
optional(field('attributes', $.attribute_block)),
field('modifiers', optional($.modifiers)),
optional(kw('Dim')),
commaSep1($.variable_declarator),
$._terminator
),
variable_declarator: $ => seq(
field('name', $.identifier),
optional($.array_rank_specifier),
optional($.as_clause),
optional(seq('=', field('initializer', $.expression)))
),
array_rank_specifier: $ => seq('(', optional(repeat(',')), ')'), as_clause: $ => seq(kw('As'), field('type', $.type)),
type: $ => choice(
$.primitive_type,
$.array_type,
$.generic_type,
seq($.namespace_name, optional($.type_argument_list), optional($.array_rank_specifier))
),
generic_type: $ => prec.left(3, seq(
$.namespace_name,
$.type_argument_list,
optional($.array_rank_specifier)
)),
array_type: $ => seq(
choice($.primitive_type, $.namespace_name),
optional($.type_argument_list),
$.array_rank_specifier
),
primitive_type: $ => token(choice( kw('Boolean'), kw('Byte'), kw('Short'), kw('Integer'), kw('Long'),
kw('Single'), kw('Double'), kw('Decimal'),
kw('Char'), kw('String'),
kw('Object'), kw('Date')
)),
type_argument_list: $ => seq(kw('Of'), commaSep1($.type)),
method_declaration: $ => seq(
optional(field('attributes', $.attribute_block)),
field('modifiers', optional($.modifiers)),
choice(kw('Sub'), kw('Function')),
field('name', $.identifier),
optional($.type_parameters),
field('parameters', $.parameter_list),
optional(seq(kw('As'), field('return_type', $.type))), choice(
seq($._terminator, repeat($.statement), kw('End'), choice(kw('Sub'), kw('Function')), $._terminator),
$._terminator
)
),
constructor_declaration: $ => seq(
optional(field('attributes', $.attribute_block)),
field('modifiers', optional($.modifiers)),
kw('Sub'), kw('New'),
field('parameters', $.parameter_list),
choice(
seq($._terminator, repeat($.statement), kw('End'), kw('Sub'), $._terminator),
$._terminator
)
),
property_declaration: $ => seq(
optional(field('attributes', $.attribute_block)),
field('modifiers', optional($.modifiers)),
kw('Property'),
field('name', $.identifier),
optional(field('parameters', $.parameter_list)), optional($.as_clause),
choice(
seq(optional(seq('=', field('initializer', $.expression))), $._terminator),
seq(
$._terminator,
optional($.get_accessor),
optional($.set_accessor),
kw('End'), kw('Property'), $._terminator
)
)
),
get_accessor: $ => seq(
optional(field('modifiers', optional($.modifiers))),
kw('Get'), $._terminator,
repeat($.statement),
kw('End'), kw('Get'), $._terminator
),
set_accessor: $ => seq(
optional(field('modifiers', optional($.modifiers))),
kw('Set'),
optional(field('parameters', $.parameter_list)), $._terminator,
repeat($.statement),
kw('End'), kw('Set'), $._terminator
),
event_declaration: $ => seq(
optional(field('attributes', $.attribute_block)),
field('modifiers', optional($.modifiers)),
kw('Event'),
field('name', $.identifier),
choice(
seq(optional($.parameter_list), optional($.as_clause), $._terminator),
seq(
$.parameter_list, $.as_clause, $._terminator,
repeat(choice($.add_handler_block, $.remove_handler_block, $.raise_event_block)),
kw('End'), kw('Event'), $._terminator
)
)
),
add_handler_block: $ => seq(
optional(field('modifiers', optional($.modifiers))),
kw('AddHandler'), $._terminator,
repeat($.statement),
kw('End'), kw('AddHandler'), $._terminator
),
remove_handler_block: $ => seq(
optional(field('modifiers', optional($.modifiers))),
kw('RemoveHandler'), $._terminator,
repeat($.statement),
kw('End'), kw('RemoveHandler'), $._terminator
),
raise_event_block: $ => seq(
optional(field('modifiers', optional($.modifiers))),
kw('RaiseEvent'), $._terminator,
repeat($.statement),
kw('End'), kw('RaiseEvent'), $._terminator
),
parameter_list: $ => seq('(', optional(commaSep($.parameter)), ')'),
parameter: $ => seq(
optional($.attribute_block), optional(choice(kw('Optional'), kw('ByVal'), kw('ByRef'), kw('ParamArray'))),
field('name', $.identifier),
optional($.array_rank_specifier),
optional($.as_clause),
optional(seq('=', field('default_value', $.expression)))
),
statement: $ => choice(
$.empty_statement,
$.label_statement,
$.dim_statement,
$.const_declaration, prec(1, $.assignment_statement),
$.call_statement,
$.if_statement,
$.select_case_statement,
$.while_statement,
$.do_statement,
$.for_statement,
$.for_each_statement,
$.try_statement,
$.with_statement,
$.using_statement,
$.sync_lock_statement,
$.return_statement,
$.exit_statement,
$.continue_statement,
$.throw_statement,
$.goto_statement,
$.redim_statement,
$.preprocessor_directive, $.compound_assignment_statement,
),
empty_statement: $ => prec(1, $._terminator),
label_statement: $ => seq(field('label', $.identifier), ':'),
dim_statement: $ => seq(
kw('Dim'),
commaSep1(seq(
field('name', $.identifier),
optional($.as_clause),
optional(seq('=', field('initializer', $.expression)))
)),
$._terminator
),
compound_assignment_statement: $ => seq(
field('left', $.left_hand_side),
field('operator', choice('+=', '-=', '*=', '/=', '\\=', '^=', '&=', '<<=', '>>=')),
field('right', $.expression),
$._terminator
),
assignment_statement: $ => seq(
field('left', $.left_hand_side),
'=',
field('right', $.expression),
$._terminator
),
left_hand_side: $ => choice(
$.identifier,
$.member_access,
$.element_access
),
call_statement: $ => seq(
choice(seq(kw('Call'), $.expression), $.expression),
$._terminator
),
if_statement: $ => choice(
prec(2, seq(
kw('If'), field('condition', $.expression), kw('Then'),
field('then_branch', choice($.statement, $.expression)),
optional(seq(kw('Else'), field('else_branch', choice($.statement, $.expression))))
)),
prec(1, seq(
kw('If'), field('condition', $.expression), kw('Then'), $._terminator,
repeat($.statement),
repeat($.elseif_clause),
optional($.else_clause),
kw('End'), kw('If'), $._terminator
))
),
elseif_clause: $ => seq(
kw('ElseIf'), field('condition', $.expression), kw('Then'), $._terminator,
repeat($.statement)
),
else_clause: $ => seq(
kw('Else'), $._terminator,
repeat($.statement)
),
select_case_statement: $ => seq(
kw('Select'), kw('Case'), field('selector', $.expression), $._terminator,
repeat($.case_block),
optional($.case_else_block),
kw('End'), kw('Select'), $._terminator
),
case_block: $ => seq(
kw('Case'),
commaSep1($.case_clause),
$._terminator,
repeat($.statement)
),
case_else_block: $ => seq(
kw('Case'), kw('Else'), $._terminator,
repeat($.statement)
),
case_clause: $ => choice(
$.expression,
seq($.expression, kw('To'), $.expression), seq(kw('Is'), $.relational_operator, $.expression) ),
relational_operator: $ => token(choice('=', '<>', '<', '>', '<=', '>=')),
while_statement: $ => seq(
kw('While'), field('condition', $.expression), $._terminator,
repeat($.statement),
kw('End'), kw('While'), $._terminator
),
do_statement: $ => choice(
seq(kw('Do'), $._terminator,
repeat($.statement),
kw('Loop'), optional(choice(seq(kw('While'), $.expression), seq(kw('Until'), $.expression))), $._terminator),
seq(kw('Do'), choice(seq(kw('While'), $.expression), seq(kw('Until'), $.expression)), $._terminator,
repeat($.statement),
kw('Loop'), $._terminator)
),
for_statement: $ => seq(
kw('For'),
choice(
seq(
field('variable', $.identifier),
$.as_clause,
'=',
field('start', $.expression)
),
seq(
field('variable', $.identifier),
'=',
field('start', $.expression)
)
),
kw('To'), field('end', $.expression),
optional(seq(kw('Step'), field('step', $.expression))),
$._terminator,
repeat($.statement),
kw('Next'), optional(field('variable', $.identifier)), $._terminator
),
for_each_statement: $ => seq(
kw('For'), kw('Each'),
field('variable', $.identifier),
kw('In'),
field('collection', $.expression),
$._terminator,
repeat($.statement),
kw('Next'), optional(field('variable', $.identifier)), $._terminator
),
try_statement: $ => seq(
kw('Try'), $._terminator,
repeat($.statement),
repeat($.catch_block),
optional($.finally_block),
kw('End'), kw('Try'), $._terminator
),
catch_block: $ => seq(
kw('Catch'),
optional(seq(field('exception', $.identifier), optional(seq(kw('As'), field('type', $.type))))),
optional(seq(kw('When'), field('filter', $.expression))),
$._terminator,
repeat($.statement)
),
finally_block: $ => seq(
kw('Finally'), $._terminator,
repeat($.statement)
),
with_statement: $ => seq(
kw('With'), field('target', $.expression), $._terminator,
repeat($.statement),
kw('End'), kw('With'), $._terminator
),
using_statement: $ => seq(
kw('Using'),
choice(
seq(field('resource', $.identifier), $.as_clause, '=', field('value', $.expression)),
field('value', $.expression)
),
$._terminator,
repeat($.statement),
kw('End'), kw('Using'), $._terminator
),
sync_lock_statement: $ => seq(
kw('SyncLock'), field('lock', $.expression), $._terminator,
repeat($.statement),
kw('End'), kw('SyncLock'), $._terminator
),
return_statement: $ => seq(kw('Return'), optional($.expression), $._terminator),
exit_statement: $ => seq(kw('Exit'), choice(kw('Sub'), kw('Function'), kw('Property'), kw('Do'), kw('For'), kw('While'), kw('Select'), kw('Try')), $._terminator),
continue_statement: $ => seq(kw('Continue'), choice(kw('Do'), kw('For'), kw('While')), $._terminator),
throw_statement: $ => seq(kw('Throw'), optional($.expression), $._terminator),
goto_statement: $ => seq(kw('GoTo'), field('label', $.identifier), $._terminator),
redim_statement: $ => seq(
kw('ReDim'),
optional(kw('Preserve')),
commaSep1(seq(field('array', $.identifier), $.re_dim_clause)),
$._terminator
),
re_dim_clause: $ => seq(
'(', field('upper_bound', $.expression), optional(seq(kw('To'), $.expression)), ')'
),
preprocessor_directive: $ => token(seq('#', /[^\r\n]*/)),
expression: $ => choice(
$.literal,
$.identifier,
$.parenthesized_expression,
$.member_access,
$.element_access,
$.invocation,
$.unary_expression,
$.binary_expression,
$.ternary_expression,
$.new_expression,
$.lambda_expression,
$.array_literal,
),
array_literal: $ => seq(
'{',
optional(commaSep($.expression)),
'}'
),
parenthesized_expression: $ => seq('(', $.expression, ')'),
lambda_expression: $ => prec.right(seq(
choice(kw('Function'), kw('Sub')),
'(',
optional(commaSep($.lambda_parameter)),
')',
choice(
$.expression, seq( $._terminator,
repeat($.statement),
kw('End'), choice(kw('Function'), kw('Sub'))
)
)
)),
lambda_parameter: $ => seq(
field('name', $.identifier),
optional($.as_clause)
),
invocation: $ => prec.left(1, seq(
field('target', choice($.member_access, $.identifier)),
field('arguments', $.argument_list)
)),
argument_list: $ => seq(
'(',
optional(commaSep($.argument)),
')'
),
argument: $ => choice(
$.expression,
seq(field('name', $.identifier), ':', '=', $.expression) ),
member_access: $ => prec.left(10, seq(
field('object', $.expression),
'.',
field('member', $.identifier)
)),
element_access: $ => seq(
field('object', $.expression),
'(',
commaSep(field('index', $.expression)),
')'
),
new_expression: $ => seq(
kw('New'),
field('type', $.type),
optional($.argument_list),
optional(choice(
$.object_initializers,
$.with_initializer
))
),
with_initializer: $ => seq(
kw('With'),
'{',
optional(commaSep($.member_initializer)),
'}'
),
member_initializer: $ => seq(
'.',
field('member', $.identifier),
'=',
field('value', $.expression)
),
object_initializers: $ => seq(
'{',
optional(commaSep($.object_initializer)),
'}'
),
object_initializer: $ => choice(
seq('.', $.identifier, '=', $.expression), $.expression ),
unary_expression: $ => prec(8, seq(
field('operator', choice(kw('Not'), kw('AddressOf'), '-', '+')),
field('operand', $.expression)
)),
binary_expression: $ => {
const table = [
[7, choice('^')], [6, choice('*', '/', '\\', kw('Mod'))], [5, choice('+', '-')], [4, kw('&')], [3, choice('<<', '>>')], [2, choice('=', '<>', '<', '>', '<=', '>=', kw('Is'), kw('IsNot'), kw('Like'))],
[1, choice(kw('TypeOf'))], [0, choice(kw('And'), kw('Or'), kw('Xor'))], [-1, choice(kw('AndAlso'), kw('OrElse'))] ];
return choice(...table.map(([precedence, operator]) =>
prec.left(precedence, seq(field('left', $.expression), field('operator', operator), field('right', $.expression)))
));
},
ternary_expression: $ => prec.right(seq(
kw('If'),
'(',
field('condition', $.expression), ',',
field('true_branch', $.expression), ',',
field('false_branch', $.expression),
')'
)),
literal: $ => choice(
$.boolean_literal,
$.integer_literal,
$.floating_point_literal,
$.string_literal,
$.character_literal,
$.date_literal,
kw('Nothing')
),
boolean_literal: $ => token(choice(kw('True'), kw('False'))),
integer_literal: $ => token(choice(
/\d+(?:US|UI|UL|S|I|L|%|&)?/i,
/&H[0-9A-F]+(?:US|UI|UL|S|I|L|%|&)?/i,
/&O[0-7]+(?:US|UI|UL|S|I|L|%|&)?/i
)),
floating_point_literal: $ => token(choice(
/\d+\.\d+([Ee][+-]?\d+)?[FfRrDd!#@]?/,
/\d+\.([Ee][+-]?\d+)?[FfRrDd!#@]?/,
/\.\d+([Ee][+-]?\d+)?[FfRrDd!#@]?/,
/\d+([Ee][+-]?\d+)[FfRrDd!#@]?/,
/\d+[FfRrDd!#@]/
)),
string_literal: $ => choice(
token(seq(
'"',
repeat(choice(/[^"\r\n]/, /""/)),
'"'
)),
$.interpolated_string_literal
),
interpolated_string_literal: $ => seq(
'$"',
repeat(choice(
/[^"{}\r\n]+/, /""/, $.interpolation )),
'"'
),
interpolation: $ => seq(
'{',
$.expression,
optional(seq(':', /[^}]+/)), '}'
),
character_literal: $ => token(seq(
'"',
choice(/[^"\r\n]/, /""/), '"',
/[cC]/ )),
date_literal: $ => token(seq(
'#',
/[0-9/\-:\sAPMapm]+/, '#'
)),
identifier: $ => token(choice(
/\[(?:[^\]\r\n]+)\]/,
/[A-Za-z_][A-Za-z_0-9]*[$%&@#!]?/ )),
comment: $ => token(choice(
seq("'", /[^\r\n]*/),
seq(kw('REM'), /[^\r\n]*/)
)),
_newline: $ => /\r?\n/,
_line_continuation: $ => token(seq('_', /[ \t]*/, /\r?\n/)),
_terminator: $ => choice($._newline, ':')
}
});
function commaSep(rule) {
return optional(commaSep1(rule));
}
function kw(word) {
return token(prec(1, ci(word)));
}
function commaSep1(rule) {
return seq(rule, repeat(seq(token(seq(',', optional(/\r?\n/))), rule)));
}
function ci(keyword) {
const pattern = keyword.split('').map(ch => {
if (/[A-Za-z]/.test(ch)) {
return `[${ch.toLowerCase()}${ch.toUpperCase()}]`;
}
return ch.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
}).join('');
return new RegExp(pattern);
}