feat(compiler): richer field defaults + cross-file consts
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<Text, CronWatch> = {}` 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) <noreply@anthropic.com>
This commit is contained in:
parent
fb9d60fc77
commit
f59340a1e3
3 changed files with 78 additions and 12 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue