mirror of https://github.com/Wilfred/difftastic/
411 lines
8.9 KiB
JavaScript
411 lines
8.9 KiB
JavaScript
const PREC = {
|
|
COMMA: -1,
|
|
PRIORITY: 1,
|
|
|
|
OR: 1, //=> or
|
|
AND: 2, //=> and
|
|
COMPARE: 3, //=> < <= == ~= >= >
|
|
BIT_OR: 4, //=> |
|
|
BIT_NOT: 5, //=> ~
|
|
BIT_AND: 6, //=> &
|
|
SHIFT: 7, //=> << >>
|
|
CONCAT: 8, //=> ..
|
|
PLUS: 9, //=> + -
|
|
MULTI: 10, //=> * / // %
|
|
UNARY: 11, //=> not # - ~
|
|
POWER: 12 //=> ^
|
|
}
|
|
|
|
module.exports = grammar({
|
|
name: 'lua',
|
|
|
|
extras: $ => [
|
|
$.comment,
|
|
/[\s\n]/
|
|
],
|
|
|
|
inline: $ => [
|
|
$._statement
|
|
],
|
|
|
|
conflicts: $ => [
|
|
[$._prefix],
|
|
[$._expression, $._variable_declarator],
|
|
[$._expression, $.function_call_statement],
|
|
[$.function_name, $.function_name_field]
|
|
],
|
|
|
|
externals: $ => [
|
|
$.comment,
|
|
$.string
|
|
],
|
|
|
|
rules: {
|
|
program: $ => seq(optional($.shebang), repeat($._statement), optional($.return_statement)),
|
|
|
|
shebang: $ => /#!.*\n/,
|
|
|
|
// Return statement
|
|
return_statement: $ => seq(
|
|
'return',
|
|
optional(commaSeq($._expression)),
|
|
optional($._empty_statement)
|
|
),
|
|
|
|
// Statements
|
|
_statement: $ => choice(
|
|
alias($._expression, $.expression),
|
|
|
|
$.variable_declaration,
|
|
$.local_variable_declaration,
|
|
|
|
$.do_statement,
|
|
$.if_statement,
|
|
$.while_statement,
|
|
$.repeat_statement,
|
|
$.for_statement,
|
|
$.for_in_statement,
|
|
|
|
$.goto_statement,
|
|
$.break_statement,
|
|
|
|
$.label_statement,
|
|
$._empty_statement,
|
|
|
|
alias($.function_statement, $.function),
|
|
alias($.local_function_statement, $.local_function),
|
|
alias($.function_call_statement, $.function_call)
|
|
),
|
|
|
|
// Statements: Variable eclarations
|
|
variable_declaration: $ => seq(
|
|
commaSeq(alias($._variable_declarator, $.variable_declarator)),
|
|
'=',
|
|
commaSeq($._expression)
|
|
),
|
|
|
|
local_variable_declaration: $ => seq(
|
|
'local',
|
|
alias($._local_variable_declarator, $.variable_declarator),
|
|
optional(seq('=', commaSeq($._expression)))
|
|
),
|
|
|
|
_variable_declarator: $ => choice(
|
|
$.identifier,
|
|
seq($._prefix, '[', $._expression, ']'),
|
|
$.field_expression
|
|
),
|
|
|
|
field_expression: $ => seq($._prefix, '.', alias($.identifier, $.property_identifier)),
|
|
|
|
_local_variable_declarator: $ => commaSeq($.identifier),
|
|
|
|
// Statements: Control statements
|
|
do_statement: $ => seq(
|
|
'do',
|
|
repeat($._statement),
|
|
optional($.return_statement),
|
|
'end'
|
|
),
|
|
|
|
if_statement: $ => seq(
|
|
'if',
|
|
alias($._expression, $.condition_expression),
|
|
'then',
|
|
repeat($._statement),
|
|
optional($.return_statement),
|
|
repeat($.elseif),
|
|
optional($.else),
|
|
'end'
|
|
),
|
|
|
|
elseif: $ => seq(
|
|
'elseif',
|
|
alias($._expression, $.condition_expression),
|
|
'then',
|
|
repeat($._statement),
|
|
optional($.return_statement)
|
|
),
|
|
|
|
else: $ => seq(
|
|
'else',
|
|
repeat($._statement),
|
|
optional($.return_statement)
|
|
),
|
|
|
|
while_statement: $ => seq(
|
|
'while',
|
|
alias($._expression, $.condition_expression),
|
|
'do',
|
|
repeat($._statement),
|
|
optional($.return_statement),
|
|
'end'
|
|
),
|
|
|
|
repeat_statement: $ => seq(
|
|
'repeat',
|
|
repeat($._statement),
|
|
optional($.return_statement),
|
|
'until',
|
|
alias($._expression, $.condition_expression)
|
|
),
|
|
|
|
// Statements: For statements
|
|
for_statement: $ => seq(
|
|
'for',
|
|
alias($._loop_expression, $.loop_expression),
|
|
'do',
|
|
repeat($._statement),
|
|
optional($.return_statement),
|
|
'end'
|
|
),
|
|
|
|
for_in_statement: $ => seq(
|
|
'for',
|
|
alias($._in_loop_expression, $.loop_expression),
|
|
'do',
|
|
repeat($._statement),
|
|
optional($.return_statement),
|
|
'end'
|
|
),
|
|
|
|
_loop_expression: $ => seq(
|
|
$.identifier,
|
|
'=',
|
|
$._expression,
|
|
',',
|
|
$._expression,
|
|
optional(seq(',', $._expression))
|
|
),
|
|
|
|
_in_loop_expression: $ => seq(
|
|
commaSeq($.identifier),
|
|
'in',
|
|
commaSeq($._expression),
|
|
),
|
|
|
|
// Statements: Simple statements
|
|
goto_statement: $ => seq(
|
|
'goto',
|
|
$.identifier
|
|
),
|
|
|
|
break_statement: $ => 'break',
|
|
|
|
// Statements: Void statements
|
|
label_statement: $ => seq(
|
|
'::',
|
|
$.identifier,
|
|
'::'
|
|
),
|
|
|
|
_empty_statement: $ => ';',
|
|
|
|
// Statements: Function statements
|
|
function_statement: $ => seq(
|
|
'function',
|
|
$.function_name,
|
|
$._function_body
|
|
),
|
|
|
|
local_function_statement: $ => seq(
|
|
'local',
|
|
'function',
|
|
$.identifier,
|
|
$._function_body
|
|
),
|
|
|
|
function_call_statement: $ => prec.dynamic(PREC.PRIORITY, choice(
|
|
seq($._prefix, $.arguments),
|
|
seq($._prefix, ':', alias($.identifier, $.method), $.arguments)
|
|
)),
|
|
|
|
arguments: $ => choice(
|
|
seq('(', optional(commaSeq($._expression)), ')'),
|
|
$.table,
|
|
$.string
|
|
),
|
|
|
|
function_name: $ => seq(
|
|
choice($.identifier,
|
|
$.function_name_field
|
|
),
|
|
optional(seq(':', alias($.identifier, $.method)))
|
|
),
|
|
|
|
function_name_field: $ => seq(
|
|
field("object", $.identifier),
|
|
repeat(seq('.', alias($.identifier, $.property_identifier))),
|
|
),
|
|
|
|
parameters: $ => seq(
|
|
'(',
|
|
optional(seq(
|
|
choice($.self, $.spread, $.identifier),
|
|
repeat(seq(',', $.identifier)),
|
|
optional(seq(',', $.spread))
|
|
)),
|
|
')'
|
|
),
|
|
|
|
_function_body: $ => seq(
|
|
$.parameters,
|
|
repeat($._statement),
|
|
optional($.return_statement),
|
|
'end'
|
|
),
|
|
|
|
// Expressions
|
|
_expression: $ => choice(
|
|
$.spread,
|
|
$._prefix,
|
|
|
|
$.next,
|
|
|
|
$.function_definition,
|
|
|
|
$.table,
|
|
|
|
$.binary_operation,
|
|
$.unary_operation,
|
|
|
|
$.string,
|
|
$.number,
|
|
$.nil,
|
|
$.true,
|
|
$.false,
|
|
$.identifier
|
|
),
|
|
|
|
// Expressions: Common
|
|
spread: $ => '...',
|
|
|
|
self: $ => 'self',
|
|
|
|
next: $ => 'next',
|
|
|
|
global_variable: $ => choice('_G', '_VERSION'),
|
|
|
|
_prefix: $ => choice(
|
|
$.self,
|
|
$.global_variable,
|
|
$._variable_declarator,
|
|
prec(-1, alias($.function_call_statement, $.function_call)),
|
|
seq('(', $._expression, ')')
|
|
),
|
|
|
|
// Expressions: Function definition
|
|
function_definition: $ => seq(
|
|
'function',
|
|
$._function_body
|
|
),
|
|
|
|
// Expressions: Table expressions
|
|
table: $ => seq(
|
|
'{',
|
|
optional($._field_sequence),
|
|
'}'
|
|
),
|
|
|
|
field: $ => choice(
|
|
seq('[', $._expression, ']', '=', $._expression),
|
|
seq($.identifier, '=', $._expression),
|
|
$._expression
|
|
),
|
|
|
|
_field_sequence: $ => prec(PREC.COMMA, seq(
|
|
$.field,
|
|
repeat(seq($._field_sep, $.field)),
|
|
optional($._field_sep)
|
|
)),
|
|
|
|
_field_sep: $ => choice(',', ';'),
|
|
|
|
// Expressions: Operation expressions
|
|
binary_operation: $ => choice(
|
|
...[
|
|
['or', PREC.OR],
|
|
['and', PREC.AND],
|
|
['<', PREC.COMPARE],
|
|
['<=', PREC.COMPARE],
|
|
['==', PREC.COMPARE],
|
|
['~=', PREC.COMPARE],
|
|
['>=', PREC.COMPARE],
|
|
['>', PREC.COMPARE],
|
|
['|', PREC.BIT_OR],
|
|
['~', PREC.BIT_NOT],
|
|
['&', PREC.BIT_AND],
|
|
['<<', PREC.SHIFT],
|
|
['>>', PREC.SHIFT],
|
|
['+', PREC.PLUS],
|
|
['-', PREC.PLUS],
|
|
['*', PREC.MULTI],
|
|
['/', PREC.MULTI],
|
|
['//', PREC.MULTI],
|
|
['%', PREC.MULTI],
|
|
].map(([operator, precedence]) => prec.left(precedence, seq(
|
|
$._expression,
|
|
operator,
|
|
$._expression
|
|
))),
|
|
...[
|
|
['..', PREC.CONCAT],
|
|
['^', PREC.POWER],
|
|
].map(([operator, precedence]) => prec.right(precedence, seq(
|
|
$._expression,
|
|
operator,
|
|
$._expression
|
|
)))
|
|
),
|
|
|
|
unary_operation: $ => prec.left(PREC.UNARY, seq(
|
|
choice('not', '#', '-', '~'),
|
|
$._expression
|
|
)),
|
|
|
|
// Expressions: Primitives
|
|
number: $ => {
|
|
const decimal_digits = /[0-9]+/
|
|
const signed_integer = seq(optional(choice('-', '+')), decimal_digits)
|
|
const decimal_exponent_part = seq(choice('e', 'E'), signed_integer)
|
|
|
|
const decimal_integer_literal = choice(
|
|
'0',
|
|
seq(optional('0'), /[1-9]/, optional(decimal_digits))
|
|
)
|
|
|
|
const hex_digits = /[a-fA-F0-9]+/
|
|
const hex_exponent_part = seq(choice('p', 'P'), signed_integer)
|
|
|
|
const decimal_literal = choice(
|
|
seq(decimal_integer_literal, '.', optional(decimal_digits), optional(decimal_exponent_part)),
|
|
seq('.', decimal_digits, optional(decimal_exponent_part)),
|
|
seq(decimal_integer_literal, optional(decimal_exponent_part))
|
|
)
|
|
|
|
const hex_literal = seq(
|
|
choice('0x', '0X'),
|
|
hex_digits,
|
|
optional(seq('.', hex_digits)),
|
|
optional(hex_exponent_part)
|
|
)
|
|
|
|
return token(choice(
|
|
decimal_literal,
|
|
hex_literal
|
|
))
|
|
},
|
|
|
|
nil: $ => 'nil',
|
|
true: $ => 'true',
|
|
false: $ => 'false',
|
|
|
|
// Identifier
|
|
identifier: $ => /[a-zA-Z_][a-zA-Z0-9_]*/
|
|
}
|
|
})
|
|
|
|
function commaSeq(rule) {
|
|
return seq(rule, repeat(seq(',', rule)))
|
|
}
|