mruby-compiler: implement array pattern matching

add support for array destructuring patterns in case/in expressions:
- [a, b, c] - fixed length match
- [first, *rest] - head + rest
- [*init, last] - init + tail
- [first, *middle, last] - head + middle + tail
- [1, x, 3] - mixed value and variable patterns
- [first, *, last] - anonymous rest (discarded)

implementation includes:
- grammar rules for p_array, p_array_body, p_rest in parse.y
- NODE_PAT_ARRAY codegen with deconstruct call and length checks
- rest variable binding via range slicing (arr[pre..-(post+1)])
- Array#deconstruct method (returns self)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-12-17 12:18:51 +09:00
parent e61d71aa29
commit ec67fd9587
4 changed files with 5136 additions and 4474 deletions
+104
View File
@@ -4410,6 +4410,110 @@ codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos)
}
break;
case NODE_PAT_ARRAY:
{
struct mrb_ast_pat_array_node *pat_arr = pat_array_node(pattern);
int pre_len = 0, post_len = 0;
int arr_reg = cursp();
node *elem;
int i;
/* Count pre and post elements */
for (elem = pat_arr->pre; elem; elem = elem->cdr) pre_len++;
for (elem = pat_arr->post; elem; elem = elem->cdr) post_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 length constraints */
if (pat_arr->rest == 0) {
/* No rest: exact length match */
/* Generate: arr.size == pre_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(), pre_len);
push(); push(); pop(); pop(); pop();
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_OPSYM_2(s->mrb, eq)), 1);
tmp = genjmp2(s, OP_JMPNOT, cursp(), *fail_pos, 1);
*fail_pos = tmp;
}
else {
/* Has rest: minimum length check */
int min_len = pre_len + post_len;
if (min_len > 0) {
/* Generate: arr.size >= min_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(), min_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;
}
}
/* Match pre-rest elements */
i = 0;
for (elem = pat_arr->pre; elem; elem = elem->cdr, i++) {
/* Get arr[i] */
gen_move(s, cursp(), arr_reg, 0);
push();
gen_int(s, cursp(), i);
push(); push(); pop(); pop(); pop();
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_OPSYM_2(s->mrb, aref)), 1);
/* Match element pattern */
codegen_pattern(s, elem->car, cursp(), fail_pos);
}
/* Bind rest elements if rest is a variable */
if (pat_arr->rest && pat_arr->rest != (node*)-1) {
struct mrb_ast_pat_var_node *rest_var = pat_var_node(pat_arr->rest);
if (rest_var->name) {
int var_idx = lv_idx(s, rest_var->name);
/* Generate: arr[pre_len..-(post_len+1)] or arr[pre_len..-1] if no post */
gen_move(s, cursp(), arr_reg, 0); /* arr at cursp */
push();
gen_int(s, cursp(), pre_len); /* start at cursp */
push();
if (post_len > 0) {
gen_int(s, cursp(), -(post_len + 1)); /* end at cursp */
}
else {
gen_int(s, cursp(), -1); /* end 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);
}
}
}
/* Match post-rest elements */
i = -post_len;
for (elem = pat_arr->post; elem; elem = elem->cdr, i++) {
/* Get arr[i] (negative index from end) */
gen_move(s, cursp(), arr_reg, 0);
push();
gen_int(s, cursp(), i);
push(); push(); pop(); pop(); pop();
genop_3(s, OP_SEND, cursp(), new_sym(s, MRB_OPSYM_2(s->mrb, aref)), 1);
/* Match element pattern */
codegen_pattern(s, elem->car, cursp(), fail_pos);
}
pop(); /* Pop arr_reg */
}
break;
default:
raise_error(s, "unsupported pattern type");
break;
+75 -1
View File
@@ -653,6 +653,17 @@ new_pat_alt(parser_state *p, node *left, node *right)
return (node*)n;
}
/* Create array pattern node [a, b, *rest, c] */
static node*
new_pat_array(parser_state *p, node *pre, node *rest, node *post)
{
struct mrb_ast_pat_array_node *n = NEW_NODE(pat_array, NODE_PAT_ARRAY);
n->pre = pre;
n->rest = rest;
n->post = post;
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)
@@ -2053,7 +2064,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
%type <nd> in_clauses p_expr p_alt p_value p_var p_as p_array p_array_body p_array_elems p_rest
%token tUPLUS "unary plus"
%token tUMINUS "unary minus"
@@ -3893,6 +3904,69 @@ p_value : p_var
{
$$ = new_pat_value(p, new_colon3(p, $2));
}
| p_array
;
/* Array pattern: [a, b, *rest, c] */
p_array : tLBRACK p_array_body ']'
{
$$ = $2;
}
| tLBRACK ']'
{
$$ = new_pat_array(p, 0, 0, 0);
}
;
/* Array pattern body - pre elements, optional rest, post elements */
p_array_body : p_array_elems
{
/* Just pre elements, no rest */
$$ = new_pat_array(p, $1, 0, 0);
}
| p_array_elems ',' p_rest
{
/* Pre elements + rest, no post */
$$ = new_pat_array(p, $1, $3, 0);
}
| p_array_elems ',' p_rest ',' p_array_elems
{
/* Pre + rest + post */
$$ = new_pat_array(p, $1, $3, $5);
}
| p_rest
{
/* Just rest, no pre or post */
$$ = new_pat_array(p, 0, $1, 0);
}
| p_rest ',' p_array_elems
{
/* Rest + post, no pre */
$$ = new_pat_array(p, 0, $1, $3);
}
;
/* Non-rest array pattern elements */
p_array_elems : p_expr
{
$$ = list1($1);
}
| p_array_elems ',' p_expr
{
$$ = push($1, $3);
}
;
/* Rest pattern in array: *var, *_, or just * */
p_rest : tSTAR tIDENTIFIER
{
$$ = new_pat_var(p, $2);
}
| tSTAR
{
/* Anonymous rest pattern */
$$ = (node*)-1;
}
;
p_var : tIDENTIFIER
File diff suppressed because it is too large Load Diff
+10
View File
@@ -85,6 +85,16 @@ class Array
self.dup.sort!(&block)
end
##
# call-seq:
# array.deconstruct -> self
#
# Returns self. Used for array pattern matching in case/in expressions.
#
def deconstruct
self
end
##
# Array is enumerable
# ISO 15.2.12.3