const PREC = {
PAREN: -1,
ASSIGN: 1,
TERNARY: 2,
LOGICAL_OR: 3,
LOGICAL_AND: 4,
IN: 4,
INCLUSIVE_OR: 5,
EXCLUSIVE_OR: 6,
BITWISE_AND: 7,
EQUALITY: 8,
COMPARISON: 9,
INSTANCEOF: 9,
SHIFT: 10,
ADDITIVE: 11,
MULTIPLICATIVE: 12,
UNARY: 13,
CALL: 14,
MEMBER: 15,
};
function optionalCommaSep(rule) {
return sep1(rule, optional(','));
}
function commaSep1(rule) {
return sep1(rule, ',');
}
function commaSep(rule) {
return optional(commaSep1(rule));
}
function sep1(rule, separator) {
return seq(rule, repeat(seq(separator, rule)));
}
module.exports = grammar({
name: 'squirrel',
externals: $ => [
$.verbatim_string,
],
extras: $ => [
$.comment,
/\s/,
],
inline: $ => [
$.expression_statement,
$._statements,
$._statement,
],
supertypes: $ => [
$.expression,
$.primary_expression,
],
word: $ => $.identifier,
rules: {
script: $ => optional($._statements),
_statements: $ => repeat1($._statement),
_statement: $ => choice(
$.block,
$.if_statement,
$.while_statement,
$.do_while_statement,
$.switch_statement,
$.for_statement,
$.foreach_statement,
$.break,
$.continue,
$.return,
$.yield,
$.local_declaration,
$.var_statement,
$.function_declaration,
$.class_declaration,
$.try_statement,
$.throw_statement,
$.const_declaration,
$.enum_declaration,
$.expression_statement,
),
expression_statement: $ =>
prec.right(choice(
seq(
$.expression,
optional(';'),
),
';',
)),
block: $ => seq(
'{',
repeat($._statement),
'}',
),
if_statement: $ => prec.right(1, seq(
'if',
field('condition', $.parenthesized_expression),
field('consequence', $._statement),
optional($.else_statement),
)),
else_statement: $ => prec.right(1, seq(
'else',
field('alternative', $._statement),
)),
while_statement: $ => prec.right(seq(
'while',
'(', $.expression, ')',
optional($._statement),
)),
do_while_statement: $ => prec.right(seq(
'do',
$._statement,
'while',
'(', $.expression, ')',
)),
switch_statement: $ => seq(
'switch',
'(', $.expression, ')',
'{',
repeat($.case_statement),
optional($.default_statement),
'}',
),
case_statement: $ => seq(
'case',
field('case', $.expression),
':',
repeat($._statement),
),
default_statement: $ => seq(
'default',
':',
repeat($._statement),
),
for_statement: $ => seq(
'for',
'(',
field('initial', optional(choice($.expression, $.local_declaration))),
';',
field('condition', optional($.expression)),
';',
field('increment', optional($.expression)),
')',
$._statement,
),
foreach_statement: $ => seq(
'foreach',
'(',
optional(seq(field('index', $.identifier), ',')),
field('value', $.identifier),
'in',
field('collection', $.expression),
')',
$._statement,
),
break: _ => seq('break', ';'),
continue: _ => seq('continue', ';'),
return: $ => prec.right(seq(
'return',
optional(choice($.expression, $.table)),
optional(';'),
)),
yield: $ => seq(
'yield',
optional($.expression),
choice(';', '\n'),
),
resume_expression: $ => seq(
'resume',
$.expression,
),
local_declaration: $ => prec.left(seq('local', $._initz, optional(';'))),
_initz: $ => prec.right(seq(
$.identifier,
optional(seq(choice('=', '<-'), choice($.expression, $.table))),
optional(seq(',', $._initz)),
)),
function_declaration: $ => seq(
'function',
$.identifier,
repeat(seq('::', $.identifier)),
'(',
optional($.parameters),
')',
$._statement,
),
parameters: $ => commaSep1($.parameter),
parameter: $ => choice(
seq(
$.identifier,
optional(seq('=', $.const_value)),
),
'...',
),
class_declaration: $ => seq(
'class',
$.identifier,
repeat(seq('.', $.identifier)),
optional(seq('extends', $.identifier)),
optional($.attribute_declaration),
'{',
repeat($.member_declaration),
'}',
),
member_declaration: $ => seq(
optional($.attribute_declaration),
choice(
seq(optional($.qualifier), $.identifier, '=', choice($.expression, $.table), optional(';')),
seq('[', $.expression, ']', '=', $.expression, optional(';')),
$.function_declaration,
seq('constructor', '(', optional($.parameters), ')', $._statement),
),
),
qualifier: _ => choice(
'static',
),
try_statement: $ => seq(
'try',
$._statement,
$.catch_statement,
),
catch_statement: $ => seq(
'catch',
'(',
$.identifier,
')',
$._statement,
),
throw_statement: $ => prec.right(seq(
'throw',
$.expression,
optional(';'),
)),
const_declaration: $ => seq(
'const',
$.identifier,
'=',
$.const_value,
choice(';', '\n'),
),
const_value: $ => choice(
$.array,
$.table,
$.integer,
$.float,
$.string,
$.verbatim_string,
$.char,
$.bool,
$.null,
$.call_expression,
$.identifier,
$.global_variable,
),
enum_declaration: $ => prec.right(seq(
'enum',
$.identifier,
optional($.attribute_declaration),
'{',
commaSep1(seq($.identifier, optional(seq('=', $.const_value)))),
'}',
optional(';'),
)),
attribute_declaration: $ => seq(
'</',
commaSep1(
seq(
field('left', $.identifier),
field('operator', '='),
field('right', $.const_value),
),
),
'/>',
),
expression: $ => choice(
$.delete_expression,
$.clone_expression,
$.array,
$.assignment_expression,
$.update_expression,
$.resume_expression,
$.primary_expression,
),
primary_expression: $ => prec.right(choice(
$.unary_expression,
$.binary_expression,
$.ternary_expression,
$.anonymous_function,
$.deref_expression,
$.index_expression,
$.call_expression,
$.lambda_expression,
$.parenthesized_expression,
$.integer,
$.float,
$.string,
$.verbatim_string,
$.char,
$.bool,
$.null,
$.identifier,
$.global_variable,
)),
unary_expression: $ => prec.left(PREC.UNARY, choice(
seq(
field('operator', choice('-', '~', '!', 'typeof', '++', '--')),
field('operand', choice($.expression, $.table)),
optional(';'),
),
seq(
field('operand', $.expression),
field('operator', choice('++', '--')),
optional(';'),
),
)),
binary_expression: $ => {
const table = [
['+', PREC.ADDITIVE],
['-', PREC.ADDITIVE],
['*', PREC.MULTIPLICATIVE],
['/', PREC.MULTIPLICATIVE],
['%', PREC.MULTIPLICATIVE],
['||', PREC.LOGICAL_OR],
['&&', PREC.LOGICAL_AND],
['in', PREC.IN],
['|', PREC.INCLUSIVE_OR],
['^', PREC.EXCLUSIVE_OR],
['&', PREC.BITWISE_AND],
['==', PREC.EQUALITY],
['!=', PREC.EQUALITY],
['<=>', PREC.COMPARISON],
['>', PREC.COMPARISON],
['>=', PREC.COMPARISON],
['<=', PREC.COMPARISON],
['<', PREC.COMPARISON],
['instanceof', PREC.INSTANCEOF],
['<<', PREC.SHIFT],
['>>', PREC.SHIFT],
['>>>', PREC.SHIFT],
];
return choice(...table.map(([operator, precedence]) => {
return prec.left(precedence, seq(
field('left', $.expression),
field('operator', operator),
field('right', $.expression),
optional(';'),
));
}));
},
ternary_expression: $ => prec.right(PREC.TERNARY, seq(
field('condition', $.expression),
'?',
field('consequence', choice($.expression, $.table)),
':',
field('alternative', choice($.expression, $.table)),
)),
assignment_expression: $ => prec.right(PREC.ASSIGN, seq(
field('left', $.expression),
field('operator', '='),
field('right', choice($.expression, $.table)),
)),
update_expression: $ => prec.right(PREC.ASSIGN, seq(
field('left', $.expression),
field('operator', choice(
'<-',
'+=',
'-=',
'*=',
'/=',
'%=',
)),
field('right', choice($.expression, $.table)),
)),
table: $ => prec.right(seq(
'{',
optional($.table_slots),
'}',
optional(seq('.', $.expression)),
)),
table_slots: $ => seq(
optionalCommaSep($.table_slot),
optional(','),
),
table_slot: $ => choice(
seq($.identifier, '=', choice($.expression, $.table)),
seq('[', $.expression, ']', '=', choice($.expression, $.table)),
seq($.expression, ':', choice($.expression, $.table)),
$.function_declaration,
),
delete_expression: $ => seq(
'delete',
$.expression,
choice(';', '\n'),
),
var_statement: $ => (seq(
'var',
$.identifier,
'=',
$.expression,
)),
deref_expression: $ => prec(PREC.MEMBER, choice(
seq(
$.expression,
'.',
$.identifier,
),
)),
index_expression: $ => prec.left(PREC.MEMBER, seq(
field('object', $.expression),
'[',
field('index', $.expression),
']',
)),
call_expression: $ => prec.left(PREC.CALL, seq(
choice(field('function', $.expression), 'rawcall'),
'(',
optional($.call_args),
')',
)),
call_args: $ => commaSep1(choice(
$.expression,
$.table,
)),
anonymous_function: $ => seq(
'function',
'(',
optional($.parameters),
')',
$._statement,
),
lambda_expression: $ => seq(
'@',
'(',
optional($.parameters),
')',
$.expression,
),
parenthesized_expression: $ => prec(PREC.PAREN, seq(
'(',
$.expression,
')',
)),
clone_expression: $ => prec.right(seq(
'clone',
$.expression,
)),
array: $ => prec.right(seq(
'[',
commaSep($.expression),
optional(','),
']',
)),
identifier: _ => /[a-zA-Z_][a-zA-Z0-9_]*/,
global_variable: $ => seq('::', $.identifier),
integer: _ => token(choice(
/0/,
/-?[1-9][0-9]*/,
/0[xX][0-9A-Fa-f]+/,
/'''[.]+'''/,
/0[0-7]+/,
)),
float: _ => token(choice(
/-?[0-9]+\.[0-9]+/,
/[0-9]+\.[eE][+-]?[0-9]+/,
)),
string: $ => seq(
'"',
repeat(choice(
$.string_content,
$._escape_sequence,
)),
'"',
),
string_content: _ => token.immediate(prec(1, /[^"\\]+/)),
char: $ => choice(
seq(
'\'',
choice(
$.escape_sequence,
/[^\\']/,
),
'\'',
),
'\'#\'',
),
_escape_sequence: $ =>
choice(
prec(2, token.immediate(seq('\\', /[^abfnrtvxu'\"\\\?]/))),
prec(1, $.escape_sequence),
),
escape_sequence: _ => token.immediate(seq(
'\\',
choice(
/[^xu0-7]/,
/[0-7]{1,3}/,
/x[0-9a-fA-F]{2}/,
/u[0-9a-fA-F]{4}/,
/u{[0-9a-fA-F]+}/,
))),
bool: _ => choice('true', 'false'),
null: _ => 'null',
comment: _ =>
token(
choice(
seq('#', /.*/),
seq('//', /(\\(.|\r?\n)|[^\\\n])*/),
seq('/*', /[^*]*\*+([^/*][^*]*\*+)*/, '/'),
),
),
},
});
module.exports.PREC = PREC;