One of my earlier posts included two or three (interconnected) fixes. I’ll submit them as a first PR to your GitHub repo. Do you also check the discussion on GitHub regularly? Where do you prefer?
For example, the operator fix:
const PREC = { CALL: 140, BIN: 20 };
function_call: $ => prec.left(PREC.CALL, seq(
field('receiver', $._primary),
repeat1($.method_call)
));
binary_expression: $ => prec.left(PREC.BIN, seq(
field('left', $._object),
field('operator', choice(
'||','&&','|','^','&','==','!=','<','<=','>','>=',
'<<','>>','+','-','++','+/+','*','/','%','**'
)),
field('right', $._object)
));
This fixes most operators, but it’s still not quite correct. One question is **: in SC it isn’t a special math operator, but a method selector defined on Object. That’s a semantic particular, not a parsing rule, so it shouldn’t get special precedence. The correct solution is to maintain all binary operators in a flat, left-to-right tier, while ensuring that message sends always bind “tighter” than binary expressions.
This also connects to the .midiratio issue. Currently, the grammar lets a postfix method chain attach to any expression—including a complete binary expression. So the parser builds: receiver = the whole \freq.kr(440) * (…) then applies .midiratio to that receiver.
To fix this, postfix chains must be part of the operand (a “term”), not something that can wrap a finished binary expression. That change affects the operator rule too:
const PREC = { CALL: 140, BIN: 20 };
_primary: $ => choice(
$.number, $.string, $.symbol, $.variable, $.class,
$.collection, $.code_block, $.group
),
group: $ => seq('(', $._expression, ')'),
_postfix: $ => choice(
prec.left(PREC.CALL, seq($._primary, repeat1($.method_call))),
$._primary
),
method_call: $ => seq(
'.',
field('name', alias($.identifier, $.method_name)),
optional(seq('(', optional($.parameter_call_list), ')'))
),
// keep identifier ':' only in named args / associative items
parameter_call_list: $ => sepBy1(',', choice($.named_argument, $._object)),
named_argument: $ => seq(
field('name', choice($.symbol, $.identifier)),
':',
field('value', $._object)
),
binary_expression: $ => prec.left(PREC.BIN, seq(
field('left', $._postfix),
field('operator', choice(
'||','&&','|','^','&','==','!=','<','<=','>','>=',
'<<','>>','+','-','++','+/+','*','/','%','**'
)),
field('right', $._postfix)
));
Alternatively, if you want to keep function_call as the chain node:
function_call: $ => prec.left(PREC.CALL, seq(
field('receiver', $._primary),
repeat1($.method_call)
)),
_postfix: $ => choice($.function_call, $._primary),
Let’s put this to the test and see how it goes. I just rewrote my earlier post, did not do any additional testing myself.
EDIT:
