How can I achieve implied multiplication for expressions in TI-BASIC

Hi there! I’m writing a plugin for TI-BASIC, where expressions can have implied multiplication, i.e. 2A is valid to write instead of 2*A. The rules are that implied multiplication is allowed after a trailing unary operator (for examle, factorial) or after an operand, like a number or variable. How can I achieve that in the expression parser from Grammar-Kit? My current expression setup looks like this, and can be found here:

expr ::= or_group
  | and_group
  | comparison_group
  | addition_group
  | multiplication_group
  | combinations_group
  | negation_group
  | exponential_group
  | modifiers_group
  | primary_group

// Private rules to define operators with the same priority
private or_group ::= or_expr | xor_expr
private and_group ::= and_expr
private comparison_group ::= eq_expr | ne_expr | gt_expr | ge_expr | lt_expr | le_expr
private addition_group ::= plus_expr | minus_expr
private multiplication_group ::= mul_expr | div_expr | implied_mul_expr
private combinations_group ::= npr_expr | ncr_expr
private negation_group ::= negation_expr
private exponential_group ::= pow_expr | xroot_expr
private modifiers_group ::= radian_expr | degree_expr | inverse_expr | pow2_expr | transpose_expr | pow3_expr | factorial_expr
private primary_group ::= literal_expr | func_expr | func_optional_expr | paren_expr

// Public rules for each expression
// Precedence 9
or_expr ::= expr OR expr
xor_expr ::= expr XOR expr
// Precedence 8
and_expr ::= expr AND expr
// Precedence 7
eq_expr ::= expr EQ expr
ne_expr ::= expr NE expr
gt_expr ::= expr GT expr
ge_expr ::= expr GE expr
lt_expr ::= expr LT expr
le_expr ::= expr LE expr
// Precedence 6
plus_expr ::= expr PLUS expr
minus_expr ::= expr MINUS expr
// Precedence 5
mul_expr ::= expr TIMES expr
div_expr ::= expr DIVIDE expr
implied_mul_expr ::= implied_mul_arg implied_mul_arg+
private implied_mul_arg ::= NUMBER | MATH_VARIABLE | EXPR_FUNCTIONS_NO_ARGS | ANS_VARIABLE | LIST_VARIABLE | custom_list_with_l | EQUATION_VARIABLE | SIMPLE_VARIABLE | WINDOW_VARIABLE | COLOR_VARIABLE | paren_expr | func_optional_expr | func_expr
// Precedence 4
npr_expr ::= expr NPR expr
ncr_expr ::= expr NCR expr
// Precedence 3.5
negation_expr ::= NEG expr
// Precedence 3
pow_expr ::= expr POW expr { rightAssociative=true }
xroot_expr ::= expr XROOT expr
// Precedence 2
radian_expr ::= expr TO_RADIAN
degree_expr ::= expr TO_DEGREE
inverse_expr ::= expr INVERSE
pow2_expr ::= expr POW2
transpose_expr ::= expr TRANSPOSE
pow3_expr ::= expr POW3
factorial_expr ::= expr FACTORIAL
// Precedence 1
paren_expr ::= LPAREN expr optional_rparen
func_expr ::= (EXPR_FUNCTIONS_WITH_ARGS | DIM) LPAREN <<list expr>> optional_rparen { pin=1 }
func_optional_expr ::= EXPR_FUNCTIONS_OPTIONAL_ARGS [LPAREN <<list expr>> optional_rparen] { pin=1 }

// Base literal
literal_expr ::= list_index | matrix_index | EXPR_FUNCTIONS_NO_ARGS | ANS_VARIABLE | LIST_VARIABLE | custom_list_with_l | EQUATION_VARIABLE | STRING_VARIABLE | SIMPLE_VARIABLE | WINDOW_VARIABLE | MATRIX_VARIABLE | COLOR_VARIABLE | MATH_VARIABLE | NUMBER | STRING | anonymous_list | anonymous_matrix
list_index ::= (LIST_VARIABLE | ANS_VARIABLE | custom_list_with_l) LPAREN expr optional_rparen { pin=2 }
matrix_index ::= (MATRIX_VARIABLE | ANS_VARIABLE) LPAREN expr COMMA expr optional_rparen { pin=2 }
anonymous_list ::= LCURLY <<list expr>> [RCURLY] { pin=1 }
anonymous_matrix ::= LBRACKET <<list anonymous_matrix_row>> [RBRACKET] { pin=1 }
anonymous_matrix_row ::= LBRACKET <<list expr>> [RBRACKET] { pin=1 }

The problem is within the implied_mul_expr rule, which now only allows hardcoded operands. But constructs like A!5 (which is considered valid syntax for TI-BASIC) is not matched with this rule. I tried to experiment with things like implied_mul_expr ::= (modifiers_group | primary_group) (modifiers_group | primary_group) et alia, but that freezes the IDE and so far I couldn’t get it working sadly. Is it possible to achieve implied multiplication with the correct rules?

Besides, now it treats the implied_mul_expr as an atom, so in the tree of operators it gets matched before other atoms:

  // Expression root: expr
  // Operator priority table:
  // 0: BINARY(or_expr) BINARY(xor_expr)
  // 1: BINARY(and_expr)
  // 2: BINARY(eq_expr) BINARY(ne_expr) BINARY(gt_expr) BINARY(ge_expr)
  //    BINARY(lt_expr) BINARY(le_expr)
  // 3: BINARY(plus_expr) BINARY(minus_expr)
  // 4: BINARY(mul_expr) BINARY(div_expr) ATOM(implied_mul_expr) <<----- SEE THIS
  // 5: BINARY(npr_expr) BINARY(ncr_expr)
  // 6: PREFIX(negation_expr)
  // 7: BINARY(pow_expr) BINARY(xroot_expr)
  // 8: POSTFIX(radian_expr) POSTFIX(degree_expr) POSTFIX(inverse_expr) POSTFIX(pow2_expr)
  //    POSTFIX(transpose_expr) POSTFIX(pow3_expr) POSTFIX(factorial_expr)
  // 9: ATOM(literal_expr) ATOM(func_expr) ATOM(func_optional_expr) PREFIX(paren_expr)

Which means that implied multiplication matches before, say, a list index, like L1(3 becomes implied multiplication rather than a list index.

Any help is greatly appreciated :slight_smile:

After experimenting a bit more, and with the help of Junie, it seems that implied_mul_expr ::= expr (modifiers_group | primary_group) fixed both issues. It is left associative, and only applies when the second operand is one from the modifiers group or the primary group. That means that A!2! is correctly an implied multiplication of A! and 2!. I’m sure to write a bunch of tests to verify it’s actually correct in all cases (should have done that earlier…).