feat: map iteration (for k, v in m) + multi element writes
log-watcher parse errors 7 -> 3 (only `as` left); corpus 71/0, woc 565/0. - wob.h/builtin.c/loader.c: WO_B_MULTI_SET — `m[i] = v` for a multi, dropping the element it replaces (the mirror of map_set, which the format doc's sugar rule already had; the "no element write" gap is closed) - ast/parser: `for k, v in m` — the second name binds the value for that key - types.ml/owner.ml: the two cursors take the map's key and value types; both are borrows of what the map owns, so neither is dropped per iteration - emit.ml: map form lowers to len + key_at/val_at over slot indexes (insertion order, cont.h's parallel arrays), same loop skeleton as the multi form Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e5c5952cdc
commit
ea77fc9d5c
9 changed files with 164 additions and 29 deletions
|
|
@ -318,6 +318,11 @@ and stmt_kind =
|
|||
}
|
||||
| For of {
|
||||
var : string;
|
||||
(* `for k, v in m` over a `map<K, V>`: the second name binds the value
|
||||
for that key. None is the one-name form, over a `multi`. Map
|
||||
enumeration is slot-ordered (runtime/src/cont.h's parallel arrays),
|
||||
which is insertion order. *)
|
||||
var2 : string option;
|
||||
iter : expr;
|
||||
body : stmt list;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -273,7 +273,8 @@ let rec dump_stmt (s : Ast.stmt) : string list =
|
|||
(header :: indent_block then_body) @ else_lines
|
||||
| Ast.While { cond; body } ->
|
||||
Printf.sprintf "%s WHILE %s" (pos_str s.Ast.s_pos) (expr_str cond) :: indent_block body
|
||||
| Ast.For { var; iter; body } ->
|
||||
| Ast.For { var; var2; iter; body } ->
|
||||
let var = match var2 with Some v2 -> var ^ ", " ^ v2 | None -> var in
|
||||
Printf.sprintf "%s FOR %s IN %s" (pos_str s.Ast.s_pos) var (expr_str iter) :: indent_block body
|
||||
| Ast.Return None -> [ Printf.sprintf "%s RETURN" (pos_str s.Ast.s_pos) ]
|
||||
| Ast.Return (Some e) -> [ Printf.sprintf "%s RETURN %s" (pos_str s.Ast.s_pos) (expr_str e) ]
|
||||
|
|
|
|||
|
|
@ -254,6 +254,7 @@ let b_reverse = 35
|
|||
let b_map_remove = 36
|
||||
let b_map_key_at = 37
|
||||
let b_map_val_at = 38
|
||||
let b_multi_set = 39
|
||||
|
||||
let ins_abc op a b c = op lor (a lsl 8) lor (b lsl 16) lor (c lsl 24)
|
||||
let ins_abx op a bx = op lor (a lsl 8) lor (bx lsl 16)
|
||||
|
|
@ -2605,7 +2606,7 @@ and emit_stmt (p : pctx) (f : fstate) (v : views) (s : Ast.stmt) : unit =
|
|||
| Return opt -> emit_return p f v s opt
|
||||
| If { cond; then_body; else_body } -> emit_if p f v s cond then_body else_body
|
||||
| While { cond; body } -> emit_while p f v s cond body
|
||||
| For { var; iter; body } -> emit_for p f v s var iter body
|
||||
| For { var; var2; iter; body } -> emit_for p f v s var var2 iter body
|
||||
| Break -> emit_break p f v s
|
||||
| Continue -> emit_continue p f v s
|
||||
| DoWhile { body; cond } -> emit_do_while p f v s body cond
|
||||
|
|
@ -2735,10 +2736,20 @@ and emit_assign (p : pctx) (f : fstate) (v : views) (s : Ast.stmt) (target : Ast
|
|||
put f (ins_abc op_builtin sink w b_map_set);
|
||||
release_guards f guards
|
||||
| Multi _ ->
|
||||
err p ~code:cannot_lower_code ~file:f.f_file ~pos:target.pos
|
||||
~message:
|
||||
"element assignment into a `multi` — the v1 instruction set has push/get but no \
|
||||
element write"
|
||||
(* `m[i] = x` is the multi_set builtin — container, index, value in
|
||||
three consecutive registers, exactly the map_set shape above.
|
||||
The element it replaces is the container's, so the VM drops it. *)
|
||||
let w = alloc_temps p f s.s_pos 3 in
|
||||
emit_expr p f v ~dst:w base;
|
||||
Hashtbl.replace f.f_node target.id w;
|
||||
emit_expr p f v ~dst:(w + 1) idx;
|
||||
emit_expr p f v ~dst:(w + 2) value;
|
||||
let sink = alloc_temp p f s.s_pos in
|
||||
f.f_cur_line <- s.s_pos.line;
|
||||
let guards = residual_guards p f v s.s_id None in
|
||||
acquire_guards f guards;
|
||||
put f (ins_abc op_builtin sink w b_multi_set);
|
||||
release_guards f guards
|
||||
| _ ->
|
||||
err p ~code:cannot_lower_code ~file:f.f_file ~pos:target.pos
|
||||
~message:"element assignment into a value that is neither a `multi` nor a `map`")
|
||||
|
|
@ -2928,9 +2939,76 @@ and emit_while (p : pctx) (f : fstate) (v : views) (s : Ast.stmt) (cond : Ast.ex
|
|||
because BUILTIN's argument window is consecutive. A `map` has no
|
||||
key-enumeration builtin in v1, so iterating one is WO-E403 rather
|
||||
than invented bytecode. *)
|
||||
and emit_for (p : pctx) (f : fstate) (v : views) (s : Ast.stmt) (var : string) (iter : Ast.expr)
|
||||
(body : Ast.stmt list) : unit =
|
||||
and emit_for (p : pctx) (f : fstate) (v : views) (s : Ast.stmt) (var : string)
|
||||
(var2 : string option) (iter : Ast.expr) (body : Ast.stmt list) : unit =
|
||||
match ty_of_expr p f iter with
|
||||
| Some t
|
||||
when (match (unwrap t, var2) with Map _, Some _ -> true | _ -> false) ->
|
||||
(* `for k, v in m` — slot-ordered enumeration over the map's parallel
|
||||
arrays: `len` bounds it, `key_at`/`val_at` read slot i. Both cursors
|
||||
are borrows of what the map owns, so nothing is dropped per
|
||||
iteration (owner.ml declares them l_holds = false). *)
|
||||
let kt, vt = match unwrap t with Map (k, v') -> (Scalar k, Scalar v') | _ -> (Scalar "Int", Scalar "Int") in
|
||||
let v2 = match var2 with Some n -> n | None -> "_" in
|
||||
let saved_locals = f.f_nlocals in
|
||||
let saved_env = f.f_env in
|
||||
let saved_decls = f.f_declared in
|
||||
let div0 = f.f_div in
|
||||
let rc = alloc_local p f s.s_pos in
|
||||
let ri = alloc_local p f s.s_pos in
|
||||
let rn = alloc_local p f s.s_pos in
|
||||
let rk = alloc_local p f s.s_pos in
|
||||
let rv = alloc_local p f s.s_pos in
|
||||
f.f_temp <- f.f_nlocals;
|
||||
emit_expr p f v ~dst:rc iter;
|
||||
f.f_cur_line <- s.s_pos.line;
|
||||
put f (ins_abc op_builtin rn rc b_len);
|
||||
put f (ins_abx op_loadk ri (check_bx p f s.s_pos "constant" (const_int p 0)));
|
||||
f.f_env <- (v2, (rv, vt)) :: (var, (rk, kt)) :: f.f_env;
|
||||
Hashtbl.replace f.f_decl s.s_id rk;
|
||||
f.f_declared <- s.s_id :: f.f_declared;
|
||||
let top = here f in
|
||||
let entry_owned = f.f_owned and entry_gc = f.f_gc in
|
||||
f.f_temp <- f.f_nlocals;
|
||||
let tc = alloc_temp p f s.s_pos in
|
||||
put f (ins_abc op_lt tc ri rn);
|
||||
let jz = here f in
|
||||
put f (ins_asbx op_jz tc 0);
|
||||
(* the builtin window is (container, index) in two consecutive slots *)
|
||||
let w = alloc_temps p f s.s_pos 2 in
|
||||
put f (ins_abc op_move w rc 0);
|
||||
put f (ins_abc op_move (w + 1) ri 0);
|
||||
put f (ins_abc op_builtin rk w b_map_key_at);
|
||||
put f (ins_abc op_builtin rv w b_map_val_at);
|
||||
let lf = { lf_node = s.s_id; lf_breaks = []; lf_continues = [] } in
|
||||
f.f_loops <- lf :: f.f_loops;
|
||||
List.iter (emit_stmt p f v) body;
|
||||
f.f_loops <- List.tl f.f_loops;
|
||||
emit_scope_drops p f v ~node:s.s_id ~label:"FOR";
|
||||
emit_rc p f v ~node:s.s_id ~acquire:false ~groups:(declared_since f saved_decls) ();
|
||||
let continue_target = here f in
|
||||
List.iter (fun pc -> patch_jump p f ~file:f.f_file ~pos:s.s_pos pc continue_target) lf.lf_continues;
|
||||
f.f_temp <- f.f_nlocals;
|
||||
f.f_cur_line <- s.s_pos.line;
|
||||
let one = alloc_temp p f s.s_pos in
|
||||
put f (ins_abx op_loadk one (check_bx p f s.s_pos "constant" (const_int p 1)));
|
||||
put f (ins_abc op_add ri ri one);
|
||||
let back = here f in
|
||||
put f (ins_asbx op_jmp 0 0);
|
||||
patch_jump p f ~file:f.f_file ~pos:s.s_pos back top;
|
||||
let exit_pc = here f in
|
||||
patch_jump p f ~file:f.f_file ~pos:s.s_pos jz exit_pc;
|
||||
List.iter (fun pc -> patch_jump p f ~file:f.f_file ~pos:s.s_pos pc exit_pc) lf.lf_breaks;
|
||||
if f.f_div then begin
|
||||
f.f_owned <- entry_owned;
|
||||
f.f_gc <- entry_gc
|
||||
end
|
||||
else mask_meet f entry_owned entry_gc;
|
||||
f.f_div <- div0;
|
||||
f.f_nlocals <- saved_locals;
|
||||
f.f_env <- saved_env;
|
||||
f.f_declared <- saved_decls;
|
||||
f.f_temp <- saved_locals
|
||||
| Some t when (match unwrap t with Multi _ -> true | _ -> false) ->
|
||||
let elem = match unwrap t with Multi e -> Scalar e | other -> other in
|
||||
let saved_locals = f.f_nlocals in
|
||||
|
|
|
|||
|
|
@ -1505,11 +1505,23 @@ and analyze_stmt (ctx : ctx) (s : Ast.stmt) : unit =
|
|||
read_expr ctx cond;
|
||||
analyze_block ctx ~node:s.s_id ~pos:s.s_pos ~label:"WHILE" body);
|
||||
ctx.loop_stack <- List.tl ctx.loop_stack
|
||||
| For { var; iter; body } ->
|
||||
| For { var; var2; iter; body } ->
|
||||
read_expr ctx iter;
|
||||
let src = place_of iter in
|
||||
let item_ty =
|
||||
match expr_ty ctx iter with Some t -> ( match elem_ty t with Some e -> e | None -> t) | None -> Scalar "Int"
|
||||
let iter_ty = expr_ty ctx iter in
|
||||
(* `for k, v in m` binds the map's key and value types; the one-name
|
||||
form binds the element type (elem_ty). Both cursors are BORROWS of
|
||||
what the container owns, so neither is ever dropped by the body. *)
|
||||
let key_ty, item_ty =
|
||||
match (iter_ty, var2) with
|
||||
| Some (Map (k, v)), Some _ -> (Some (Scalar k), Scalar v)
|
||||
| Some (Nullable (Map (k, v))), Some _ -> (Some (Scalar k), Scalar v)
|
||||
| Some t, _ -> (None, ( match elem_ty t with Some e -> e | None -> t))
|
||||
| None, _ -> (None, Scalar "Int")
|
||||
in
|
||||
let cursor (n : string) (t : Ast.field_ty) : local =
|
||||
{ l_name = n; l_ty = t; l_class = oclass_of ctx t; l_node = s.s_id; l_pos = s.s_pos;
|
||||
l_holds = false; l_src = src; l_bkind = AShared; l_state = Borrowed s.s_pos }
|
||||
in
|
||||
ctx.loop_stack <- s.s_id :: ctx.loop_stack;
|
||||
fixpoint ctx
|
||||
|
|
@ -1517,10 +1529,11 @@ and analyze_stmt (ctx : ctx) (s : Ast.stmt) : unit =
|
|||
push_scope ctx ~node:s.s_id ~pos:s.s_pos ~label:"FOR";
|
||||
(* the cursor borrows an element of the iterable for the whole
|
||||
body: moving the container out from under it is E302 *)
|
||||
declare ctx
|
||||
{ l_name = var; l_ty = item_ty; l_class = oclass_of ctx item_ty; l_node = s.s_id;
|
||||
l_pos = s.s_pos; l_holds = false; l_src = src; l_bkind = AShared;
|
||||
l_state = Borrowed s.s_pos };
|
||||
(match (key_ty, var2) with
|
||||
| Some kt, Some v2 ->
|
||||
declare ctx (cursor var kt);
|
||||
declare ctx (cursor v2 item_ty)
|
||||
| _ -> declare ctx (cursor var item_ty));
|
||||
List.iter (analyze_stmt ctx) body;
|
||||
pop_scope ctx);
|
||||
ctx.loop_stack <- List.tl ctx.loop_stack
|
||||
|
|
|
|||
|
|
@ -1202,10 +1202,14 @@ and parse_for_stmt (st : state) : Ast.stmt =
|
|||
ignore (advance st);
|
||||
(* 'for' *)
|
||||
let var = expect_ident st "loop variable name" in
|
||||
(* `for k, v in m` — the map form (see Ast.For.var2) *)
|
||||
let var2 =
|
||||
if accept st Token.Comma then Some (expect_ident st "second loop variable name") else None
|
||||
in
|
||||
expect st Token.KwIn "`in`";
|
||||
let iter = parse_expr_no_brace st in
|
||||
let body = parse_block st in
|
||||
{ Ast.s_id = id; s_pos = pos; s_kind = Ast.For { var; iter; body } }
|
||||
{ Ast.s_id = id; s_pos = pos; s_kind = Ast.For { var; var2; iter; body } }
|
||||
|
||||
and parse_return_stmt (st : state) : Ast.stmt =
|
||||
let pos = peek_pos st in
|
||||
|
|
@ -1820,9 +1824,13 @@ and subst_stmt (consts : Ast.expr StringMap.t) (bound : StringSet.t) (s : Ast.st
|
|||
}
|
||||
| Ast.While { cond; body } ->
|
||||
{ s with Ast.s_kind = Ast.While { cond = e cond; body = subst_block consts bound body } }
|
||||
| Ast.For { var; iter; body } ->
|
||||
let bound' = StringSet.add var bound in
|
||||
{ s with Ast.s_kind = Ast.For { var; iter = e iter; body = subst_block consts bound' body } }
|
||||
| Ast.For { var; var2; iter; body } ->
|
||||
let bound' =
|
||||
match var2 with
|
||||
| Some v2 -> StringSet.add v2 (StringSet.add var bound)
|
||||
| None -> StringSet.add var bound
|
||||
in
|
||||
{ s with Ast.s_kind = Ast.For { var; var2; iter = e iter; body = subst_block consts bound' body } }
|
||||
| Ast.Return opt -> { s with Ast.s_kind = Ast.Return (Option.map e opt) }
|
||||
| Ast.ExprStmt ex -> { s with Ast.s_kind = Ast.ExprStmt (e ex) }
|
||||
| Ast.Break | Ast.Continue -> s
|
||||
|
|
|
|||
|
|
@ -1686,14 +1686,22 @@ let typecheck_program ~file ~(module_of : string -> string)
|
|||
| While { cond; body } ->
|
||||
let _ = typecheck_expr env cenv cond in
|
||||
List.fold_left typecheck_stmt (env, cenv) body
|
||||
| For { var; iter; body } ->
|
||||
| For { var; var2; iter; body } ->
|
||||
let iter_res = typecheck_expr env cenv iter in
|
||||
let env_body = StringMap.add var iter_res.typ env in
|
||||
let cenv_body =
|
||||
match confident_typ cenv iter with
|
||||
| Some (TMulti inner_t) -> StringMap.add var inner_t cenv
|
||||
| _ -> StringMap.remove var cenv
|
||||
(* `for k, v in m`: the names take the map's key and value types.
|
||||
The one-name form over a `multi` keeps the element type. *)
|
||||
let bind (env0 : typ StringMap.t) (t : typ option) : typ StringMap.t =
|
||||
match (t, var2) with
|
||||
| Some (TMap (kt, vt)), Some v2 -> StringMap.add v2 vt (StringMap.add var kt env0)
|
||||
| Some (TMulti it), None -> StringMap.add var it env0
|
||||
| Some other, None -> StringMap.add var other env0
|
||||
| _ -> (
|
||||
match var2 with
|
||||
| Some v2 -> StringMap.remove v2 (StringMap.remove var env0)
|
||||
| None -> StringMap.remove var env0)
|
||||
in
|
||||
let env_body = bind env (Some iter_res.typ) in
|
||||
let cenv_body = bind cenv (confident_typ cenv iter) in
|
||||
List.fold_left typecheck_stmt (env_body, cenv_body) body
|
||||
| Return opt_e ->
|
||||
(match opt_e with Some e -> let _ = typecheck_expr env cenv e in () | None -> ());
|
||||
|
|
@ -1878,9 +1886,14 @@ and walk_stmt (bound : StringSet.t) (visit : StringSet.t -> expr -> unit) (s : s
|
|||
walk_expr bound visit cond;
|
||||
walk_block bound visit body;
|
||||
bound
|
||||
| For { var; iter; body } ->
|
||||
| For { var; var2; iter; body } ->
|
||||
walk_expr bound visit iter;
|
||||
walk_block (StringSet.add var bound) visit body;
|
||||
let bound' =
|
||||
match var2 with
|
||||
| Some v2 -> StringSet.add v2 (StringSet.add var bound)
|
||||
| None -> StringSet.add var bound
|
||||
in
|
||||
walk_block bound' visit body;
|
||||
bound
|
||||
| Return (Some e) ->
|
||||
walk_expr bound visit e;
|
||||
|
|
|
|||
|
|
@ -582,6 +582,20 @@ int wo_builtin(wo_vm *vm, uint64_t *R, uint32_t ins, const char **msg) {
|
|||
R[A] = C == WO_B_MAP_KEY_AT ? m->keys[i] : m->vals[i];
|
||||
return 0;
|
||||
}
|
||||
case WO_B_MULTI_SET: { /* `m[i] = v`: the replaced element was the
|
||||
* container's, so it dies here */
|
||||
wo_multi *m = native_check(R[B], WO_CLS_MULTI, msg);
|
||||
if (!m) return WO_T_BOUNDS;
|
||||
uint64_t i = R[B + 1];
|
||||
if (i >= m->len) {
|
||||
*msg = "multi index out of range";
|
||||
return WO_T_BOUNDS;
|
||||
}
|
||||
if (m->items[i] != R[B + 2]) wo_drop_kind(rt, m->elem_kind, m->items[i]);
|
||||
m->items[i] = R[B + 2];
|
||||
R[A] = 0;
|
||||
return 0;
|
||||
}
|
||||
default: /* unreachable: loader validated the id */
|
||||
*msg = "unknown builtin";
|
||||
return WO_T_EXPLICIT;
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ static const uint8_t b_arity[WO_B_MAX + 1] = {
|
|||
[WO_B_SPLIT] = 2, [WO_B_SPLIT_WS] = 1, [WO_B_JOIN] = 2,
|
||||
[WO_B_SLICE] = 3, [WO_B_POP] = 1, [WO_B_SHIFT] = 1,
|
||||
[WO_B_SORT] = 1, [WO_B_REVERSE] = 1, [WO_B_MAP_REMOVE] = 2,
|
||||
[WO_B_MAP_KEY_AT] = 2, [WO_B_MAP_VAL_AT] = 2,
|
||||
[WO_B_MAP_KEY_AT] = 2, [WO_B_MAP_VAL_AT] = 2, [WO_B_MULTI_SET] = 3,
|
||||
};
|
||||
|
||||
static int vtab_cmp(const void *a, const void *b) {
|
||||
|
|
|
|||
|
|
@ -205,8 +205,11 @@ enum {
|
|||
WO_B_MAP_REMOVE = 36, /* (map, key) -> 1/0; drops the removed key and value */
|
||||
WO_B_MAP_KEY_AT = 37, /* (map, i) -> key at slot i (insertion order) */
|
||||
WO_B_MAP_VAL_AT = 38, /* (map, i) -> value at slot i */
|
||||
WO_B_MULTI_SET = 39, /* (multi, i, v) -> 0; in-place element write,
|
||||
* dropping the element it replaces. `m[i] = v`
|
||||
* for a multi, the mirror of map_set. */
|
||||
};
|
||||
#define WO_B_MAX 38u
|
||||
#define WO_B_MAX 39u
|
||||
|
||||
/* ---- instruction encode/decode: op:8 A:8 then B:8 C:8 or Bx:16 ---- */
|
||||
static inline uint32_t wo_ins_abc(uint8_t op, uint8_t a, uint8_t b, uint8_t c) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue