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