mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
mruby-compiler: implement find pattern matching
add support for find patterns in case/in expressions: - [*pre, elem, *post] - find elem anywhere in array - [*, elem, *] - anonymous rest (discarded) - [*pre, a, b, *post] - multiple middle elements implementation includes: - grammar rules for find patterns with p_args, p_rest in parse.y - NODE_PAT_FIND codegen with iterative search loop - pre/post variable binding via range slicing - p_const rule to prevent conflict with array literals Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -4538,6 +4538,163 @@ codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos)
|
||||
}
|
||||
break;
|
||||
|
||||
case NODE_PAT_FIND:
|
||||
{
|
||||
/* Find pattern: [*pre, elem1, elem2, ..., *post]
|
||||
* Searches for elems anywhere in the array.
|
||||
*
|
||||
* Stack layout:
|
||||
* arr_reg: deconstructed array (stable)
|
||||
* idx_reg: current search index (stable)
|
||||
*
|
||||
* Loop bound is recomputed each iteration since OP_SEND clobbers registers.
|
||||
*/
|
||||
struct mrb_ast_pat_find_node *pat_find = pat_find_node(pattern);
|
||||
int elems_len = 0;
|
||||
node *elem;
|
||||
int arr_reg = cursp();
|
||||
int idx_reg;
|
||||
uint32_t loop_start, match_fail, loop_end;
|
||||
|
||||
/* Count middle elements */
|
||||
for (elem = pat_find->elems; elem; elem = elem->cdr) elems_len++;
|
||||
|
||||
/* Call deconstruct on target */
|
||||
gen_move(s, cursp(), target, 0);
|
||||
push();
|
||||
genop_3(s, OP_SEND, arr_reg, new_sym(s, MRB_SYM_2(s->mrb, deconstruct)), 0);
|
||||
|
||||
/* Check minimum length: arr.size >= elems_len */
|
||||
gen_move(s, cursp(), arr_reg, 0);
|
||||
push();
|
||||
genop_3(s, OP_SEND, cursp() - 1, new_sym(s, MRB_SYM_2(s->mrb, size)), 0);
|
||||
gen_int(s, cursp(), elems_len);
|
||||
push(); push(); pop(); pop(); pop();
|
||||
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_OPSYM_2(s->mrb, ge)), 1);
|
||||
tmp = genjmp2(s, OP_JMPNOT, cursp(), *fail_pos, 1);
|
||||
*fail_pos = tmp;
|
||||
|
||||
/* Initialize index to 0 */
|
||||
idx_reg = cursp();
|
||||
gen_int(s, idx_reg, 0);
|
||||
push();
|
||||
|
||||
/* Loop: try matching at each position */
|
||||
loop_start = s->pc;
|
||||
match_fail = JMPLINK_START;
|
||||
|
||||
/* Check if idx <= arr.size - elems_len (i.e., idx < arr.size - elems_len + 1) */
|
||||
/* Compute: arr.size - elems_len */
|
||||
gen_move(s, cursp(), arr_reg, 0);
|
||||
push();
|
||||
genop_3(s, OP_SEND, cursp() - 1, new_sym(s, MRB_SYM_2(s->mrb, size)), 0);
|
||||
gen_int(s, cursp(), elems_len);
|
||||
push(); push(); pop(); pop(); pop();
|
||||
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_OPSYM_2(s->mrb, sub)), 1);
|
||||
/* Now cursp() has (size - elems_len), compare: idx <= (size - elems_len) */
|
||||
gen_move(s, cursp() + 1, idx_reg, 0);
|
||||
push();
|
||||
push(); push(); pop(); pop(); pop();
|
||||
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_OPSYM_2(s->mrb, ge)), 1);
|
||||
tmp = genjmp2(s, OP_JMPNOT, cursp(), *fail_pos, 1);
|
||||
*fail_pos = tmp;
|
||||
|
||||
/* Try to match each middle element at idx+offset */
|
||||
int offset = 0;
|
||||
for (elem = pat_find->elems; elem; elem = elem->cdr, offset++) {
|
||||
/* Get arr[idx + offset] */
|
||||
gen_move(s, cursp(), arr_reg, 0);
|
||||
push();
|
||||
if (offset == 0) {
|
||||
gen_move(s, cursp(), idx_reg, 0);
|
||||
}
|
||||
else {
|
||||
gen_move(s, cursp(), idx_reg, 0);
|
||||
push();
|
||||
gen_int(s, cursp(), offset);
|
||||
push(); push(); pop(); pop(); pop();
|
||||
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_OPSYM_2(s->mrb, add)), 1);
|
||||
}
|
||||
push(); push(); pop(); pop(); pop();
|
||||
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_OPSYM_2(s->mrb, aref)), 1);
|
||||
/* Match element pattern - on fail, try next index */
|
||||
codegen_pattern(s, elem->car, cursp(), &match_fail);
|
||||
}
|
||||
|
||||
/* All elements matched - bind pre and post if named */
|
||||
if (pat_find->pre && pat_find->pre != (node*)-1) {
|
||||
struct mrb_ast_pat_var_node *pre_var = pat_var_node(pat_find->pre);
|
||||
if (pre_var->name) {
|
||||
int var_idx = lv_idx(s, pre_var->name);
|
||||
/* pre = arr[0...idx] (exclusive range) */
|
||||
/* Following the NODE_PAT_ARRAY pattern exactly */
|
||||
gen_move(s, cursp(), arr_reg, 0); /* arr at cursp */
|
||||
push();
|
||||
gen_int(s, cursp(), 0); /* start=0 at cursp */
|
||||
push();
|
||||
gen_move(s, cursp(), idx_reg, 0); /* end=idx at cursp */
|
||||
/* start at cursp-1, end at cursp; create exclusive range at cursp-1 */
|
||||
genop_1(s, OP_RANGE_EXC, cursp() - 1);
|
||||
/* arr at cursp-2, range at cursp-1 */
|
||||
pop(); /* cursp now at range position */
|
||||
pop(); /* cursp now at arr position */
|
||||
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_OPSYM_2(s->mrb, aref)), 1);
|
||||
if (var_idx > 0) {
|
||||
gen_move(s, var_idx, cursp(), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pat_find->post && pat_find->post != (node*)-1) {
|
||||
struct mrb_ast_pat_var_node *post_var = pat_var_node(pat_find->post);
|
||||
if (post_var->name) {
|
||||
int var_idx = lv_idx(s, post_var->name);
|
||||
/* post = arr[(idx+elems_len)..-1] (inclusive range) */
|
||||
/* Following the NODE_PAT_ARRAY pattern exactly */
|
||||
gen_move(s, cursp(), arr_reg, 0); /* arr at cursp */
|
||||
push();
|
||||
/* Compute idx + elems_len for start index */
|
||||
gen_move(s, cursp(), idx_reg, 0); /* idx at cursp */
|
||||
push();
|
||||
gen_int(s, cursp(), elems_len); /* elems_len at cursp */
|
||||
push(); push(); pop(); pop(); pop();
|
||||
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_OPSYM_2(s->mrb, add)), 1);
|
||||
/* start index (idx+elems_len) now at cursp */
|
||||
push();
|
||||
gen_int(s, cursp(), -1); /* end=-1 at cursp */
|
||||
/* start at cursp-1, end at cursp; create inclusive range at cursp-1 */
|
||||
genop_1(s, OP_RANGE_INC, cursp() - 1);
|
||||
/* arr at cursp-2, range at cursp-1 */
|
||||
pop(); /* cursp now at range position */
|
||||
pop(); /* cursp now at arr position */
|
||||
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_OPSYM_2(s->mrb, aref)), 1);
|
||||
if (var_idx > 0) {
|
||||
gen_move(s, var_idx, cursp(), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Jump to success (end of find pattern) */
|
||||
loop_end = genjmp(s, OP_JMP, JMPLINK_START);
|
||||
|
||||
/* Match failed - increment index and try again */
|
||||
dispatch_linked(s, match_fail);
|
||||
gen_move(s, cursp(), idx_reg, 0);
|
||||
push();
|
||||
gen_int(s, cursp(), 1);
|
||||
push(); push(); pop(); pop(); pop();
|
||||
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_OPSYM_2(s->mrb, add)), 1);
|
||||
gen_move(s, idx_reg, cursp(), 0);
|
||||
genjmp(s, OP_JMP, loop_start);
|
||||
|
||||
/* Success exit point */
|
||||
dispatch(s, loop_end);
|
||||
|
||||
pop(); /* idx_reg */
|
||||
pop(); /* arr_reg */
|
||||
}
|
||||
break;
|
||||
|
||||
case NODE_PAT_HASH:
|
||||
{
|
||||
struct mrb_ast_pat_hash_node *pat_hash = pat_hash_node(pattern);
|
||||
|
||||
@@ -90,6 +90,7 @@ enum node_type {
|
||||
NODE_PAT_AS, /* as pattern (pattern => var) */
|
||||
NODE_PAT_ALT, /* alternative pattern (pat1 | pat2) */
|
||||
NODE_PAT_ARRAY, /* array pattern [a, b, *rest] */
|
||||
NODE_PAT_FIND, /* find pattern [*pre, elem, *post] */
|
||||
NODE_PAT_HASH, /* hash pattern {a:, b: x} */
|
||||
NODE_MATCH_PAT, /* one-line pattern matching (expr in pat / expr => pat) */
|
||||
NODE_LAST
|
||||
@@ -334,6 +335,14 @@ struct mrb_ast_pat_array_node {
|
||||
struct mrb_ast_node *post; /* Post-rest patterns (cons list) */
|
||||
};
|
||||
|
||||
/* Find pattern node [*pre, elem, *post] - searches for elem anywhere in array */
|
||||
struct mrb_ast_pat_find_node {
|
||||
struct mrb_ast_var_header header; /* 8 bytes */
|
||||
struct mrb_ast_node *pre; /* Pre rest pattern (NULL or -1 if anonymous) */
|
||||
struct mrb_ast_node *elems; /* Middle patterns to find (cons list) */
|
||||
struct mrb_ast_node *post; /* Post rest pattern (NULL or -1 if anonymous) */
|
||||
};
|
||||
|
||||
/* Hash pattern node {a:, b: x} */
|
||||
struct mrb_ast_pat_hash_node {
|
||||
struct mrb_ast_var_header header; /* 8 bytes */
|
||||
@@ -459,6 +468,7 @@ struct mrb_ast_super_node {
|
||||
#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_find_node(n) ((struct mrb_ast_pat_find_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))
|
||||
|
||||
@@ -674,6 +674,17 @@ new_pat_array(parser_state *p, node *pre, node *rest, node *post)
|
||||
return (node*)n;
|
||||
}
|
||||
|
||||
/* Create find pattern node [*pre, elems, *post] */
|
||||
static node*
|
||||
new_pat_find(parser_state *p, node *pre, node *elems, node *post)
|
||||
{
|
||||
struct mrb_ast_pat_find_node *n = NEW_NODE(pat_find, NODE_PAT_FIND);
|
||||
n->pre = pre;
|
||||
n->elems = elems;
|
||||
n->post = post;
|
||||
return (node*)n;
|
||||
}
|
||||
|
||||
/* Create hash pattern node {a:, b: x, **rest} */
|
||||
static node*
|
||||
new_pat_hash(parser_state *p, node *pairs, node *rest)
|
||||
@@ -2095,7 +2106,7 @@ prohibit_literals(parser_state *p, node *n)
|
||||
%type <id> f_label f_kwrest
|
||||
|
||||
/* pattern matching */
|
||||
%type <nd> in_clauses p_expr p_alt p_value p_var p_as p_array p_array_body p_array_elems p_rest p_hash p_hash_body p_hash_elems p_hash_elem p_kwrest p_args_head p_args_post
|
||||
%type <nd> in_clauses p_expr p_alt p_value p_var p_as p_array p_array_body p_array_elems p_rest p_hash p_hash_body p_hash_elems p_hash_elem p_kwrest p_args_head p_args_post p_const
|
||||
|
||||
%token tUPLUS "unary plus"
|
||||
%token tUMINUS "unary minus"
|
||||
@@ -3989,17 +4000,9 @@ p_value : p_var
|
||||
{
|
||||
$$ = new_pat_value(p, new_false(p));
|
||||
}
|
||||
| tCONSTANT
|
||||
| p_const
|
||||
{
|
||||
$$ = 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));
|
||||
$$ = new_pat_value(p, $1);
|
||||
}
|
||||
| p_array
|
||||
| p_hash
|
||||
@@ -4046,6 +4049,11 @@ p_array_body : p_array_elems
|
||||
/* Rest + post, no pre */
|
||||
$$ = new_pat_array(p, 0, $1, $3);
|
||||
}
|
||||
| p_rest ',' p_array_elems ',' p_rest
|
||||
{
|
||||
/* Find pattern: [*pre, elems, *post] */
|
||||
$$ = new_pat_find(p, $1, $3, $5);
|
||||
}
|
||||
;
|
||||
|
||||
/* Non-rest array pattern elements - use p_as, not p_expr to avoid bracket-less recursion */
|
||||
@@ -4071,6 +4079,21 @@ p_rest : tSTAR tIDENTIFIER
|
||||
}
|
||||
;
|
||||
|
||||
/* Constant path for pattern matching: Foo, Foo::Bar, ::Foo */
|
||||
p_const : tCONSTANT
|
||||
{
|
||||
$$ = new_const(p, $1);
|
||||
}
|
||||
| p_const tCOLON2 tCONSTANT
|
||||
{
|
||||
$$ = new_colon2(p, $1, $3);
|
||||
}
|
||||
| tCOLON3 tCONSTANT
|
||||
{
|
||||
$$ = new_colon3(p, $2);
|
||||
}
|
||||
;
|
||||
|
||||
/* Hash pattern: {a:, b: x, **rest} */
|
||||
p_hash : tLBRACE p_hash_body '}'
|
||||
{
|
||||
|
||||
+4328
-4894
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user