315 lines
8.1 KiB
JavaScript
315 lines
8.1 KiB
JavaScript
module.exports = grammar({
|
||
name: 'stonescript',
|
||
|
||
rules: {
|
||
source_file: $ => repeat($._statement),
|
||
|
||
_statement: $ => prec.right(seq(
|
||
choice(
|
||
$._newline,
|
||
$.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'
|
||
// Control flow
|
||
$.conditional, // '?'
|
||
$.else_clause, // ':'
|
||
// Commands (higher precedence!)
|
||
prec.dynamic(1, $.command),
|
||
$.print_command,
|
||
// Fallback
|
||
$.expression_statement
|
||
),
|
||
optional($._newline)
|
||
)),
|
||
|
||
// Comments
|
||
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),
|
||
')',
|
||
$.block
|
||
),
|
||
|
||
parameter_list: $ => seq(
|
||
$.identifier,
|
||
repeat(seq(',', $.identifier))
|
||
),
|
||
|
||
// Loops
|
||
for_loop: $ => choice(
|
||
seq(
|
||
'for',
|
||
$.identifier,
|
||
'=',
|
||
$._expression,
|
||
'..',
|
||
$._expression,
|
||
$.block
|
||
),
|
||
seq(
|
||
'for',
|
||
$.identifier,
|
||
':',
|
||
$._expression,
|
||
$.block
|
||
)
|
||
),
|
||
|
||
// Import
|
||
import_expression: $ => seq(
|
||
'import',
|
||
$.module_path
|
||
),
|
||
|
||
new_statement: $ => 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,
|
||
$.block
|
||
),
|
||
|
||
else_clause: $ => choice(
|
||
seq(':?', $._expression, $.block),
|
||
seq(':', $.block)
|
||
),
|
||
|
||
block: $ => seq(
|
||
$._indent,
|
||
repeat1($._statement),
|
||
$._dedent
|
||
),
|
||
|
||
// Commands - Generic structure to match tests
|
||
// Must have at least one argument to distinguish from simple identifier expressions
|
||
command: $ => prec.dynamic(1, prec.right(seq(
|
||
$.identifier,
|
||
repeat1($._command_arg)
|
||
))),
|
||
|
||
_command_arg: $ => choice(
|
||
$.identifier,
|
||
$.number,
|
||
$.string,
|
||
$.star_level,
|
||
$.enchantment_level
|
||
),
|
||
|
||
star_level: $ => seq('*', $.number),
|
||
|
||
enchantment_level: $ => seq('+', $.number),
|
||
|
||
print_command: $ => prec.right(seq(
|
||
choice('>', '>o', '>h', '>`', '>c', '>f'),
|
||
optional($.print_args)
|
||
)),
|
||
|
||
// Print specific helpers
|
||
print_args: $ => sep1(',', $.print_argument),
|
||
|
||
print_argument: $ => prec.left(repeat1(choice(
|
||
$.interpolation,
|
||
$.string,
|
||
$.ascii_string,
|
||
$.color_code,
|
||
$.print_text
|
||
))),
|
||
|
||
print_text: $ => /[^,@\r\n"]+/,
|
||
|
||
interpolation: $ => seq(
|
||
'@',
|
||
$._expression,
|
||
'@'
|
||
),
|
||
|
||
color_code: $ => /#[a-zA-Z0-9]+/,
|
||
|
||
// Expressions
|
||
expression_statement: $ => $._expression,
|
||
|
||
_expression: $ => choice(
|
||
$.identifier,
|
||
$.number,
|
||
$.float,
|
||
$.string,
|
||
$.boolean,
|
||
$.null,
|
||
$.ascii_string,
|
||
$.array,
|
||
$.member_expression,
|
||
$.call_expression,
|
||
$.index_expression,
|
||
$.unary_expression,
|
||
$.binary_expression,
|
||
$.update_expression,
|
||
$.assignment_expression,
|
||
$.parenthesized_expression,
|
||
$.new_statement,
|
||
$.import_expression,
|
||
$.color_code
|
||
),
|
||
|
||
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,
|
||
choice('[', '['),
|
||
$._expression,
|
||
choice(']', ']')
|
||
)),
|
||
|
||
unary_expression: $ => prec.right(12, seq(
|
||
choice('!', '-'),
|
||
$._expression
|
||
)),
|
||
|
||
// Binary operators with proper precedence
|
||
binary_expression: $ => choice(
|
||
prec.left(6, seq($._expression, choice('*', '/', '%'), $._expression)),
|
||
prec.left(5, seq($._expression, choice('+', '-'), $._expression)),
|
||
prec.left(4, seq($._expression, choice('=', '!=', '!', '<', '>', '<=', '>='), $._expression)),
|
||
prec.left(3, seq($._expression, '&', $._expression)),
|
||
prec.left(2, 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(
|
||
choice('[', '['),
|
||
repeat($._newline),
|
||
optional(seq(
|
||
$.array_elements,
|
||
repeat($._newline)
|
||
)),
|
||
choice(']', ']')
|
||
),
|
||
|
||
array_elements: $ => seq(
|
||
$._expression,
|
||
repeat(seq(
|
||
',',
|
||
repeat($._newline),
|
||
$._expression
|
||
)),
|
||
optional(',')
|
||
),
|
||
|
||
// Primitives
|
||
identifier: $ => /[a-zA-Z_][a-zA-Z0-9_]*/,
|
||
|
||
number: $ => /\d+/,
|
||
|
||
float: $ => /\d+\.\d+/,
|
||
|
||
string: $ => choice(
|
||
seq('"', repeat(choice(/[^"\\]/, /\\./)), '"'),
|
||
seq('"', repeat(choice(/[^"\\]/, /\\./)), '"')
|
||
),
|
||
|
||
boolean: $ => choice('true', 'false'),
|
||
|
||
null: $ => 'null',
|
||
|
||
ascii_string: $ => seq('ascii', $.ascii_content)
|
||
},
|
||
|
||
extras: $ => [
|
||
/[ \t\r\f]/,
|
||
/\r?\n[ \t]*\^/,
|
||
$.comment,
|
||
$.block_comment
|
||
],
|
||
|
||
externals: $ => [
|
||
$._newline,
|
||
$._indent,
|
||
$._dedent,
|
||
$.ascii_content,
|
||
$.block_comment
|
||
],
|
||
|
||
word: $ => $.identifier,
|
||
|
||
conflicts: $ => [
|
||
[$.identifier, $.string],
|
||
[$._expression],
|
||
[$.command],
|
||
[$._statement, $._expression], // new_statement can be both
|
||
[$.binary_expression, $.assignment_expression], // = operator ambiguity
|
||
[$.command, $._expression], // * operator ambiguity
|
||
[$.array_elements],
|
||
[$.ascii_string]
|
||
]
|
||
});
|
||
|
||
// Helper to create comma-separated lists
|
||
function sep1(separator, rule) {
|
||
return seq(rule, repeat(seq(separator, rule)));
|
||
}
|