feat: Major grammar improvements and refactoring

- 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
This commit is contained in:
2025-11-26 22:19:38 +01:00
parent b7942e9f79
commit 4d61f91e06
9 changed files with 15323 additions and 18695 deletions

View File

@@ -4,28 +4,30 @@ module.exports = grammar({
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
),
_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('//', /.*/)),
@@ -50,13 +52,7 @@ module.exports = grammar({
'(',
optional($.parameter_list),
')',
optional($.function_body)
),
function_body: $ => seq(
$._indent,
repeat1($._statement),
$._dedent
$.block
),
parameter_list: $ => seq(
@@ -73,14 +69,14 @@ module.exports = grammar({
$._expression,
'..',
$._expression,
optional($.block)
$.block
),
seq(
'for',
$.identifier,
':',
$._expression,
optional($.block)
$.block
)
),
@@ -90,12 +86,12 @@ module.exports = grammar({
$.module_path
),
new_expression: $ => seq(
new_statement: $ => seq(
'new',
$.module_path
),
module_path: $ => /[a-zA-Z_][a-zA-Z0-9_\/]*/,
module_path: $ => /[a-zA-Z_][a-zA-Z0-9_\\/]*/,
// Control flow
return_statement: $ => prec.right(seq(
@@ -111,18 +107,12 @@ module.exports = grammar({
conditional: $ => seq(
'?',
$._expression,
optional($.block)
$.block
),
else_if_clause: $ => seq(
':?',
$._expression,
optional($.block)
),
else_clause: $ => seq(
':',
optional($.block)
else_clause: $ => choice(
seq(':?', $._expression, $.block),
seq(':', $.block)
),
block: $ => seq(
@@ -131,73 +121,55 @@ module.exports = grammar({
$._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(
// 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),
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'),
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(
$.identifier,
$.string,
$.number,
$.color_code,
','
/[^@\r\n]+/,
$.interpolation
))
)),
@@ -222,7 +194,9 @@ module.exports = grammar({
$.update_expression,
$.assignment_expression,
$.parenthesized_expression,
$.new_expression
$.new_statement,
// $.ascii_string,
$.color_code
),
member_expression: $ => prec.left(15, seq(
@@ -291,11 +265,16 @@ module.exports = grammar({
// Arrays
array: $ => seq(
'[',
optional(sep1($.comma_sep, $._expression)),
optional(','),
optional($.array_elements),
']'
),
array_elements: $ => seq(
$._expression,
repeat(seq(',', $._expression)),
optional(',')
),
// Primitives
identifier: $ => /[a-zA-Z_][a-zA-Z0-9_]*/,
@@ -307,11 +286,19 @@ module.exports = grammar({
boolean: $ => choice('true', 'false'),
null: $ => 'null'
null: $ => 'null',
// ascii_string: $ => seq(
// 'ascii',
// $.ascii_content,
// 'asciiend'
// )
},
extras: $ => [
/\s/
/[ \t\r\f]/,
/[\r\n]\^/,
$.comment,
$.block_comment
],
externals: $ => [
@@ -325,10 +312,10 @@ module.exports = grammar({
conflicts: $ => [
[$.identifier, $.string],
[$._expression],
[$.command_statement],
[$._statement, $._expression], // new_expression can be both
[$.equip_command], // handle repeat ambiguity
[$.binary_expression, $.assignment_expression] // = operator ambiguity
[$.command],
[$._statement, $._expression], // new_statement can be both
[$.binary_expression, $.assignment_expression], // = operator ambiguity
[$.command, $._expression] // * operator ambiguity
]
});