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) )), // 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, $.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, choice('[', '['), $._expression, choice(']', ']') )), 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( 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: $ => choice( seq('ascii', $.ascii_content, 'asciiend'), seq(choice('[', '['), 'ascii', $.ascii_content, 'asciiend', choice(']', ']')) ) }, extras: $ => [ /[ \t\r\f]/, /\r?\n[ \t]*\^/, $.comment, $.block_comment ], externals: $ => [ $._newline, $._indent, $._dedent, $.ascii_content ], 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))); }