339 lines
8.7 KiB
JavaScript
339 lines
8.7 KiB
JavaScript
module.exports = grammar({
|
|
name: 'stonescript',
|
|
|
|
rules: {
|
|
source_file: $ => repeat($._statement),
|
|
|
|
_statement: $ => choice(
|
|
// Comments first
|
|
$.comment,
|
|
$.block_comment,
|
|
// Keyword-based statements (must come before generic command)
|
|
$.variable_declaration, // 'var'
|
|
$.function_declaration, // 'func'
|
|
$.for_loop, // 'for'
|
|
$.return_statement, // 'return'
|
|
$.break_statement, // 'break'
|
|
$.continue_statement, // 'continue'
|
|
$.import_statement, // 'import'
|
|
$.new_expression, // 'new'
|
|
// Control flow
|
|
$.conditional, // '?'
|
|
$.else_if_clause, // ':?'
|
|
$.else_clause, // ':'
|
|
// Commands (after keywords!)
|
|
$.command_statement,
|
|
// Fallback
|
|
$.expression_statement
|
|
),
|
|
|
|
// Comments
|
|
comment: $ => token(seq('//', /.*/)),
|
|
|
|
block_comment: $ => token(seq(
|
|
'/*',
|
|
/[^*]*\*+(?:[^/*][^*]*\*+)*/,
|
|
'/'
|
|
)),
|
|
|
|
// Variable declaration
|
|
variable_declaration: $ => seq(
|
|
'var',
|
|
field('name', $.identifier),
|
|
optional(seq('=', field('value', $._expression)))
|
|
),
|
|
|
|
// Function declaration
|
|
function_declaration: $ => seq(
|
|
'func',
|
|
field('name', $.identifier),
|
|
'(',
|
|
optional($.parameter_list),
|
|
')',
|
|
optional($.function_body)
|
|
),
|
|
|
|
function_body: $ => seq(
|
|
$._indent,
|
|
repeat1($._statement),
|
|
$._dedent
|
|
),
|
|
|
|
parameter_list: $ => seq(
|
|
$.identifier,
|
|
repeat(seq(',', $.identifier))
|
|
),
|
|
|
|
// Loops
|
|
for_loop: $ => choice(
|
|
seq(
|
|
'for',
|
|
$.identifier,
|
|
'=',
|
|
$._expression,
|
|
'..',
|
|
$._expression,
|
|
optional($.block)
|
|
),
|
|
seq(
|
|
'for',
|
|
$.identifier,
|
|
':',
|
|
$._expression,
|
|
optional($.block)
|
|
)
|
|
),
|
|
|
|
// Import
|
|
import_statement: $ => seq(
|
|
'import',
|
|
$.module_path
|
|
),
|
|
|
|
new_expression: $ => seq(
|
|
'new',
|
|
$.module_path
|
|
),
|
|
|
|
module_path: $ => /[a-zA-Z_][a-zA-Z0-9_\/]*/,
|
|
|
|
// Control flow
|
|
return_statement: $ => prec.right(seq(
|
|
'return',
|
|
optional($._expression)
|
|
)),
|
|
|
|
break_statement: $ => 'break',
|
|
|
|
continue_statement: $ => 'continue',
|
|
|
|
// Conditionals
|
|
conditional: $ => seq(
|
|
'?',
|
|
$._expression,
|
|
optional($.block)
|
|
),
|
|
|
|
else_if_clause: $ => seq(
|
|
':?',
|
|
$._expression,
|
|
optional($.block)
|
|
),
|
|
|
|
else_clause: $ => seq(
|
|
':',
|
|
optional($.block)
|
|
),
|
|
|
|
block: $ => seq(
|
|
$._indent,
|
|
repeat1($._statement),
|
|
$._dedent
|
|
),
|
|
|
|
// Commands - specific patterns
|
|
command_statement: $ => choice(
|
|
$.equip_command,
|
|
$.activate_command,
|
|
$.loadout_command,
|
|
$.brew_command,
|
|
$.disable_enable_command,
|
|
$.play_command,
|
|
$.print_command
|
|
),
|
|
|
|
equip_command: $ => prec.left(seq(
|
|
choice('equip', 'equipL', 'equipR'),
|
|
repeat1($.item_criteria)
|
|
)),
|
|
|
|
item_criteria: $ => prec.left(choice(
|
|
$.identifier,
|
|
$.star_level,
|
|
$.enchantment_level
|
|
)),
|
|
|
|
star_level: $ => seq('*', $.number),
|
|
|
|
enchantment_level: $ => seq('+', $.number),
|
|
|
|
activate_command: $ => seq(
|
|
'activate',
|
|
choice(
|
|
$.identifier,
|
|
'P', 'L', 'R'
|
|
)
|
|
),
|
|
|
|
loadout_command: $ => seq(
|
|
'loadout',
|
|
$.number
|
|
),
|
|
|
|
brew_command: $ => seq(
|
|
'brew',
|
|
$.identifier,
|
|
repeat(seq('+', $.identifier))
|
|
),
|
|
|
|
disable_enable_command: $ => prec.left(seq(
|
|
choice('disable', 'enable'),
|
|
choice(
|
|
'abilities', 'hud', 'banner',
|
|
'loadout', 'npcDialog', 'pause', 'player'
|
|
)
|
|
)),
|
|
|
|
play_command: $ => prec.left(seq(
|
|
'play',
|
|
$.identifier,
|
|
optional($.number)
|
|
)),
|
|
|
|
print_command: $ => prec.right(seq(
|
|
choice('>', '>o', '>h', '>`', '>c', '>f'),
|
|
repeat(choice(
|
|
$.identifier,
|
|
$.string,
|
|
$.number,
|
|
$.color_code,
|
|
','
|
|
))
|
|
)),
|
|
|
|
color_code: $ => /#[a-zA-Z0-9]+/,
|
|
|
|
// Expressions
|
|
expression_statement: $ => $._expression,
|
|
|
|
_expression: $ => choice(
|
|
$.identifier,
|
|
$.number,
|
|
$.float,
|
|
$.string,
|
|
$.boolean,
|
|
$.null,
|
|
$.array,
|
|
$.member_expression,
|
|
$.call_expression,
|
|
$.index_expression,
|
|
$.unary_expression,
|
|
$.binary_expression,
|
|
$.update_expression,
|
|
$.assignment_expression,
|
|
$.parenthesized_expression,
|
|
$.new_expression
|
|
),
|
|
|
|
member_expression: $ => prec.left(15, seq(
|
|
field('object', $._expression),
|
|
'.',
|
|
field('property', $.identifier)
|
|
)),
|
|
|
|
call_expression: $ => prec.left(14, seq(
|
|
field('function', $._expression),
|
|
'(',
|
|
optional($.argument_list),
|
|
')'
|
|
)),
|
|
|
|
argument_list: $ => sep1($.comma_sep, $._expression),
|
|
|
|
comma_sep: $ => ',',
|
|
|
|
index_expression: $ => prec.left(13, seq(
|
|
$._expression,
|
|
'[',
|
|
$._expression,
|
|
']'
|
|
)),
|
|
|
|
unary_expression: $ => prec.right(12, seq(
|
|
choice('!', '-'),
|
|
$._expression
|
|
)),
|
|
|
|
// Binary operators with proper precedence
|
|
binary_expression: $ => choice(
|
|
prec.left(4, seq($._expression, '|', $._expression)),
|
|
prec.left(5, seq($._expression, '&', $._expression)),
|
|
prec.left(7, seq($._expression, '!', $._expression)),
|
|
prec.left(7, seq($._expression, '=', $._expression)),
|
|
prec.left(8, seq($._expression, '<', $._expression)),
|
|
prec.left(8, seq($._expression, '>', $._expression)),
|
|
prec.left(8, seq($._expression, '<=', $._expression)),
|
|
prec.left(8, seq($._expression, '>=', $._expression)),
|
|
prec.left(9, seq($._expression, '+', $._expression)),
|
|
prec.left(9, seq($._expression, '-', $._expression)),
|
|
prec.left(10, seq($._expression, '*', $._expression)),
|
|
prec.left(10, seq($._expression, '/', $._expression)),
|
|
prec.left(11, seq($._expression, '%', $._expression))
|
|
),
|
|
|
|
update_expression: $ => choice(
|
|
prec.left(12, seq($._expression, choice('++', '--'))),
|
|
prec.right(12, seq(choice('++', '--'), $._expression))
|
|
),
|
|
|
|
assignment_expression: $ => prec.right(2, seq(
|
|
$._expression,
|
|
choice('=', '+=', '-=', '*=', '/='),
|
|
$._expression
|
|
)),
|
|
|
|
parenthesized_expression: $ => seq(
|
|
'(',
|
|
$._expression,
|
|
')'
|
|
),
|
|
|
|
// Arrays
|
|
array: $ => seq(
|
|
'[',
|
|
optional(sep1($.comma_sep, $._expression)),
|
|
optional(','),
|
|
']'
|
|
),
|
|
|
|
// Primitives
|
|
identifier: $ => /[a-zA-Z_][a-zA-Z0-9_]*/,
|
|
|
|
number: $ => /\d+/,
|
|
|
|
float: $ => /\d+\.\d+/,
|
|
|
|
string: $ => seq('"', repeat(choice(/[^"\\]/, /\\./)), '"'),
|
|
|
|
boolean: $ => choice('true', 'false'),
|
|
|
|
null: $ => 'null'
|
|
},
|
|
|
|
extras: $ => [
|
|
/\s/
|
|
],
|
|
|
|
externals: $ => [
|
|
$._newline,
|
|
$._indent,
|
|
$._dedent
|
|
],
|
|
|
|
word: $ => $.identifier,
|
|
|
|
conflicts: $ => [
|
|
[$.identifier, $.string],
|
|
[$._expression],
|
|
[$.command_statement],
|
|
[$._statement, $._expression], // new_expression can be both
|
|
[$.equip_command], // handle repeat ambiguity
|
|
[$.binary_expression, $.assignment_expression] // = operator ambiguity
|
|
]
|
|
});
|
|
|
|
// Helper to create comma-separated lists
|
|
function sep1(separator, rule) {
|
|
return seq(rule, repeat(seq(separator, rule)));
|
|
}
|