mruby-compiler: add guard clauses to pattern matching

Add support for if/unless guards in case/in pattern matching:

  case value
  in x if x > 0 then :positive
  in x unless x == 0 then :non_zero
  end

Uses modifier_if/modifier_unless tokens since guards appear after
an expression. Disable peephole optimization for pattern variable
binding to prevent gen_move() from being optimized away when failed
guard jumps target the binding instruction.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-12-16 23:20:28 +09:00
parent dadfac678d
commit 07ac110ddd
3 changed files with 3218 additions and 3110 deletions
+18 -1
View File
@@ -4271,6 +4271,8 @@ codegen_case_match(codegen_scope *s, node *varnode, int val)
while (current_in) {
struct mrb_ast_in_node *in_n = in_node(current_in->car);
node *pattern = in_n->pattern;
node *guard = in_n->guard;
mrb_bool guard_is_unless = in_n->guard_is_unless;
node *body = in_n->body;
uint32_t fail_pos = JMPLINK_START;
@@ -4280,6 +4282,21 @@ codegen_case_match(codegen_scope *s, node *varnode, int val)
codegen_pattern(s, pattern, head, &fail_pos);
}
/* Generate guard clause if present */
if (guard) {
codegen(s, guard, VAL);
pop(); /* pop before jump - cursp() now points to guard result */
if (guard_is_unless) {
/* unless guard: fail if guard is true */
tmp = genjmp2(s, OP_JMPIF, cursp(), fail_pos, 0);
}
else {
/* if guard: fail if guard is false */
tmp = genjmp2(s, OP_JMPNOT, cursp(), fail_pos, 0);
}
fail_pos = tmp;
}
/* Generate in-clause body */
codegen(s, body, val);
if (val) pop();
@@ -4348,7 +4365,7 @@ codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos)
/* Bind the matched value to the variable */
int idx = lv_idx(s, pat_var->name);
if (idx > 0) {
gen_move(s, idx, target, 0);
gen_move(s, idx, target, 1); /* nopeep=1 to prevent optimization */
}
}
/* Variable pattern always matches (wildcard if name is 0) */
+28
View File
@@ -3327,6 +3327,24 @@ primary : literal
node *in_clause = new_in(p, $5, NULL, $7, FALSE);
$$ = new_case_match(p, $2, cons(in_clause, $8));
}
| keyword_case expr_value opt_terms
keyword_in p_expr modifier_if expr_value then
compstmt
in_clauses
keyword_end
{
node *in_clause = new_in(p, $5, $7, $9, FALSE);
$$ = new_case_match(p, $2, cons(in_clause, $10));
}
| keyword_case expr_value opt_terms
keyword_in p_expr modifier_unless expr_value then
compstmt
in_clauses
keyword_end
{
node *in_clause = new_in(p, $5, $7, $9, TRUE);
$$ = new_case_match(p, $2, cons(in_clause, $10));
}
| keyword_for for_var keyword_in
{COND_PUSH(1);}
expr_value do
@@ -3808,6 +3826,16 @@ in_clauses : opt_else
node *in_clause = new_in(p, $2, NULL, $4, FALSE);
$$ = cons(in_clause, $5);
}
| keyword_in p_expr modifier_if expr_value then compstmt in_clauses
{
node *in_clause = new_in(p, $2, $4, $6, FALSE);
$$ = cons(in_clause, $7);
}
| keyword_in p_expr modifier_unless expr_value then compstmt in_clauses
{
node *in_clause = new_in(p, $2, $4, $6, TRUE);
$$ = cons(in_clause, $7);
}
;
/* Pattern expressions for case/in */
File diff suppressed because it is too large Load Diff