mruby-compiler: add one-line pattern matching

add support for one-line pattern matching syntax:
- 'expr in pattern' returns true/false
- 'expr => pattern' raises NoMatchingPatternError on mismatch

add NODE_MATCH_PAT node type for both forms, distinguished by
raise_on_fail flag. grammar rules placed at expr level to avoid
conflict with rescue clause's exception variable syntax.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-12-17 16:37:12 +09:00
parent e8096bf745
commit e76ce24860
5 changed files with 5744 additions and 5520 deletions
+55
View File
@@ -6118,6 +6118,61 @@ codegen(codegen_scope *s, node *tree, int val)
codegen_case_match(s, tree, val);
break;
case NODE_MATCH_PAT:
{
/* One-line pattern matching: expr in pattern / expr => pattern */
struct mrb_ast_match_pat_node *mp = match_pat_node(tree);
int head = cursp();
uint32_t fail_pos = JMPLINK_START;
uint32_t match_pos;
/* Evaluate the value */
codegen(s, mp->value, VAL);
/* Generate pattern matching code */
codegen_pattern(s, mp->pattern, head, &fail_pos);
/* Pattern matched - load true and jump to end */
pop(); /* pop the value */
if (val) {
genop_1(s, OP_LOADT, cursp());
push();
}
match_pos = genjmp(s, OP_JMP, JMPLINK_START);
/* Pattern failed */
dispatch_linked(s, fail_pos);
pop(); /* pop the value */
if (mp->raise_on_fail) {
/* expr => pattern: raise NoMatchingPatternError */
int msg_off = new_lit_cstr(s, "pattern not matched");
int exc_reg = cursp();
/* Get NoMatchingPatternError class */
genop_2(s, OP_GETCONST, exc_reg, new_sym(s, MRB_SYM_2(s->mrb, NoMatchingPatternError)));
push();
/* Create message string */
genop_2(s, OP_STRING, cursp(), msg_off);
push();
/* Call NoMatchingPatternError.new(message) */
pop(); /* pop argument */
genop_3(s, OP_SEND, exc_reg, new_sym(s, MRB_SYM_2(s->mrb, new)), 1);
/* Raise the exception */
genop_1(s, OP_RAISEIF, exc_reg);
if (val) push();
}
else {
/* expr in pattern: return false */
if (val) {
genop_1(s, OP_LOADF, cursp());
push();
}
}
/* End of pattern matching */
dispatch(s, match_pos);
}
break;
case NODE_DEF:
codegen_def(s, tree, val);
break;
+10
View File
@@ -91,6 +91,7 @@ enum node_type {
NODE_PAT_ALT, /* alternative pattern (pat1 | pat2) */
NODE_PAT_ARRAY, /* array pattern [a, b, *rest] */
NODE_PAT_HASH, /* hash pattern {a:, b: x} */
NODE_MATCH_PAT, /* one-line pattern matching (expr in pat / expr => pat) */
NODE_LAST
};
@@ -340,6 +341,14 @@ struct mrb_ast_pat_hash_node {
struct mrb_ast_node *rest; /* Rest pattern (NULL if none, -1 if **nil) */
};
/* One-line pattern matching: expr in pattern / expr => pattern */
struct mrb_ast_match_pat_node {
struct mrb_ast_var_header header; /* 8 bytes */
struct mrb_ast_node *value; /* Expression to match */
struct mrb_ast_node *pattern; /* Pattern */
mrb_bool raise_on_fail; /* TRUE for =>, FALSE for in */
};
/* Variable-sized for node */
struct mrb_ast_for_node {
struct mrb_ast_var_header header; /* 8 bytes */
@@ -451,6 +460,7 @@ struct mrb_ast_super_node {
#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 match_pat_node(n) ((struct mrb_ast_match_pat_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))
+33 -6
View File
@@ -684,6 +684,17 @@ new_pat_hash(parser_state *p, node *pairs, node *rest)
return (node*)n;
}
/* Create one-line pattern matching node (expr in pattern / expr => pattern) */
static node*
new_match_pat(parser_state *p, node *value, node *pattern, mrb_bool raise_on_fail)
{
struct mrb_ast_match_pat_node *n = NEW_NODE(match_pat, NODE_MATCH_PAT);
n->value = value;
n->pattern = pattern;
n->raise_on_fail = raise_on_fail;
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)
@@ -2313,12 +2324,6 @@ stmt : keyword_alias fsym {p->lstate = EXPR_FNAME;} fsym
{
$$ = new_masgn(p, $1, new_array(p, $3));
}
| arg tASSOC tIDENTIFIER
{
node *lhs = new_lvar(p, $3);
assignable(p, lhs);
$$ = new_asgn(p, lhs, $1);
}
| expr
;
@@ -2419,6 +2424,18 @@ expr : command_call
{
$$ = call_uni_op(p, cond($2), "!");
}
| arg tASSOC {p->in_kwarg++;} p_expr
{
/* expr => pattern (raises NoMatchingPatternError on failure) */
p->in_kwarg--;
$$ = new_match_pat(p, $1, $4, TRUE);
}
| arg keyword_in {p->in_kwarg++;} p_expr
{
/* expr in pattern (returns true/false) */
p->in_kwarg--;
$$ = new_match_pat(p, $1, $4, FALSE);
}
| arg
;
@@ -8894,6 +8911,16 @@ dump_node(mrb_state *mrb, node *tree, int offset)
}
break;
case NODE_MATCH_PAT:
printf("NODE_MATCH_PAT%s:\n", match_pat_node(tree)->raise_on_fail ? " (=>)" : " (in)");
dump_prefix(offset+1, lineno);
printf("value:\n");
dump_node(mrb, match_pat_node(tree)->value, offset+2);
dump_prefix(offset+1, lineno);
printf("pattern:\n");
dump_node(mrb, match_pat_node(tree)->pattern, offset+2);
break;
default:
/* Fallback: unknown node type - skip like codegen.c does */
printf("unknown node type %d (0x%x)\n", nodetype, (unsigned)nodetype);
File diff suppressed because it is too large Load Diff
+1
View File
@@ -920,6 +920,7 @@ mrb_init_exception(mrb_state *mrb)
mrb_define_class_id(mrb, MRB_SYM(SyntaxError), script_error); /* 15.2.38 */
struct RClass *index_error = mrb_define_class_id(mrb, MRB_SYM(IndexError), E_STANDARD_ERROR); /* 15.2.33 */
mrb_define_class_id(mrb, MRB_SYM(KeyError), index_error);
mrb_define_class_id(mrb, MRB_SYM(NoMatchingPatternError), E_STANDARD_ERROR); /* pattern matching */
struct RClass *stack_error = mrb_define_class_id(mrb, MRB_SYM(SystemStackError), exception);
mrb->stack_err = mrb_obj_ptr(mrb_exc_new_lit(mrb, stack_error, "stack level too deep"));