From 76dacf69850170904698b4dbc9b24d6e93f08c53 Mon Sep 17 00:00:00 2001 From: "shoney.arickathil" Date: Fri, 14 Aug 2026 16:56:55 +0200 Subject: [PATCH] feat(compiler): richer field defaults + cross-file consts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit log-watcher diagnostics 55 -> 48 (all json-rooted now). corpus 71/0, woc 565/0. - emit.ml: a field default may now be `nil`, `{}` (a fresh empty container of the field's declared type) or `Rec {}` (a record built from its own field defaults) — the workload's `st: TailState = TailState {}` and `scheduled: map = {}` shapes - parser.ml/main.ml: a top-level `const` is visible to every file of its module, not just its own file — files in a directory are one module by the discovery contract, and the workload reads logtail.wo's `const CHUNK` from mcp.wo. A file's own const still wins on a name collision Co-Authored-By: Claude Opus 5 (1M context) --- compiler/bin/main.ml | 29 ++++++++++++++++++++++------ compiler/src/emit.ml | 44 ++++++++++++++++++++++++++++++++++++++++-- compiler/src/parser.ml | 17 ++++++++++++---- 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/compiler/bin/main.ml b/compiler/bin/main.ml index 5ed34ed..dd71b60 100644 --- a/compiler/bin/main.ml +++ b/compiler/bin/main.ml @@ -198,14 +198,31 @@ let finish (collector : Woc_lib.Diag.Collector.t) (lookup : Woc_lib.Diag.source_ check-pass calls see, so a class declared in one file resolves for a field/constructor/etc. in another regardless of discovery order. *) +(* Parses every discovered file, then makes one more constant-substitution + pass so a `const` declared in one file reaches its siblings: files in a + directory are one module and unconditionally visible to each other + (haxe-parity Task 1), and the workload relies on it (logtail.wo's + `const CHUNK` is read from mcp.wo). Parser.parse already substituted each + file's own constants; this second pass only fills names that were still + unresolved, since a file's own const wins over a sibling's. *) let parse_all (collector : Woc_lib.Diag.Collector.t) (sources : (string * string) list) : (string * Woc_lib.Ast.program) list = - List.map - (fun (f, src) -> - let toks = Woc_lib.Lexer.tokenize collector ~file:f src in - let prog = Woc_lib.Parser.parse collector ~file:f toks in - (f, prog)) - sources + let parsed = + List.map + (fun (f, src) -> + let toks = Woc_lib.Lexer.tokenize collector ~file:f src in + let prog = Woc_lib.Parser.parse collector ~file:f toks in + (f, prog)) + sources + in + let module SM = Woc_lib.Parser.StringMap in + let all_consts = + List.fold_left + (fun acc (_, prog) -> SM.fold SM.add (Woc_lib.Parser.top_level_consts prog) acc) + SM.empty parsed + in + if SM.is_empty all_consts then parsed + else List.map (fun (f, prog) -> (f, Woc_lib.Parser.subst_consts ~extra:all_consts prog)) parsed let merge_symbols (syms_list : Woc_lib.Types.symbols list) : Woc_lib.Types.symbols = let module SM = Woc_lib.Types.StringMap in diff --git a/compiler/src/emit.ml b/compiler/src/emit.ml index 96c76b8..aa94b57 100644 --- a/compiler/src/emit.ml +++ b/compiler/src/emit.ml @@ -2076,14 +2076,54 @@ and emit_default_value (p : pctx) (f : fstate) ~(dst : int) ~(fty : Ast.field_ty let bad () = err p ~code:cannot_lower_code ~file:f.f_file ~pos ~message: - "cannot lower this field default — only Int/Text/Bool literals, `now()`, and `[]` are \ - supported"; + "cannot lower this field default — only Int/Text/Bool literals, `nil`, `now()`, `[]`, \ + `{}` and `Rec {}` are supported"; put f (ins_abx op_loadk dst (const_int p 0)) in match d with | Ast.DefaultNow -> put f (ins_abc op_builtin dst dst b_now) | Ast.DefaultOpaque toks -> ( match List.map (fun (t : Token.t) -> t.Token.kind) toks with + (* `= nil` — the zero word, whatever `?T` the field is *) + | [ Token.KwNil ] -> put f (ins_abx op_loadk dst (const_int p 0)) + (* `= {}` — a fresh empty container of the field's own declared type, + the same rule `[]` above follows *) + | [ Token.LBrace; Token.RBrace ] -> ( + match container_imm p (Some fty) (match unwrap fty with Map _ -> true | _ -> false) with + | Some imm -> + put f + (ins_abc op_builtin dst imm (match unwrap fty with Map _ -> b_map_new | _ -> b_multi_new)) + | None -> bad ()) + (* `= Rec {}` — a record built from ITS own field defaults, which is how + the workload's `st: TailState = TailState {}` self-initializes. Only + the empty literal: a default with real field values is a general + expression, and defaults are an opaque token span by design. *) + | [ Token.Ident cn; Token.LBrace; Token.RBrace ] -> ( + match class_of_name p cn with + | None -> bad () + | Some cid -> + put f (ins_abx op_new dst (check_bx p f pos "class" cid)); + let outer = f.f_temp in + if f.f_temp <= dst then f.f_temp <- dst + 1; + (match Types.StringMap.find_opt cn p.p_syms.Types.classes with + | None -> () + | Some (ci : Types.class_info) -> + List.iter + (fun (fname, _, fdefault, _) -> + match fdefault with + | Some d2 -> ( + match field_of p cid fname with + | None -> () + | Some (idx, fty2) -> + let save = f.f_temp in + let t = alloc_temp p f pos in + emit_default_value p f ~dst:t ~fty:fty2 ~pos d2; + f.f_cur_line <- pos.line; + put f (ins_abc op_setf dst (check_field_idx p f pos idx) t); + f.f_temp <- save) + | None -> ()) + ci.Types.fields); + f.f_temp <- outer) | [ Token.Int n ] -> put f (ins_abx op_loadk dst (check_bx p f pos "constant" (const_int p n))) | [ Token.Dash; Token.Int n ] -> put f (ins_abx op_loadk dst (check_bx p f pos "constant" (const_int p (-n)))) diff --git a/compiler/src/parser.ml b/compiler/src/parser.ml index 1368220..f8abede 100644 --- a/compiler/src/parser.ml +++ b/compiler/src/parser.ml @@ -1843,10 +1843,19 @@ let params_bound (base : StringSet.t) (params : Ast.param list) : StringSet.t = let const_map (consts : Ast.const_decl list) : Ast.expr StringMap.t = List.fold_left (fun acc (c : Ast.const_decl) -> StringMap.add c.Ast.name c.Ast.value acc) StringMap.empty consts -let subst_consts (prog : Ast.program) : Ast.program = - let top_consts = - const_map (List.filter_map (function Ast.Const c -> Some c | _ -> None) prog.Ast.decls) - in +(* Every top-level `const` a program declares, for a caller that needs to + substitute one file's constants into ANOTHER file of the same module — + every file in a directory is unconditionally visible to every other + (haxe-parity Task 1's discovery contract), so a `const CHUNK = 65536` in + one file is in scope in its neighbours. *) +let top_level_consts (prog : Ast.program) : Ast.expr StringMap.t = + const_map (List.filter_map (function Ast.Const c -> Some c | _ -> None) prog.Ast.decls) + +(* [extra] holds constants declared elsewhere (a sibling file's); this + program's own top-level ones win on a name collision, exactly as a + class-level const outranks a top-level one below. *) +let subst_consts ?(extra = StringMap.empty) (prog : Ast.program) : Ast.program = + let top_consts = StringMap.fold StringMap.add (top_level_consts prog) extra in let decls' = List.map (function