- Refactor statement parsing with proper precedence handling - Improve block structure parsing with indent/dedent support - Enhance control flow parsing (conditionals, loops) - Add print command support - Improve function declaration parsing - Update scanner for better string and comment handling - Add comprehensive test corpus - Better handling of newlines and statement boundaries
326 lines
8.5 KiB
JavaScript
326 lines
8.5 KiB
JavaScript
module.exports = grammar({
|
|
name: 'stonescript',
|
|
|
|
rules: {
|
|
source_file: $ => repeat($._statement),
|
|
|
|
_statement: $ => prec.right(seq(
|
|
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'
|
|
// Control flow
|
|
$.conditional, // '?'
|
|
$.else_clause, // ':'
|
|
// Commands (higher precedence!)
|
|
prec.dynamic(1, $.command),
|
|
$.print_command,
|
|
// Fallback
|
|
$.expression_statement
|
|
),
|
|
optional($._newline)
|
|
)),
|
|
|
|
// 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),
|
|
')',
|
|
$.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_statement: $ => 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),
|
|
repeat($.print_continuation)
|
|
)),
|
|
|
|
// 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,
|
|
'@'
|
|
),
|
|
|
|
print_continuation: $ => prec.right(seq(
|
|
'^',
|
|
repeat(choice(
|
|
/[^@\r\n]+/,
|
|
$.interpolation
|
|
))
|
|
)),
|
|
|
|
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_statement,
|
|
// $.ascii_string,
|
|
$.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,
|
|
'[',
|
|
$._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($.array_elements),
|
|
']'
|
|
),
|
|
|
|
array_elements: $ => seq(
|
|
$._expression,
|
|
repeat(seq(',', $._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',
|
|
// ascii_string: $ => seq(
|
|
// 'ascii',
|
|
// $.ascii_content,
|
|
// 'asciiend'
|
|
// )
|
|
},
|
|
|
|
extras: $ => [
|
|
/[ \t\r\f]/,
|
|
/[\r\n]\^/,
|
|
$.comment,
|
|
$.block_comment
|
|
],
|
|
|
|
externals: $ => [
|
|
$._newline,
|
|
$._indent,
|
|
$._dedent
|
|
],
|
|
|
|
word: $ => $.identifier,
|
|
|
|
conflicts: $ => [
|
|
[$.identifier, $.string],
|
|
[$._expression],
|
|
[$.command],
|
|
[$._statement, $._expression], // new_statement can be both
|
|
[$.binary_expression, $.assignment_expression], // = operator ambiguity
|
|
[$.command, $._expression] // * operator ambiguity
|
|
]
|
|
});
|
|
|
|
// Helper to create comma-separated lists
|
|
function sep1(separator, rule) {
|
|
return seq(rule, repeat(seq(separator, rule)));
|
|
}
|