mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
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:
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
+4922
-4643
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user