mruby-compiler: add pattern matching (case/in) support

Implement Phase 1 of Ruby pattern matching:
- value patterns (literals, constants, nil/true/false)
- variable patterns (binds matched value)
- alternative patterns (pat1 | pat2)
- as patterns (pattern => var)

Pattern matching uses === operator for value comparison,
allowing type checking with class patterns (e.g., in Integer).

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-12-16 17:20:39 +09:00
parent 1ae4b7742e
commit dadfac678d
4 changed files with 5314 additions and 4643 deletions
+155
View File
@@ -4248,6 +4248,157 @@ codegen_case(codegen_scope *s, node *varnode, int val)
}
}
/* Forward declaration for pattern matching code generation */
static void codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos);
/* Pattern matching case/in expression */
static void
codegen_case_match(codegen_scope *s, node *varnode, int val)
{
struct mrb_ast_case_match_node *case_match_n = case_match_node(varnode);
node *value = case_match_n->value;
node *in_clauses = case_match_n->in_clauses;
int head = cursp();
uint32_t case_end_jumps = JMPLINK_START;
uint32_t tmp;
/* Generate code for the case value */
codegen(s, value, VAL);
/* Iterate through in clauses */
node *current_in = in_clauses;
while (current_in) {
struct mrb_ast_in_node *in_n = in_node(current_in->car);
node *pattern = in_n->pattern;
node *body = in_n->body;
uint32_t fail_pos = JMPLINK_START;
if (pattern) {
/* Generate pattern matching code */
codegen_pattern(s, pattern, head, &fail_pos);
}
/* Generate in-clause body */
codegen(s, body, val);
if (val) pop();
/* Jump to end of case/in */
tmp = genjmp(s, OP_JMP, case_end_jumps);
case_end_jumps = tmp;
/* Dispatch fail jumps to next in-clause */
if (fail_pos != JMPLINK_START) {
dispatch_linked(s, fail_pos);
}
current_in = current_in->cdr;
}
/* No pattern matched - generate nil or error */
if (val) {
genop_1(s, OP_LOADNIL, cursp());
}
/* Dispatch all end jumps */
if (case_end_jumps != JMPLINK_START) {
dispatch_linked(s, case_end_jumps);
}
if (val) {
/* Move result to original case value position */
gen_move(s, head, cursp(), 0);
pop();
push();
}
else {
pop(); /* pop the case value */
}
}
/* Generate pattern matching code for a single pattern.
* target: stack position of the value being matched
* fail_pos: linked list of jump positions for pattern match failure
*/
static void
codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos)
{
uint32_t tmp;
switch (get_node_type(pattern)) {
case NODE_PAT_VALUE:
{
struct mrb_ast_pat_value_node *pat_val = pat_value_node(pattern);
/* Generate: pattern_value === target */
codegen(s, pat_val->value, VAL);
gen_move(s, cursp(), target, 0);
push(); push(); pop(); pop(); pop();
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_OPSYM_2(s->mrb, eqq)), 1);
/* Jump to fail if not matched */
tmp = genjmp2(s, OP_JMPNOT, cursp(), *fail_pos, 1);
*fail_pos = tmp;
}
break;
case NODE_PAT_VAR:
{
struct mrb_ast_pat_var_node *pat_var = pat_var_node(pattern);
if (pat_var->name) {
/* Bind the matched value to the variable */
int idx = lv_idx(s, pat_var->name);
if (idx > 0) {
gen_move(s, idx, target, 0);
}
}
/* Variable pattern always matches (wildcard if name is 0) */
}
break;
case NODE_PAT_ALT:
{
struct mrb_ast_pat_alt_node *pat_alt = pat_alt_node(pattern);
uint32_t left_fail = JMPLINK_START;
uint32_t success_pos = JMPLINK_START;
/* Try left pattern */
codegen_pattern(s, pat_alt->left, target, &left_fail);
/* Left succeeded - jump to success */
tmp = genjmp(s, OP_JMP, success_pos);
success_pos = tmp;
/* Left failed - try right pattern */
if (left_fail != JMPLINK_START) {
dispatch_linked(s, left_fail);
}
codegen_pattern(s, pat_alt->right, target, fail_pos);
/* Dispatch success jumps */
if (success_pos != JMPLINK_START) {
dispatch_linked(s, success_pos);
}
}
break;
case NODE_PAT_AS:
{
struct mrb_ast_pat_as_node *pat_as = pat_as_node(pattern);
/* First match the pattern */
codegen_pattern(s, pat_as->pattern, target, fail_pos);
/* Then bind the value to the variable */
int idx = lv_idx(s, pat_as->name);
if (idx > 0) {
gen_move(s, idx, target, 0);
}
}
break;
default:
raise_error(s, "unsupported pattern type");
break;
}
}
/* Definition node codegen functions */
static void
@@ -5725,6 +5876,10 @@ codegen(codegen_scope *s, node *tree, int val)
codegen_case(s, tree, val);
break;
case NODE_CASE_MATCH:
codegen_case_match(s, tree, val);
break;
case NODE_DEF:
codegen_def(s, tree, val);
break;
+82
View File
@@ -81,6 +81,16 @@ enum node_type {
NODE_HEREDOC,
NODE_WORDS,
NODE_SYMBOLS,
/* Pattern matching nodes */
NODE_CASE_MATCH, /* case/in pattern matching expression */
NODE_IN, /* in-clause node */
NODE_PAT_VALUE, /* value pattern (literal, constant) */
NODE_PAT_VAR, /* variable pattern */
NODE_PAT_PIN, /* pin operator ^var */
NODE_PAT_AS, /* as pattern (pattern => var) */
NODE_PAT_ALT, /* alternative pattern (pat1 | pat2) */
NODE_PAT_ARRAY, /* array pattern [a, b, *rest] */
NODE_PAT_HASH, /* hash pattern {a:, b: x} */
NODE_LAST
};
@@ -267,6 +277,69 @@ struct mrb_ast_case_node {
struct mrb_ast_node *body; /* When/else clauses (cons list) */
};
/* Pattern matching case node (case/in) */
struct mrb_ast_case_match_node {
struct mrb_ast_var_header header; /* 8 bytes */
struct mrb_ast_node *value; /* Case value expression */
struct mrb_ast_node *in_clauses; /* In clause list (cons list) */
};
/* In clause node */
struct mrb_ast_in_node {
struct mrb_ast_var_header header; /* 8 bytes */
struct mrb_ast_node *pattern; /* Pattern to match */
struct mrb_ast_node *guard; /* Guard expression (optional) */
struct mrb_ast_node *body; /* Body to execute on match */
mrb_bool guard_is_unless; /* TRUE if 'unless', FALSE if 'if' */
};
/* Value pattern node (literal, constant) */
struct mrb_ast_pat_value_node {
struct mrb_ast_var_header header; /* 8 bytes */
struct mrb_ast_node *value; /* Literal or constant node */
};
/* Variable pattern node */
struct mrb_ast_pat_var_node {
struct mrb_ast_var_header header; /* 8 bytes */
mrb_sym name; /* Variable name (0 for wildcard _) */
};
/* Pin pattern node (^var) */
struct mrb_ast_pat_pin_node {
struct mrb_ast_var_header header; /* 8 bytes */
mrb_sym name; /* Variable name to pin */
};
/* As pattern node (pattern => var) */
struct mrb_ast_pat_as_node {
struct mrb_ast_var_header header; /* 8 bytes */
struct mrb_ast_node *pattern; /* Pattern to match */
mrb_sym name; /* Variable to bind */
};
/* Alternative pattern node (pat1 | pat2) */
struct mrb_ast_pat_alt_node {
struct mrb_ast_var_header header; /* 8 bytes */
struct mrb_ast_node *left; /* Left pattern */
struct mrb_ast_node *right; /* Right pattern */
};
/* Array pattern node [a, b, *rest] */
struct mrb_ast_pat_array_node {
struct mrb_ast_var_header header; /* 8 bytes */
struct mrb_ast_node *pre; /* Pre-rest patterns (cons list) */
struct mrb_ast_node *rest; /* Rest pattern (NULL if none, -1 if anonymous) */
struct mrb_ast_node *post; /* Post-rest patterns (cons list) */
};
/* Hash pattern node {a:, b: x} */
struct mrb_ast_pat_hash_node {
struct mrb_ast_var_header header; /* 8 bytes */
struct mrb_ast_node *pairs; /* Key-pattern pairs (cons list) */
struct mrb_ast_node *rest; /* Rest pattern (NULL if none, -1 if **nil) */
};
/* Variable-sized for node */
struct mrb_ast_for_node {
struct mrb_ast_var_header header; /* 8 bytes */
@@ -369,6 +442,15 @@ struct mrb_ast_super_node {
#define while_node(n) ((struct mrb_ast_while_node*)(n))
#define until_node(n) ((struct mrb_ast_until_node*)(n))
#define case_node(n) ((struct mrb_ast_case_node*)(n))
#define case_match_node(n) ((struct mrb_ast_case_match_node*)(n))
#define in_node(n) ((struct mrb_ast_in_node*)(n))
#define pat_value_node(n) ((struct mrb_ast_pat_value_node*)(n))
#define pat_var_node(n) ((struct mrb_ast_pat_var_node*)(n))
#define pat_pin_node(n) ((struct mrb_ast_pat_pin_node*)(n))
#define pat_as_node(n) ((struct mrb_ast_pat_as_node*)(n))
#define pat_alt_node(n) ((struct mrb_ast_pat_alt_node*)(n))
#define pat_array_node(n) ((struct mrb_ast_pat_array_node*)(n))
#define pat_hash_node(n) ((struct mrb_ast_pat_hash_node*)(n))
#define for_node(n) ((struct mrb_ast_for_node*)(n))
#define asgn_node(n) ((struct mrb_ast_asgn_node*)(n))
#define masgn_node(n) ((struct mrb_ast_masgn_node*)(n))
+155
View File
@@ -597,6 +597,74 @@ new_case(parser_state *p, node *a, node *b)
return (node*)n;
}
/* Pattern matching case/in expression */
static node*
new_case_match(parser_state *p, node *val, node *in_clauses)
{
void_expr_error(p, val);
struct mrb_ast_case_match_node *n = NEW_NODE(case_match, NODE_CASE_MATCH);
n->value = val;
n->in_clauses = in_clauses;
return (node*)n;
}
/* Create value pattern node */
static node*
new_pat_value(parser_state *p, node *val)
{
struct mrb_ast_pat_value_node *n = NEW_NODE(pat_value, NODE_PAT_VALUE);
n->value = val;
return (node*)n;
}
/* Create variable pattern node */
static node*
new_pat_var(parser_state *p, mrb_sym name)
{
struct mrb_ast_pat_var_node *n = NEW_NODE(pat_var, NODE_PAT_VAR);
n->name = name;
/* Register as local variable if not wildcard */
if (name) {
local_add(p, name);
}
return (node*)n;
}
/* Create as pattern node (pattern => var) */
static node*
new_pat_as(parser_state *p, node *pattern, mrb_sym name)
{
struct mrb_ast_pat_as_node *n = NEW_NODE(pat_as, NODE_PAT_AS);
n->pattern = pattern;
n->name = name;
local_add(p, name);
return (node*)n;
}
/* Create alternative pattern node (pat1 | pat2) */
static node*
new_pat_alt(parser_state *p, node *left, node *right)
{
struct mrb_ast_pat_alt_node *n = NEW_NODE(pat_alt, NODE_PAT_ALT);
n->left = left;
n->right = right;
return (node*)n;
}
/* Create in-clause node for case/in */
static node*
new_in(parser_state *p, node *pattern, node *guard, node *body, mrb_bool guard_is_unless)
{
struct mrb_ast_in_node *n = NEW_NODE(in, NODE_IN);
n->pattern = pattern;
n->guard = guard;
n->body = body;
n->guard_is_unless = guard_is_unless;
return (node*)n;
}
/* struct: postexe_node(body) */
static node*
new_postexe(parser_state *p, node *a)
@@ -1984,6 +2052,9 @@ prohibit_literals(parser_state *p, node *n)
%type <nd> f_block_kwarg f_block_kw block_args_tail opt_block_args_tail
%type <id> f_label f_kwrest
/* pattern matching */
%type <nd> in_clauses p_expr p_alt p_value p_var p_as
%token tUPLUS "unary plus"
%token tUMINUS "unary minus"
%token tCMP "<=>"
@@ -3247,6 +3318,15 @@ primary : literal
{
$$ = new_case(p, 0, $3);
}
| keyword_case expr_value opt_terms
keyword_in p_expr then
compstmt
in_clauses
keyword_end
{
node *in_clause = new_in(p, $5, NULL, $7, FALSE);
$$ = new_case_match(p, $2, cons(in_clause, $8));
}
| keyword_for for_var keyword_in
{COND_PUSH(1);}
expr_value do
@@ -3718,6 +3798,81 @@ cases : opt_else
| case_body
;
/* Pattern matching in-clauses for case/in */
in_clauses : opt_else
{
$$ = $1 ? list1(new_in(p, NULL, NULL, $1, FALSE)) : 0;
}
| keyword_in p_expr then compstmt in_clauses
{
node *in_clause = new_in(p, $2, NULL, $4, FALSE);
$$ = cons(in_clause, $5);
}
;
/* Pattern expressions for case/in */
p_expr : p_as
;
p_as : p_alt
| p_alt tASSOC tIDENTIFIER
{
$$ = new_pat_as(p, $1, $3);
}
;
p_alt : p_value
| p_alt '|' p_value
{
$$ = new_pat_alt(p, $1, $3);
}
;
p_value : p_var
| numeric
{
$$ = new_pat_value(p, $1);
}
| symbol
{
$$ = new_pat_value(p, $1);
}
| tSTRING
{
$$ = new_pat_value(p, $1);
}
| keyword_nil
{
$$ = new_pat_value(p, new_nil(p));
}
| keyword_true
{
$$ = new_pat_value(p, new_true(p));
}
| keyword_false
{
$$ = new_pat_value(p, new_false(p));
}
| tCONSTANT
{
$$ = new_pat_value(p, new_const(p, $1));
}
| primary_value tCOLON2 tCONSTANT
{
$$ = new_pat_value(p, new_colon2(p, $1, $3));
}
| tCOLON3 tCONSTANT
{
$$ = new_pat_value(p, new_colon3(p, $2));
}
;
p_var : tIDENTIFIER
{
$$ = new_pat_var(p, $1);
}
;
opt_rescue : keyword_rescue exc_list exc_var then
compstmt
opt_rescue
File diff suppressed because it is too large Load Diff