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))); }