const DIGITS = token(sep1(/[0-9]+/, /_+/))
const HEX_DIGITS = token(sep1(/[A-Fa-f0-9]+/, '_'))
const PREC = {
COMMENT: 0, ASSIGN: 1, SWITCH_EXP: 1, DECL: 2,
ELEMENT_VAL: 2,
TERNARY: 3, OR: 4, AND: 5, BIT_OR: 6, BIT_XOR: 7, BIT_AND: 8, EQUALITY: 9, GENERIC: 10,
REL: 10, SHIFT: 11, ADD: 12, MULT: 13, CAST: 14, OBJ_INST: 14, UNARY: 15, ARRAY: 16, OBJ_ACCESS: 16, PARENS: 16, };
module.exports = grammar({
name: 'apex',
extras: $ => [
$.line_comment,
$.block_comment,
/\s/
],
supertypes: $ => [
$.expression,
$.declaration,
$.statement,
$.primary_expression,
$._literal,
$._type,
$._simple_type,
$._unannotated_type,
$.comment,
$.database_query,
$.dml_statement
],
inline: $ => [
$._name,
$._simple_type,
$._reserved_identifier,
$._class_body_declaration,
$._variable_initializer
],
conflicts: $ => [
[$.modifiers, $.annotated_type, $.receiver_parameter],
[$._unannotated_type, $.primary_expression, $.inferred_parameters],
[$._unannotated_type, $.primary_expression],
[$._unannotated_type, $.primary_expression, $.scoped_type_identifier],
[$._unannotated_type, $.scoped_type_identifier],
[$._unannotated_type, $.generic_type],
[$.generic_type, $.primary_expression],
],
word: $ => $.identifier,
rules: {
program: $ => repeat($.statement),
_literal: $ => choice(
$.decimal_integer_literal,
$.decimal_floating_point_literal,
$.true,
$.false,
$.string_literal,
$.null_literal
),
decimal_integer_literal: $ => token(seq(
DIGITS,
optional(choice('l', 'L'))
)),
decimal_floating_point_literal: $ => token(choice(
seq(DIGITS, '.', optional(DIGITS), optional(seq((/[eE]/), optional(choice('-', '+')), DIGITS)), optional(/[fFdD]/)),
seq('.', DIGITS, optional(seq((/[eE]/), optional(choice('-', '+')), DIGITS)), optional(/[fFdD]/)),
seq(DIGITS, /[eEpP]/, optional(choice('-', '+')), DIGITS, optional(/[fFdD]/)),
seq(DIGITS, optional(seq((/[eE]/), optional(choice('-', '+')), DIGITS)), (/[fFdD]/))
)),
hex_floating_point_literal: $ => token(seq(
choice('0x', '0X'),
choice(
seq(HEX_DIGITS, optional('.')),
seq(optional(HEX_DIGITS), '.', HEX_DIGITS)
),
optional(seq(
/[eEpP]/,
optional(choice('-', '+')),
DIGITS,
optional(/[fFdD]/)
))
)),
true: $ => caseInsensitive('true'),
false: $ => caseInsensitive('false'),
string_literal: $ => token(
seq('\'', repeat(choice(/[^\\'\n]/, /\\(.|\n)/)), '\''),
),
null_literal: $ => caseInsensitive('null'),
expression: $ => choice(
$.assignment_expression,
$.binary_expression,
$.instanceof_expression,
$.ternary_expression,
$.update_expression,
$.primary_expression,
$.unary_expression,
$.cast_expression,
),
database_query: $ => choice($.soql_query, $.sosl_query),
sosl_query: $ => seq(
'[',
$.sosl_find_clause,
optional($.sosl_in_clause),
$.sosl_returning_clause,
']'
),
sosl_find_clause: $ => seq(
caseInsensitive('find'),
field("search_query", $._query_value_reference)
),
sosl_in_clause: $ => seq(
caseInsensitive('in'),
choice(
caseInsensitive('all'),
caseInsensitive('email'),
caseInsensitive('name'),
caseInsensitive('phone'),
caseInsensitive('sidebar')
),
caseInsensitive('fields')
),
sosl_returning_clause: $ => seq(
caseInsensitive('returning'),
commaSep1($.sosl_entity_definition)
),
sosl_entity_definition: $ => seq(
field('sobject', $.identifier)
),
_query_value_reference: $ => choice(
seq(':', $.expression),
$._literal
),
soql_query: $ => seq(
'[',
$.select_clause,
$.from_clause,
optional($.where_clause),
optional($.group_by_clause),
optional($.having_clause),
optional($.order_by_clause),
optional($.limit_clause),
optional($.offset_clause),
']'
),
select_clause: $ => seq(
caseInsensitive('SELECT'),
commaSep(choice(
$.field_name,
$.aggregation_function,
$.typeof_clause,
field('relationship_query', $.soql_query)
))
),
aggregation_function: $ => seq(
field('aggr_function', $.identifier),
'(',
$.field_name,
')',
optional(field('alias', $.identifier))
),
queried_value: $ => choice($.aggregation_function, $.field_name),
from_clause: $ => seq(
caseInsensitive('FROM'),
commaSep1($.from_clause_source)
),
from_clause_source: $ => seq(
field('relationship_name', $.field_name),
optional(field('alias', $.identifier))
),
where_clause: $ => seq(
caseInsensitive('WHERE'),
$._field_expression_wrapper
),
field_expression: $ => choice(seq(
field('field', $.field_name),
field('operator', choice(
'=',
'!=',
caseInsensitive('in'),
caseInsensitive('like')
)),
field('value', $._query_value_reference)
),
seq('(', $.field_expression, ')')
),
_field_expression_wrapper: $ => choice(
$.field_expression,
$.complex_field_expression,
),
parenthesized_field_expression: $ => seq(
'(',
$._field_expression_wrapper,
')'
),
complex_field_expression: $ => prec.left(10, seq(
field('left', $._field_expression_wrapper),
field('operator', choice(caseInsensitive('OR'), caseInsensitive('AND'))),
field('right', $._field_expression_wrapper)
)),
where_condition: $ => choice(
$.field_expression,
seq('(', $.field_expression)
),
typeof_clause: $ => seq(
caseInsensitive('typeof'),
$.field_name,
repeat($.typeof_clause_branch),
optional($.typeof_clause_else_branch),
caseInsensitive('end')
),
typeof_clause_else_branch: $ => seq(
caseInsensitive('else'),
commaSep1($.queried_value)
),
typeof_clause_branch: $ => seq(
caseInsensitive('WHEN'),
field('object_type', $.identifier),
caseInsensitive('THEN'),
commaSep1($.queried_value)
),
field_name: $ => dotSeparated($.identifier),
order_by_clause: $ => seq(
caseInsensitive('Order'),
caseInsensitive('by'),
commaSep1(seq(
field("field", $.field_name),
field("order", optional(choice(
caseInsensitive('ASC'),
caseInsensitive('desc')
))),
field("null_order", optional(seq(
caseInsensitive('nulls'),
choice(
caseInsensitive('first'),
caseInsensitive('last')
)
)))
))
),
limit_clause: $ => seq(
caseInsensitive('limit'),
field('limit', $._query_value_reference)
),
offset_clause: $ => seq(
caseInsensitive('offset'),
field('offset', $._query_value_reference)
),
group_by_clause: $ => seq(
caseInsensitive('GROUP'),
caseInsensitive('BY')
),
having_clause: $ => seq(
caseInsensitive('having')
),
dml_statement: $=> choice(
$.dml_insert_statement,
$.dml_update_statement,
$.dml_upsert_statement,
$.dml_delete_statement,
$.dml_undelete_statemetn,
),
dml_insert_statement: $=> seq(
caseInsensitive('insert'),
field('value', $.primary_expression),
';'
),
dml_update_statement: $=> seq(
caseInsensitive('update'),
field('value', $.primary_expression),
';'
),
dml_upsert_statement: $=> seq(
caseInsensitive('upsert'),
field('value', $.primary_expression),
optional(field('field', $.field_name)),
';'
),
dml_delete_statement: $=>seq(
caseInsensitive('delete'),
field('value', $.expression),
';'
),
dml_undelete_statemetn: $=> seq(
caseInsensitive('undelete'),
field('value', $.expression),
';'
),
cast_expression: $ => prec(PREC.CAST, seq(
'(',
sep1(field('type', $._type), '&'),
')',
field('value', $.expression)
)),
assignment_expression: $ => prec.right(PREC.ASSIGN, seq(
field('left', choice(
$.identifier,
$._reserved_identifier,
$.field_access,
$.array_access
)),
field('operator', choice('=', '+=', '-=', '*=', '/=', '&=', '|=', '^=', '%=', '<<=', '>>=', '>>>=')),
field('right', $.expression)
)),
binary_expression: $ => choice(
...[
['>', PREC.REL],
['<', PREC.REL],
['>=', PREC.REL],
['<=', PREC.REL],
['==', PREC.EQUALITY],
['===', PREC.EQUALITY],
['!=', PREC.EQUALITY],
['&&', PREC.AND],
['||', PREC.OR],
['+', PREC.ADD],
['-', PREC.ADD],
['*', PREC.MULT],
['/', PREC.MULT],
['&', PREC.BIT_AND],
['|', PREC.BIT_OR],
['^', PREC.BIT_XOR],
['%', PREC.MULT],
['<<', PREC.SHIFT],
['>>', PREC.SHIFT],
['>>>', PREC.SHIFT],
].map(([operator, precedence]) =>
prec.left(precedence, seq(
field('left', $.expression),
field('operator', operator),
field('right', $.expression)
))
)),
instanceof_expression: $ => prec(PREC.REL, seq(
field('left', $.expression),
caseInsensitive('instanceof'),
field('right', $._type)
)),
inferred_parameters: $ => seq(
'(',
commaSep1($.identifier),
')'
),
ternary_expression: $ => prec.right(PREC.TERNARY, seq(
field('condition', $.expression),
'?',
field('consequence', $.expression),
':',
field('alternative', $.expression)
)),
unary_expression: $ => choice(...[
['+', PREC.UNARY],
['-', PREC.UNARY],
['!', PREC.UNARY],
['~', PREC.UNARY],
].map(([operator, precedence]) =>
prec.left(precedence, seq(
field('operator', operator),
field('operand', $.expression)
))
)),
update_expression: $ => prec.left(PREC.UNARY, choice(
seq($.expression, choice('++', '--')),
seq(choice('++', '--'), $.expression),
)),
primary_expression: $ => choice(
$._literal,
$.database_query,
$.class_literal,
$.this,
$.identifier,
$.parenthesized_expression,
$.object_creation_expression,
$.field_access,
$.array_access,
$.method_invocation,
$.array_creation_expression,
),
array_creation_expression: $ => prec.right(seq(
'new',
field('type', $._simple_type),
choice(
seq(
field('dimensions', repeat1($.dimensions_expr)),
field('dimensions', optional($.dimensions))
),
seq(
field('dimensions', $.dimensions),
field('value', $.array_initializer)
)
)
)),
dimensions_expr: $ => seq('[', $.expression, ']'),
parenthesized_expression: $ => seq('(', $.expression, ')'),
class_literal: $ => seq($._unannotated_type, '.', 'class'),
object_creation_expression: $ => choice(
$._unqualified_object_creation_expression,
seq($.primary_expression, '.', $._unqualified_object_creation_expression)
),
_unqualified_object_creation_expression: $ => prec.right(seq(
'new',
field('type_arguments', optional($.type_arguments)),
field('type', $._simple_type),
field('arguments', $.argument_list),
optional($.class_body)
)),
field_access: $ => seq(
field('object', choice($.primary_expression, $.super)),
optional(seq(
'.',
$.super
)),
'.',
field('field', choice($.identifier, $._reserved_identifier, $.this))
),
array_access: $ => seq(
field('array', $.primary_expression),
'[',
field('index', $.expression),
']',
),
method_invocation: $ => seq(
choice(
field('name', choice($.identifier, $._reserved_identifier)),
seq(
field('object', choice($.primary_expression, $.super)),
'.',
optional(seq(
$.super,
'.'
)),
field('type_arguments', optional($.type_arguments)),
field('name', choice($.identifier, $._reserved_identifier)),
)
),
field('arguments', $.argument_list)
),
argument_list: $ => seq('(', commaSep($.expression), ')'),
type_arguments: $ => seq(
'<',
commaSep(choice($._type, $.wildcard)),
'>'
),
wildcard: $ => seq(
repeat($._annotation),
'?',
optional($._wildcard_bounds)
),
_wildcard_bounds: $ => choice(
seq('extends', $._type),
seq($.super, $._type)
),
dimensions: $ => prec.right(repeat1(
seq('[', ']')
)),
switch_expression: $ => seq(
caseInsensitive('switch'),
caseInsensitive('on'),
field('condition', $.expression),
field('body', $.switch_block)
),
switch_block: $ => seq(
'{',
repeat(choice(
$.switch_block_statement_group,
$.sobject_switch_statement_group
)),
optional($.default_switch_statement_group),
'}'
),
switch_block_statement_group: $ => prec.left(seq(
caseInsensitive('when'),
field('condition', commaSep1($.expression)),
$.block,
)),
default_switch_statement_group: $ => prec.left(seq(
caseInsensitive('when'),
caseInsensitive('default'),
$.block
)),
sobject_switch_statement_group: $=> seq(
caseInsensitive('when'),
field('sobject_type', $.identifier),
field('var_name', $.identifier),
field('code', $.block)
),
statement: $ => choice(
$.declaration,
$.expression_statement,
$.if_statement,
$.while_statement,
$.for_statement,
$.enhanced_for_statement,
$.block,
$.do_statement,
$.break_statement,
$.continue_statement,
$.return_statement,
$.switch_expression, $.local_variable_declaration,
$.throw_statement,
$.try_statement,
$.dml_statement
),
block: $ => seq(
'{', repeat($.statement), '}'
),
expression_statement: $ => seq(
$.expression,
';'
),
do_statement: $ => seq(
caseInsensitive('do'),
field('body', $.statement),
caseInsensitive('while'),
field('condition', $.parenthesized_expression),
';'
),
break_statement: $ => seq(caseInsensitive('break'), optional($.identifier), ';'),
continue_statement: $ => seq(caseInsensitive('continue'), optional($.identifier), ';'),
return_statement: $ => seq(
caseInsensitive('return'),
optional($.expression),
';'
),
throw_statement: $ => seq(caseInsensitive('throw'), $.expression, ';'),
try_statement: $ => seq(
caseInsensitive('try'),
field('body', $.block),
choice(
repeat1($.catch_clause),
seq(repeat($.catch_clause), $.finally_clause)
)
),
catch_clause: $ => seq(
caseInsensitive('catch'),
'(',
$.catch_formal_parameter,
')',
field('body', $.block)
),
catch_formal_parameter: $ => seq(
optional($.modifiers),
$.catch_type,
$._variable_declarator_id
),
catch_type: $ => sep1($._unannotated_type, '|'),
finally_clause: $ => seq(caseInsensitive('finally'), $.block),
if_statement: $ => prec.right(seq(
caseInsensitive('if'),
field('condition', $.parenthesized_expression),
field('consequence', $.statement),
optional(seq(caseInsensitive('else'), field('alternative', $.statement)))
)),
while_statement: $ => seq(
caseInsensitive('while'),
field('condition', $.parenthesized_expression),
field('body', $.statement)
),
for_statement: $ => seq(
caseInsensitive('for'), '(',
choice(
field('init', $.local_variable_declaration),
seq(
commaSep(field('init', $.expression)),
';'
)
),
field('condition', optional($.expression)), ';',
commaSep(field('update', $.expression)), ')',
field('body', $.statement)
),
enhanced_for_statement: $ => seq(
caseInsensitive('for'),
'(',
optional($.modifiers),
field('type', $._unannotated_type),
$._variable_declarator_id,
':',
field('value', $.expression),
')',
field('body', $.statement)
),
_annotation: $ => choice(
$.marker_annotation,
$.annotation
),
marker_annotation: $ => seq(
'@',
field('name', $._name)
),
annotation: $ => seq(
'@',
field('name', $._name),
field('arguments', $.annotation_argument_list)
),
annotation_argument_list: $ => seq(
'(',
choice(
$._element_value,
commaSep($.element_value_pair),
),
')'
),
element_value_pair: $ => seq(
field('key', $.identifier),
'=',
field('value', $._element_value)
),
_element_value: $ => prec(PREC.ELEMENT_VAL, choice(
$.expression,
$.element_value_array_initializer,
$._annotation
)),
element_value_array_initializer: $ => seq(
'{',
commaSep($._element_value),
optional(','),
'}'
),
declaration: $ => prec(PREC.DECL, choice(
$.class_declaration,
$.interface_declaration,
$.enum_declaration,
)),
asterisk: $ => '*',
enum_declaration: $ => seq(
optional($.modifiers),
caseInsensitive('enum'),
field('name', $.identifier),
field('interfaces', optional($.super_interfaces)),
field('body', $.enum_body)
),
enum_body: $ => seq(
'{',
commaSep($.enum_constant),
optional(','),
optional($.enum_body_declarations),
'}'
),
enum_body_declarations: $ => seq(
';',
repeat($._class_body_declaration)
),
enum_constant: $ => (seq(
optional($.modifiers),
field('name', $.identifier),
field('arguments', optional($.argument_list)),
field('body', optional($.class_body))
)),
class_declaration: $ => seq(
optional($.modifiers),
optional($.access_modifiers),
caseInsensitive('class'),
field('name', $.identifier),
optional(field('type_parameters', $.type_parameters)),
optional(field('superclass', $.superclass)),
optional(field('interfaces', $.super_interfaces)),
field('body', $.class_body)
),
access_modifiers: $ => seq(
choice(
caseInsensitive('with'),
caseInsensitive('without'),
caseInsensitive('inherited')
),
caseInsensitive('sharing')
),
modifiers: $ => repeat1(choice(
$._annotation,
caseInsensitive('public'),
caseInsensitive('protected'),
caseInsensitive('private'),
caseInsensitive('global'),
caseInsensitive('virtual'),
caseInsensitive('static'),
caseInsensitive('final'),
)),
type_parameters: $ => seq(
'<', commaSep1($.type_parameter), '>'
),
type_parameter: $ => seq(
repeat($._annotation),
alias($.identifier, $.type_identifier),
optional($.type_bound)
),
type_bound: $ => seq('extends', $._type, repeat(seq('&', $._type))),
superclass: $ => seq(
'extends',
$._type
),
super_interfaces: $ => seq(
'implements',
$.interface_type_list
),
interface_type_list: $ => seq(
$._type,
repeat(seq(',', $._type))
),
class_body: $ => seq(
'{',
repeat($._class_body_declaration),
'}'
),
_class_body_declaration: $ => choice(
$.field_declaration,
$.method_declaration,
$.class_declaration,
$.interface_declaration,
$.enum_declaration,
$.block,
$.static_initializer,
$.constructor_declaration,
';'
),
static_initializer: $ => seq(
caseInsensitive('static'),
$.block
),
constructor_declaration: $ => seq(
optional($.modifiers),
$._constructor_declarator,
field('body', $.constructor_body)
),
_constructor_declarator: $ => seq(
field('type_parameters', optional($.type_parameters)),
field('name', $.identifier),
field('parameters', $.formal_parameters)
),
constructor_body: $ => seq(
'{',
optional($.explicit_constructor_invocation),
repeat($.statement),
'}'
),
explicit_constructor_invocation: $ => seq(
choice(
seq(
field('type_arguments', optional($.type_arguments)),
field('constructor', choice($.this, $.super)),
),
seq(
field('object', choice($.primary_expression)),
'.',
field('type_arguments', optional($.type_arguments)),
field('constructor', $.super),
)
),
field('arguments', $.argument_list),
';'
),
_name: $ => choice(
$.identifier,
$._reserved_identifier,
$.scoped_identifier
),
scoped_identifier: $ => seq(
field('scope', $._name),
'.',
field('name', $.identifier)
),
field_declaration: $ => seq(
optional($.modifiers),
field('type', $._unannotated_type),
$._variable_declarator_list,
';'
),
_default_value: $ => seq(
'default',
field('value', $._element_value)
),
interface_declaration: $ => seq(
optional($.modifiers),
caseInsensitive('interface'),
field('name', $.identifier),
field('type_parameters', optional($.type_parameters)),
optional($.extends_interfaces),
field('body', $.interface_body)
),
extends_interfaces: $ => seq(
caseInsensitive('extends'),
$.interface_type_list
),
interface_body: $ => seq(
'{',
repeat(choice(
$.constant_declaration,
$.enum_declaration,
$.method_declaration,
$.class_declaration,
$.interface_declaration,
';'
)),
'}'
),
constant_declaration: $ => seq(
optional($.modifiers),
field('type', $._unannotated_type),
$._variable_declarator_list,
';'
),
_variable_declarator_list: $ => commaSep1(
field('declarator', $.variable_declarator)
),
variable_declarator: $ => seq(
$._variable_declarator_id,
optional(seq('=', field('value', $._variable_initializer)))
),
_variable_declarator_id: $ => seq(
field('name', choice($.identifier, $._reserved_identifier)),
field('dimensions', optional($.dimensions))
),
_variable_initializer: $ => choice(
$.expression,
$.array_initializer
),
array_initializer: $ => seq(
'{',
commaSep($._variable_initializer),
optional(','),
'}'
),
_type: $ => choice(
$._unannotated_type,
$.annotated_type
),
_unannotated_type: $ => choice(
$._simple_type,
$.array_type
),
_simple_type: $ => choice(
$.void_type,
$.integral_type,
$.floating_point_type,
$.boolean_type,
alias($.identifier, $.type_identifier),
$.scoped_type_identifier,
$.generic_type
),
annotated_type: $ => seq(
repeat1($._annotation),
$._unannotated_type
),
scoped_type_identifier: $ => seq(
choice(
alias($.identifier, $.type_identifier),
$.scoped_type_identifier,
$.generic_type
),
'.',
repeat($._annotation),
alias($.identifier, $.type_identifier)
),
generic_type: $ => prec.dynamic(PREC.GENERIC, seq(
choice(
alias($.identifier, $.type_identifier),
$.scoped_type_identifier
),
$.type_arguments
)),
array_type: $ => seq(
field('element', $._unannotated_type),
field('dimensions', $.dimensions)
),
integral_type: $ => choice(
caseInsensitive('integer'),
caseInsensitive('long'),
),
floating_point_type: $ => choice(
caseInsensitive('double'),
caseInsensitive('decimal')
),
boolean_type: $ => caseInsensitive('boolean'),
void_type: $ => caseInsensitive('void'),
_method_header: $ => seq(
optional(seq(
field('type_parameters', $.type_parameters),
repeat($._annotation)
)),
field('type', $._unannotated_type),
$._method_declarator,
),
_method_declarator: $ => seq(
field('name', choice($.identifier, $._reserved_identifier)),
field('parameters', $.formal_parameters),
field('dimensions', optional($.dimensions))
),
formal_parameters: $ => seq(
'(',
optional($.receiver_parameter),
commaSep($.formal_parameter),
')'
),
formal_parameter: $ => seq(
optional($.modifiers),
field('type', $._unannotated_type),
$._variable_declarator_id
),
receiver_parameter: $ => seq(
$._unannotated_type,
optional(seq($.identifier, '.')),
$.this
),
local_variable_declaration: $ => seq(
optional($.modifiers),
field('type', $._unannotated_type),
$._variable_declarator_list,
';'
),
method_declaration: $ => seq(
optional($.modifiers),
$._method_header,
choice(field('body', $.block), ';')
),
_reserved_identifier: $ => alias(choice(
'open',
'module'
), $.identifier),
this: $ => caseInsensitive('this'),
super: $ => caseInsensitive('super'),
identifier: $ => /[\p{L}_$][\p{L}\p{Nd}_$]*/,
comment: $ => choice(
$.line_comment,
$.block_comment,
),
line_comment: $ => token(prec(PREC.COMMENT, seq('//', /[^\n]*/))),
block_comment: $ => token(prec(PREC.COMMENT,
seq(
'/*',
/[^*]*\*+([^/*][^*]*\*+)*/,
'/'
)
)),
}
});
function sep1(rule, separator) {
return seq(rule, repeat(seq(separator, rule)));
}
function commaSep1(rule) {
return seq(rule, repeat(seq(',', rule)))
}
function dotSeparated(rule) {
return seq(rule, optional(repeat(seq('.', rule))))
}
function commaSep(rule) {
return optional(commaSep1(rule))
}
function caseInsensitive(keyword) {
return new RegExp(keyword.split("").map(letter => `[${letter}${letter.toUpperCase()}]`).join(""))
}