v / vlib / v2 / ast / flat.v
3096 lines · 2945 sloc · 100.45 KB
Raw
1// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module ast
5
6import os
7import v2.token
8
9// FlatAst is a contiguous, index-based AST representation. Every node lives
10// in a single `nodes[]` table; relationships are encoded via `edges[]` ranges
11// instead of heap-allocated sumtype payloads. The schema is designed to be
12// LOSSLESS: every field of every legacy AST variant is preserved either as a
13// kind-specific bit/enum in the cell, or as a child node reachable through
14// edges. token.Pos is stored inline (8 bytes) per node to preserve diagnostics
15// without an extra interning layer.
16
17pub type FlatNodeId = int
18
19pub const invalid_flat_node_id = FlatNodeId(-1)
20
21// flag bit positions used by FlatNode.flags. Bits are reused per variant;
22// see add_* helpers below for the exact mapping.
23pub const flag_is_public = u8(1) << 0
24pub const flag_is_mut = u8(1) << 1
25pub const flag_is_method = u8(1) << 2
26pub const flag_is_static = u8(1) << 3
27pub const flag_is_union = u8(1) << 4
28pub const flag_is_gated = u8(1) << 5
29pub const flag_is_aliased = u8(1) << 6 // ImportStmt
30pub const flag_defer_func = u8(1) << 7 // DeferStmt.mode == .function
31pub const flag_field_is_module_mut = u8(1) << 2 // FieldDecl (reuse method bit)
32pub const flag_field_is_interface_method = u8(1) << 3 // FieldDecl (reuse static bit)
33pub const flag_is_count = u8(1) << 6 // SqlExpr (reuse aliased bit)
34pub const flag_is_create = u8(1) << 7 // SqlExpr (reuse defer bit)
35
36// FlatNodeKind is a dense tag for every stored AST node variant.
37pub enum FlatNodeKind {
38 file
39 stmt_asm
40 stmt_assert
41 stmt_assign
42 stmt_block
43 stmt_comptime
44 stmt_const_decl
45 stmt_defer
46 stmt_directive
47 stmt_empty
48 stmt_enum_decl
49 stmt_expr
50 stmt_flow_control
51 stmt_fn_decl
52 stmt_for_in
53 stmt_for
54 stmt_global_decl
55 stmt_import
56 stmt_interface_decl
57 stmt_label
58 stmt_module
59 stmt_return
60 stmt_struct_decl
61 stmt_type_decl
62 stmt_attributes
63 expr_array_init
64 expr_as_cast
65 expr_assoc
66 expr_basic_literal
67 expr_call
68 expr_call_or_cast
69 expr_cast
70 expr_comptime
71 expr_empty
72 expr_fn_literal
73 expr_generic_arg_or_index
74 expr_generic_args
75 expr_ident
76 expr_if
77 expr_if_guard
78 expr_index
79 expr_infix
80 expr_init
81 expr_keyword
82 expr_keyword_operator
83 expr_lambda
84 expr_lifetime
85 expr_lock
86 expr_map_init
87 expr_match
88 expr_modifier
89 expr_or
90 expr_paren
91 expr_postfix
92 expr_prefix
93 expr_range
94 expr_select
95 expr_selector
96 expr_sql
97 expr_string_inter
98 expr_string
99 expr_tuple
100 expr_unsafe
101 typ_anon_struct
102 typ_array_fixed
103 typ_array
104 typ_channel
105 typ_fn
106 typ_generic
107 typ_map
108 typ_nil
109 typ_none
110 typ_option
111 typ_pointer
112 typ_result
113 typ_thread
114 typ_tuple
115 aux_attribute
116 aux_field_init
117 aux_field_decl
118 aux_parameter
119 aux_match_branch
120 aux_string_inter
121 aux_list // ordered list grouping: edges are the items
122 aux_string // single interned string carrier (used where a node needs an extra string slot)
123 aux_int // single integer carrier (used where a node needs an extra int slot)
124}
125
126// FlatEdge represents a parent->child relationship.
127pub struct FlatEdge {
128pub:
129 child_id FlatNodeId
130}
131
132// FlatNode is the compiler-grade flat cell.
133// 28 bytes of payload, 32-byte aligned. Carries inline pos for diagnostics
134// and four side-channel slots (flags, aux, name_id, extra) for variant data.
135pub struct FlatNode {
136pub mut:
137 kind FlatNodeKind // 1 byte: variant tag
138 flags u8 // 1 byte: bit-packed booleans (see flag_* consts)
139 aux u16 // 2 bytes: token.Token or sub-kind enum (variant-specific)
140 name_id int // 4 bytes: primary interned string id (-1 if none)
141 extra int // 4 bytes: secondary slot (interned string id, packed ints, or list boundary)
142 pos token.Pos // 8 bytes: inline source position
143 first_edge int // 4 bytes: start offset into FlatAst.edges
144 edge_count int // 4 bytes: number of child edges
145}
146
147// FlatFile maps a source file to its flat root node and original selector map.
148pub struct FlatFile {
149pub:
150 file_id FlatNodeId
151 name_idx int
152 mod_idx int
153 selector_names map[int]string
154}
155
156// FlatAst is a contiguous AST graph representation.
157pub struct FlatAst {
158pub mut:
159 files []FlatFile
160 nodes []FlatNode
161 edges []FlatEdge
162 strings []string
163}
164
165// FlatAstStats captures high-level memory and shape metrics.
166pub struct FlatAstStats {
167pub:
168 file_roots int
169 nodes int
170 edges int
171 strings int
172 string_bytes u64
173 bytes_estimate u64
174}
175
176// LegacyAstStats captures estimated dynamic memory and node shape for the existing AST.
177pub struct LegacyAstStats {
178pub mut:
179 files int
180 expr_nodes int
181 stmt_nodes int
182 type_nodes int
183 aux_nodes int
184 node_bytes u64
185 array_bytes u64
186 string_entries int
187 string_bytes u64
188 // allocs counts distinct heap allocations the GC must track: every
189 // sumtype variant payload, every dynamic array (spine), and every
190 // string buffer. Flat AST collapses these to a handful of arenas.
191 allocs u64
192 bytes_estimate u64
193}
194
195// flatten_files converts recursive v2 AST files into a flat, index-based graph.
196pub fn flatten_files(files []File) FlatAst {
197 mut total_bytes := i64(0)
198 for file in files {
199 if file.name == '' {
200 continue
201 }
202 total_bytes += os.file_size(file.name)
203 }
204 nodes_cap, edges_cap, strings_cap := arena_caps_for_bytes(total_bytes)
205 mut b := new_flat_builder_with_capacity(nodes_cap, edges_cap, strings_cap)
206 for file in files {
207 b.append_file(file)
208 }
209 return b.flat
210}
211
212// arena_caps_for_bytes returns (nodes_cap, edges_cap, strings_cap) sized from
213// total source-byte estimate. Calibrated against the v2 self-host parse:
214// ~150 nodes/KB, ~170 edges/KB, ~5 unique strings/KB. Floors match the
215// default seed caps so tiny inputs don't waste arena space.
216pub fn arena_caps_for_bytes(total_bytes i64) (int, int, int) {
217 kb := int(total_bytes / 1024) + 1
218 mut nodes_cap := 150 * kb
219 mut edges_cap := 170 * kb
220 mut strings_cap := 5 * kb
221 if nodes_cap < 1024 {
222 nodes_cap = 1024
223 }
224 if edges_cap < 2048 {
225 edges_cap = 2048
226 }
227 if strings_cap < 512 {
228 strings_cap = 512
229 }
230 return nodes_cap, edges_cap, strings_cap
231}
232
233// legacy_ast_stats estimates dynamic memory and node counts for the recursive AST.
234pub fn legacy_ast_stats(files []File) LegacyAstStats {
235 mut w := LegacyAstWalker{}
236 w.walk_files(files)
237 return w.stats
238}
239
240// count_legacy_nodes returns the total legacy node count used by the walker.
241pub fn count_legacy_nodes(files []File) int {
242 stats := legacy_ast_stats(files)
243 return stats.files + stats.expr_nodes + stats.stmt_nodes + stats.type_nodes + stats.aux_nodes
244}
245
246// stats returns aggregate shape and estimated memory usage for FlatAst.
247pub fn (flat &FlatAst) stats() FlatAstStats {
248 mut string_bytes := u64(0)
249 for s in flat.strings {
250 string_bytes += u64(s.len)
251 }
252 mut bytes := u64(flat.files.len) * u64(sizeof(FlatFile))
253 bytes += u64(flat.nodes.len) * u64(sizeof(FlatNode))
254 bytes += u64(flat.edges.len) * u64(sizeof(FlatEdge))
255 bytes += u64(flat.strings.len) * u64(sizeof(string))
256 bytes += string_bytes
257 return FlatAstStats{
258 file_roots: flat.files.len
259 nodes: flat.nodes.len
260 edges: flat.edges.len
261 strings: flat.strings.len
262 string_bytes: string_bytes
263 bytes_estimate: bytes
264 }
265}
266
267// count_nodes_by_kind returns a tally of how many nodes exist of each kind.
268// Useful for spotting structural overhead (e.g., aux_list wrappers).
269pub fn (flat &FlatAst) count_nodes_by_kind() map[string]int {
270 mut hist := map[string]int{}
271 for n in flat.nodes {
272 hist[n.kind.str()]++
273 }
274 return hist
275}
276
277// count_reachable_nodes traverses from file roots and counts unique reachable nodes.
278pub fn (flat &FlatAst) count_reachable_nodes() int {
279 if flat.nodes.len == 0 || flat.files.len == 0 {
280 return 0
281 }
282 mut seen := []bool{len: flat.nodes.len}
283 mut stack := []int{cap: flat.files.len}
284 for file in flat.files {
285 stack << file.file_id
286 }
287 mut count := 0
288 for stack.len > 0 {
289 node_id := stack.pop()
290 if node_id < 0 || node_id >= flat.nodes.len {
291 continue
292 }
293 if seen[node_id] {
294 continue
295 }
296 seen[node_id] = true
297 count++
298 node := flat.nodes[node_id]
299 for i in 0 .. node.edge_count {
300 edge := flat.edges[node.first_edge + i]
301 stack << edge.child_id
302 }
303 }
304 return count
305}
306
307// node_type_name returns the short type-name tag associated with a flat node.
308pub fn (flat &FlatAst) node_type_name(node_id FlatNodeId) string {
309 if node_id < 0 || node_id >= flat.nodes.len {
310 return ''
311 }
312 return flat.nodes[node_id].kind.str()
313}
314
315// child_at returns the i-th child of `parent` (0-indexed), or invalid_flat_node_id.
316@[inline]
317pub fn (flat &FlatAst) child_at(parent FlatNodeId, i int) FlatNodeId {
318 if parent < 0 || parent >= flat.nodes.len {
319 return invalid_flat_node_id
320 }
321 node := flat.nodes[parent]
322 if i < 0 || i >= node.edge_count {
323 return invalid_flat_node_id
324 }
325 return flat.edges[node.first_edge + i].child_id
326}
327
328// string_at returns the interned string at `idx`, or empty when out of range.
329@[inline]
330pub fn (flat &FlatAst) string_at(idx int) string {
331 if idx < 0 || idx >= flat.strings.len {
332 return ''
333 }
334 return flat.strings[idx]
335}
336
337// FlatBuilder is the incremental builder behind FlatAst. It is public so
338// front-ends (parser, type checker, ...) can append nodes directly without
339// going through the legacy recursive AST. The legacy converters (add_expr,
340// add_stmt, ...) remain internal to this module and are exposed via the
341// `append_file(File)` shim used during the Phase 2 transition.
342pub struct FlatBuilder {
343pub mut:
344 flat FlatAst
345mut:
346 string_ids map[string]int
347 empty_list_id FlatNodeId = invalid_flat_node_id
348 empty_expr_id FlatNodeId = invalid_flat_node_id
349 empty_stmt_id FlatNodeId = invalid_flat_node_id
350}
351
352// new_flat_builder returns a fresh FlatBuilder seeded with reasonable arena
353// capacities. Callers may resize after measuring (b.flat.nodes.grow_cap, ...).
354pub fn new_flat_builder() FlatBuilder {
355 return new_flat_builder_with_capacity(1024, 2048, 512)
356}
357
358// new_flat_builder_with_capacity lets callers pre-size the three arenas up
359// front when an estimate is available (e.g. derived from total source size).
360// Empirically the v2 self-host produces ~150 nodes/KB, ~165 edges/KB and ~5
361// unique strings/KB; supplying tight caps avoids the geometric realloc churn
362// that dominates the streaming parse path.
363pub fn new_flat_builder_with_capacity(nodes_cap int, edges_cap int, strings_cap int) FlatBuilder {
364 return FlatBuilder{
365 flat: FlatAst{
366 files: []FlatFile{}
367 nodes: []FlatNode{cap: nodes_cap}
368 edges: []FlatEdge{cap: edges_cap}
369 strings: []string{cap: strings_cap}
370 }
371 string_ids: map[string]int{}
372 }
373}
374
375// take_flat moves the built flat graph out and releases transient builder-only
376// indexes. The returned FlatAst keeps owning its arena arrays.
377pub fn (mut b FlatBuilder) take_flat() FlatAst {
378 flat := b.flat
379 unsafe {
380 b.string_ids.free()
381 }
382 b.flat = FlatAst{}
383 b.string_ids = map[string]int{}
384 b.empty_list_id = invalid_flat_node_id
385 b.empty_expr_id = invalid_flat_node_id
386 b.empty_stmt_id = invalid_flat_node_id
387 return flat
388}
389
390// append_file converts one legacy File into flat nodes and registers it as a
391// root. Designed for streaming use: the caller can drop the legacy `file`
392// immediately after this returns, capping peak memory at ~one file's legacy
393// AST plus the cumulative flat representation.
394pub fn (mut b FlatBuilder) append_file(file File) FlatNodeId {
395 file_id := b.add_file(file)
396 b.flat.files << FlatFile{
397 file_id: file_id
398 name_idx: b.intern(file.name)
399 mod_idx: b.intern(file.mod)
400 selector_names: file.selector_names
401 }
402 return file_id
403}
404
405// append_file_with_stmt_ids registers a file root whose stmt list is built
406// from FlatNodeIds that were already emitted into this builder by a previous
407// per-stmt pass (e.g. the transformer's flat-write path). Attributes and
408// imports are still consumed from the legacy `file` because they are not
409// subject to transformer rewrites; emitting them here keeps the file shape
410// bit-equal to `add_file`'s output.
411pub fn (mut b FlatBuilder) append_file_with_stmt_ids(file File, stmt_ids []FlatNodeId) FlatNodeId {
412 mut edges := []FlatEdge{}
413 edges = b.push_edge(edges, b.make_list_attribute(file.attributes))
414 edges = b.push_edge(edges, b.make_list_imports(file.imports))
415 edges = b.push_edge(edges, b.make_list_from_stmt_ids(stmt_ids))
416 file_id := b.emit(.file, token.Pos{}, b.intern(file.name), b.intern(file.mod), 0, 0, edges)
417 b.flat.files << FlatFile{
418 file_id: file_id
419 name_idx: b.intern(file.name)
420 mod_idx: b.intern(file.mod)
421 selector_names: file.selector_names
422 }
423 return file_id
424}
425
426// append_file_with_flat_attrs_and_stmt_ids registers a file root whose
427// attributes and stmt list were already emitted into this builder. Imports are
428// still accepted as legacy values because import handling is not transformed.
429pub fn (mut b FlatBuilder) append_file_with_flat_attrs_and_stmt_ids(name string, mod string, selector_names map[int]string, attrs_id FlatNodeId, imports []ImportStmt, stmt_ids []FlatNodeId) FlatNodeId {
430 mut edges := []FlatEdge{}
431 edges = b.push_edge(edges, attrs_id)
432 edges = b.push_edge(edges, b.make_list_imports(imports))
433 edges = b.push_edge(edges, b.make_list_from_stmt_ids(stmt_ids))
434 file_id := b.emit(.file, token.Pos{}, b.intern(name), b.intern(mod), 0, 0, edges)
435 b.flat.files << FlatFile{
436 file_id: file_id
437 name_idx: b.intern(name)
438 mod_idx: b.intern(mod)
439 selector_names: selector_names
440 }
441 return file_id
442}
443
444// append_file_with_flat_lists_and_stmt_ids registers a file root whose
445// attributes, imports, and stmt list were already emitted into this builder.
446pub fn (mut b FlatBuilder) append_file_with_flat_lists_and_stmt_ids(name string, mod string, selector_names map[int]string, attrs_id FlatNodeId, imports_id FlatNodeId, stmt_ids []FlatNodeId) FlatNodeId {
447 mut edges := []FlatEdge{}
448 edges = b.push_edge(edges, attrs_id)
449 edges = b.push_edge(edges, imports_id)
450 edges = b.push_edge(edges, b.make_list_from_stmt_ids(stmt_ids))
451 file_id := b.emit(.file, token.Pos{}, b.intern(name), b.intern(mod), 0, 0, edges)
452 b.flat.files << FlatFile{
453 file_id: file_id
454 name_idx: b.intern(name)
455 mod_idx: b.intern(mod)
456 selector_names: selector_names
457 }
458 return file_id
459}
460
461// append_flat copies an entire `src` FlatAst (nodes, edges, strings, file roots)
462// into this builder, relocating every node id / edge target and re-interning
463// every string so the merged result is structurally identical to `src` decoded
464// standalone. Returns the node-id offset applied to all of `src`'s nodes (a src
465// node id `x` maps to merged id `x + offset`).
466//
467// This is the merge primitive behind the flat parallel transform: each worker
468// emits into its own FlatBuilder, then the main thread concatenates the
469// workers' outputs via append_flat (replacing the legacy per-worker `ast.File`
470// rehydrate-then-flatten). Relocation rules — verified against the canonical
471// decoder (flat_reader.v):
472// - edge.child_id : + node_offset (invalid_flat_node_id stays invalid)
473// - node.first_edge: + edge_offset
474// - node.name_id : re-interned via the string remap (-1 stays -1)
475// - node.extra : re-interned ONLY for the three kinds that store a string
476// id there (file mod, stmt_directive value, stmt_import
477// alias); every other kind packs ints/counts/flags/list-
478// boundaries in extra (lhs_len, cap_len, keys_len, width/
479// precision, aux_int value, ...) → copied verbatim
480// (relocation-invariant)
481// - node.aux / pos : copied verbatim (aux is a token/sub-kind enum; pos.id is
482// globally unique across the merged inputs, and
483// selector_names is keyed by pos.id so it needs no remap)
484// - file.file_id : + node_offset; name_idx/mod_idx re-interned;
485// selector_names copied (cloned for ownership)
486pub fn (mut b FlatBuilder) append_flat(src &FlatAst) int {
487 node_offset := b.flat.nodes.len
488 edge_offset := b.flat.edges.len
489 // Remap src string ids -> dst string ids (dedups into b's intern table).
490 mut string_remap := []int{len: src.strings.len}
491 for i, s in src.strings {
492 string_remap[i] = b.intern(s)
493 }
494 // Relocate + append edges.
495 b.flat.edges.grow_cap(src.edges.len)
496 for e in src.edges {
497 child := if e.child_id >= 0 { e.child_id + node_offset } else { e.child_id }
498 b.flat.edges << FlatEdge{
499 child_id: child
500 }
501 }
502 // Relocate + append nodes.
503 b.flat.nodes.grow_cap(src.nodes.len)
504 for n in src.nodes {
505 mut nn := n
506 nn.name_id = if n.name_id >= 0 { string_remap[n.name_id] } else { n.name_id }
507 if n.extra >= 0 && (n.kind == .file || n.kind == .stmt_directive || n.kind == .stmt_import) {
508 nn.extra = string_remap[n.extra]
509 }
510 nn.first_edge = n.first_edge + edge_offset
511 b.flat.nodes << nn
512 }
513 // Relocate + append file roots.
514 for ff in src.files {
515 b.flat.files << FlatFile{
516 file_id: ff.file_id + node_offset
517 name_idx: if ff.name_idx >= 0 { string_remap[ff.name_idx] } else { ff.name_idx }
518 mod_idx: if ff.mod_idx >= 0 { string_remap[ff.mod_idx] } else { ff.mod_idx }
519 selector_names: ff.selector_names.clone()
520 }
521 }
522 return node_offset
523}
524
525// copy_subtree_from copies the subtree rooted at `root` from `src` into this
526// builder, re-interning strings and rebuilding child edges recursively.
527pub fn (mut b FlatBuilder) copy_subtree_from(src &FlatAst, root FlatNodeId) FlatNodeId {
528 if root < 0 || root >= src.nodes.len {
529 return invalid_flat_node_id
530 }
531 n := src.nodes[root]
532 mut edges := []FlatEdge{cap: n.edge_count}
533 for i in 0 .. n.edge_count {
534 child := src.child_at(root, i)
535 copied_child := if child >= 0 {
536 b.copy_subtree_from(src, child)
537 } else {
538 child
539 }
540 edges << FlatEdge{
541 child_id: copied_child
542 }
543 }
544 name_id := b.copy_string_id_from(src, n.name_id)
545 extra := if n.extra >= 0
546 && (n.kind == .file || n.kind == .stmt_directive || n.kind == .stmt_import) {
547 b.copy_string_id_from(src, n.extra)
548 } else {
549 n.extra
550 }
551 return b.emit(n.kind, n.pos, name_id, extra, n.aux, n.flags, edges)
552}
553
554fn (mut b FlatBuilder) copy_string_id_from(src &FlatAst, id int) int {
555 if id < 0 || id >= src.strings.len {
556 return id
557 }
558 return b.intern(src.strings[id])
559}
560
561// emit_stmt is the public wrapper around the legacy stmt-to-flat conversion.
562// Future sessions of the transformer-writes-flat port reach for this when a
563// stmt variant still falls back to legacy emission; the long-term goal is for
564// transform_stmt_to_flat (in v2.transformer) to stop calling it as each stmt
565// arm gets a direct-emit replacement.
566pub fn (mut b FlatBuilder) emit_stmt(stmt Stmt) FlatNodeId {
567 return b.add_stmt(stmt)
568}
569
570// append_file_stmts re-emits the file root for `flat.files[file_idx]` with
571// `extra_stmt_ids` appended to the existing stmts list. The file's existing
572// attrs and imports edges are reused as-is (no re-emit) — only the stmts
573// list (file root edge 2) is rebuilt. Updates `flat.files[file_idx]` in place
574// to point at the new file root and returns its FlatNodeId.
575//
576// Append-only: the old file root remains in `flat.nodes` as garbage (never
577// reachable from `flat.files`). Callers must not retain the previous file_id.
578//
579// Designed for the post-pass mutation pattern (s144..s149 `_parts` extracts).
580// `inject_embed_file_helper`-equivalent + `inject_test_main`-equivalent +
581// `generated_fns_parts` core/module/user splices all need to append a list
582// of pre-emitted stmt nodes to a specific file's stmt list. The transformer's
583// upcoming `post_pass_to_flat` wires each of those into one call of this
584// helper.
585//
586// Bit-equal to `append_file(file_with_extended_stmts)` w.r.t. the
587// `signature()` walk: the file root's edge list (attrs_list_id,
588// imports_list_id, new_stmts_list_id) names the same logical children, and
589// `signature()` only follows reachable edges from the file root.
590pub fn (mut b FlatBuilder) append_file_stmts(file_idx int, extra_stmt_ids []FlatNodeId) FlatNodeId {
591 if file_idx < 0 || file_idx >= b.flat.files.len {
592 return invalid_flat_node_id
593 }
594 old_file_id := b.flat.files[file_idx].file_id
595 if extra_stmt_ids.len == 0 {
596 return old_file_id
597 }
598 name_idx := b.flat.files[file_idx].name_idx
599 mod_idx := b.flat.files[file_idx].mod_idx
600 old_attrs_list_id := b.flat.child_at(old_file_id, 0)
601 old_imports_list_id := b.flat.child_at(old_file_id, 1)
602 old_stmts_list_id := b.flat.child_at(old_file_id, 2)
603 old_stmts_count := if old_stmts_list_id < 0 || old_stmts_list_id >= b.flat.nodes.len {
604 0
605 } else {
606 b.flat.nodes[old_stmts_list_id].edge_count
607 }
608 mut stmt_ids := []FlatNodeId{cap: old_stmts_count + extra_stmt_ids.len}
609 for i in 0 .. old_stmts_count {
610 stmt_ids << b.flat.child_at(old_stmts_list_id, i)
611 }
612 for id in extra_stmt_ids {
613 stmt_ids << id
614 }
615 new_stmts_list_id := b.make_list_from_stmt_ids(stmt_ids)
616 mut edges := []FlatEdge{cap: 3}
617 edges = b.push_edge(edges, old_attrs_list_id)
618 edges = b.push_edge(edges, old_imports_list_id)
619 edges = b.push_edge(edges, new_stmts_list_id)
620 new_file_id := b.emit(.file, token.Pos{}, name_idx, mod_idx, 0, 0, edges)
621 b.flat.files[file_idx] = FlatFile{
622 file_id: new_file_id
623 name_idx: name_idx
624 mod_idx: mod_idx
625 selector_names: b.flat.files[file_idx].selector_names
626 }
627 return new_file_id
628}
629
630// prepend_file_stmts re-emits the file root for `flat.files[file_idx]` with
631// `prepended_stmt_ids` placed BEFORE the existing stmts list. The file's
632// existing attrs and imports edges are reused as-is (no re-emit) — only the
633// stmts list (file root edge 2) is rebuilt. Updates `flat.files[file_idx]`
634// in place to point at the new file root and returns its FlatNodeId.
635//
636// Sibling of `append_file_stmts` — same shape, opposite order. Designed for
637// `inject_live_reload`'s file-level prepend (c_decls + global_decls go BEFORE
638// the existing file stmts).
639//
640// Append-only: the old file root remains in `flat.nodes` as garbage (never
641// reachable from `flat.files`). Callers must not retain the previous file_id.
642pub fn (mut b FlatBuilder) prepend_file_stmts(file_idx int, prepended_stmt_ids []FlatNodeId) FlatNodeId {
643 if file_idx < 0 || file_idx >= b.flat.files.len {
644 return invalid_flat_node_id
645 }
646 old_file_id := b.flat.files[file_idx].file_id
647 if prepended_stmt_ids.len == 0 {
648 return old_file_id
649 }
650 name_idx := b.flat.files[file_idx].name_idx
651 mod_idx := b.flat.files[file_idx].mod_idx
652 old_attrs_list_id := b.flat.child_at(old_file_id, 0)
653 old_imports_list_id := b.flat.child_at(old_file_id, 1)
654 old_stmts_list_id := b.flat.child_at(old_file_id, 2)
655 old_stmts_count := if old_stmts_list_id < 0 || old_stmts_list_id >= b.flat.nodes.len {
656 0
657 } else {
658 b.flat.nodes[old_stmts_list_id].edge_count
659 }
660 mut stmt_ids := []FlatNodeId{cap: old_stmts_count + prepended_stmt_ids.len}
661 for id in prepended_stmt_ids {
662 stmt_ids << id
663 }
664 for i in 0 .. old_stmts_count {
665 stmt_ids << b.flat.child_at(old_stmts_list_id, i)
666 }
667 new_stmts_list_id := b.make_list_from_stmt_ids(stmt_ids)
668 mut edges := []FlatEdge{cap: 3}
669 edges = b.push_edge(edges, old_attrs_list_id)
670 edges = b.push_edge(edges, old_imports_list_id)
671 edges = b.push_edge(edges, new_stmts_list_id)
672 new_file_id := b.emit(.file, token.Pos{}, name_idx, mod_idx, 0, 0, edges)
673 b.flat.files[file_idx] = FlatFile{
674 file_id: new_file_id
675 name_idx: name_idx
676 mod_idx: mod_idx
677 selector_names: b.flat.files[file_idx].selector_names
678 }
679 return new_file_id
680}
681
682// replace_file_stmt re-emits the file root for `flat.files[file_idx]` with
683// the stmt at `stmt_idx` replaced by `new_stmt_id`. The file's existing
684// attrs and imports edges are reused as-is (no re-emit); only the stmts
685// list (file root edge 2) is rebuilt with the substitution applied.
686// Updates `flat.files[file_idx]` in place to point at the new file root and
687// returns its FlatNodeId.
688//
689// Companion primitive to `prepend_to_fn_body`: that primitive emits a NEW
690// fn_decl_id (the old fn is unreachable garbage) but cannot rewire the
691// surrounding file. `replace_file_stmt` is the cross-stitching call that
692// puts the new id back into the file's stmts list. Together they support
693// the prepend-style post_pass mutations (`inject_main_runtime_const_init_calls`
694// is the first user, s155).
695//
696// Append-only: the old file root remains in `flat.nodes` as garbage (never
697// reachable from `flat.files`). Callers must not retain the previous file_id.
698pub fn (mut b FlatBuilder) replace_file_stmt(file_idx int, stmt_idx int, new_stmt_id FlatNodeId) FlatNodeId {
699 if file_idx < 0 || file_idx >= b.flat.files.len {
700 return invalid_flat_node_id
701 }
702 if new_stmt_id < 0 || new_stmt_id >= b.flat.nodes.len {
703 return invalid_flat_node_id
704 }
705 old_file_id := b.flat.files[file_idx].file_id
706 old_attrs_list_id := b.flat.child_at(old_file_id, 0)
707 old_imports_list_id := b.flat.child_at(old_file_id, 1)
708 old_stmts_list_id := b.flat.child_at(old_file_id, 2)
709 old_stmts_count := if old_stmts_list_id < 0 || old_stmts_list_id >= b.flat.nodes.len {
710 0
711 } else {
712 b.flat.nodes[old_stmts_list_id].edge_count
713 }
714 if stmt_idx < 0 || stmt_idx >= old_stmts_count {
715 return invalid_flat_node_id
716 }
717 mut stmt_ids := []FlatNodeId{cap: old_stmts_count}
718 for i in 0 .. old_stmts_count {
719 if i == stmt_idx {
720 stmt_ids << new_stmt_id
721 } else {
722 stmt_ids << b.flat.child_at(old_stmts_list_id, i)
723 }
724 }
725 new_stmts_list_id := b.make_list_from_stmt_ids(stmt_ids)
726 mut edges := []FlatEdge{cap: 3}
727 edges = b.push_edge(edges, old_attrs_list_id)
728 edges = b.push_edge(edges, old_imports_list_id)
729 edges = b.push_edge(edges, new_stmts_list_id)
730 name_idx := b.flat.files[file_idx].name_idx
731 mod_idx := b.flat.files[file_idx].mod_idx
732 new_file_id := b.emit(.file, token.Pos{}, name_idx, mod_idx, 0, 0, edges)
733 b.flat.files[file_idx] = FlatFile{
734 file_id: new_file_id
735 name_idx: name_idx
736 mod_idx: mod_idx
737 selector_names: b.flat.files[file_idx].selector_names
738 }
739 return new_file_id
740}
741
742// prepend_to_fn_body re-emits a stmt_fn_decl with `extra_stmt_ids` prepended
743// to its body. The original FnDecl's receiver/typ/attrs edges (0/1/2) and its
744// metadata (name_id, extra, aux, flags, pos) are reused verbatim; only the
745// stmts list (edge 3) is rebuilt as `extras + old_children`. Returns the
746// FlatNodeId of the NEW stmt_fn_decl — the old one remains in `flat.nodes`
747// as garbage. Callers must thread the returned id back into wherever the
748// old FnDecl was referenced (typically a file's stmts list).
749//
750// Designed for the prepend-style post_pass mutations:
751// - `inject_main_runtime_const_init_calls` (prepends `__v_init_consts_*()`
752// calls to the top of `main`)
753// - `inject_live_reload` (prepends a reload-check to nested ForStmt bodies;
754// the same body-rebuild shape applies to non-FnDecl callsites too, but
755// the FnDecl case is the immediate need so this primitive lands first).
756//
757// Bit-equal to `add_stmt(FnDecl{... stmts: extras_then_old})` w.r.t.
758// `signature()`: the new node has identical name_id/extra/aux/pos/flags and
759// edges = [old_receiver, old_typ, old_attrs, new_stmts_list].
760pub fn (mut b FlatBuilder) prepend_to_fn_body(fn_decl_id FlatNodeId, extra_stmt_ids []FlatNodeId) FlatNodeId {
761 if fn_decl_id < 0 || fn_decl_id >= b.flat.nodes.len {
762 return invalid_flat_node_id
763 }
764 old_node := b.flat.nodes[fn_decl_id]
765 if old_node.kind != .stmt_fn_decl {
766 return invalid_flat_node_id
767 }
768 if extra_stmt_ids.len == 0 {
769 return fn_decl_id
770 }
771 old_receiver_id := b.flat.child_at(fn_decl_id, 0)
772 old_typ_id := b.flat.child_at(fn_decl_id, 1)
773 old_attrs_id := b.flat.child_at(fn_decl_id, 2)
774 old_stmts_list_id := b.flat.child_at(fn_decl_id, 3)
775 old_stmts_count := if old_stmts_list_id < 0 || old_stmts_list_id >= b.flat.nodes.len {
776 0
777 } else {
778 b.flat.nodes[old_stmts_list_id].edge_count
779 }
780 mut stmt_ids := []FlatNodeId{cap: extra_stmt_ids.len + old_stmts_count}
781 for id in extra_stmt_ids {
782 stmt_ids << id
783 }
784 for i in 0 .. old_stmts_count {
785 stmt_ids << b.flat.child_at(old_stmts_list_id, i)
786 }
787 new_stmts_list_id := b.make_list_from_stmt_ids(stmt_ids)
788 mut edges := []FlatEdge{cap: 4}
789 edges = b.push_edge(edges, old_receiver_id)
790 edges = b.push_edge(edges, old_typ_id)
791 edges = b.push_edge(edges, old_attrs_id)
792 edges = b.push_edge(edges, new_stmts_list_id)
793 return b.emit(.stmt_fn_decl, old_node.pos, old_node.name_id, old_node.extra, old_node.aux,
794 old_node.flags, edges)
795}
796
797// prepend_to_for_body re-emits a stmt_for with `extra_stmt_ids` prepended to
798// its body. The original ForStmt's init/cond/post edges (0/1/2) and pos are
799// reused verbatim; the body stmts (edges 3..edge_count) are rebuilt as
800// `extras + old_body_stmts`. Returns the FlatNodeId of the NEW stmt_for —
801// the old one remains in `flat.nodes` as garbage. Callers must thread the
802// returned id back into wherever the old ForStmt was referenced.
803//
804// Body-stmt encoding difference vs `prepend_to_fn_body`: ForStmt encodes
805// body stmts as INLINE edges starting at index 3 (no list child), whereas
806// FnDecl's body stmts live inside a `.aux_list` at edge 3. So `prepend_to_for_body`
807// rebuilds the for's edge list directly — no intermediate list node.
808//
809// Designed for `inject_live_reload`, which prepends a reload-check stmt to
810// the body of every ForStmt found in every function. Combined with
811// `prepend_to_fn_body` (s154) and `replace_file_stmt` (s155), this primitive
812// closes the cross-stitching chain needed for the recursive live-reload
813// splice: for each ForStmt change, rebuild the for, rebuild the enclosing
814// FnDecl, rewire the enclosing file's stmts list.
815pub fn (mut b FlatBuilder) prepend_to_for_body(for_stmt_id FlatNodeId, extra_stmt_ids []FlatNodeId) FlatNodeId {
816 if for_stmt_id < 0 || for_stmt_id >= b.flat.nodes.len {
817 return invalid_flat_node_id
818 }
819 old_node := b.flat.nodes[for_stmt_id]
820 if old_node.kind != .stmt_for {
821 return invalid_flat_node_id
822 }
823 if extra_stmt_ids.len == 0 {
824 return for_stmt_id
825 }
826 old_init_id := b.flat.child_at(for_stmt_id, 0)
827 old_cond_id := b.flat.child_at(for_stmt_id, 1)
828 old_post_id := b.flat.child_at(for_stmt_id, 2)
829 old_body_count := old_node.edge_count - 3
830 mut body_ids := []FlatNodeId{cap: extra_stmt_ids.len + old_body_count}
831 for id in extra_stmt_ids {
832 body_ids << id
833 }
834 for i in 0 .. old_body_count {
835 body_ids << b.flat.child_at(for_stmt_id, 3 + i)
836 }
837 return b.emit_for_stmt_by_ids(old_init_id, old_cond_id, old_post_id, body_ids)
838}
839
840// replace_fn_body_stmts re-emits a stmt_fn_decl with its body stmts list
841// fully REPLACED by `new_body_stmt_ids`. The original FnDecl's receiver/typ/
842// attrs edges (0/1/2) and metadata (name_id, extra, aux, flags, pos) are
843// reused verbatim; only the stmts list (edge 3) is rebuilt as a fresh
844// aux_list over `new_body_stmt_ids`. Returns the FlatNodeId of the NEW
845// stmt_fn_decl — the old one remains in `flat.nodes` as garbage. Callers
846// must thread the returned id back into wherever the old FnDecl was
847// referenced (typically a file's stmts list).
848//
849// Companion primitive to `prepend_to_fn_body` (s154). Where `prepend_to_fn_body`
850// adds extras to the head of the existing body, `replace_fn_body_stmts`
851// gives callers full control over the new body — used by `inject_live_reload`
852// which combines preamble prepend (main fn only) + per-ForStmt prepend
853// (every fn) + per-stmt call rewriting (every fn) into ONE new body. The
854// caller computes the new body stmt ids first (mixing freshly-emitted stmts
855// with ids returned by `prepend_to_for_body` and unchanged legacy stmt ids),
856// then hands them off here for a one-shot FnDecl rebuild.
857//
858// Bit-equal to `add_stmt(FnDecl{... stmts: new_body})` w.r.t. `signature()`:
859// the new node has identical name_id/extra/aux/pos/flags and edges =
860// [old_receiver, old_typ, old_attrs, new_stmts_list].
861pub fn (mut b FlatBuilder) replace_fn_body_stmts(fn_decl_id FlatNodeId, new_body_stmt_ids []FlatNodeId) FlatNodeId {
862 if fn_decl_id < 0 || fn_decl_id >= b.flat.nodes.len {
863 return invalid_flat_node_id
864 }
865 old_node := b.flat.nodes[fn_decl_id]
866 if old_node.kind != .stmt_fn_decl {
867 return invalid_flat_node_id
868 }
869 old_receiver_id := b.flat.child_at(fn_decl_id, 0)
870 old_typ_id := b.flat.child_at(fn_decl_id, 1)
871 old_attrs_id := b.flat.child_at(fn_decl_id, 2)
872 new_stmts_list_id := b.make_list_from_stmt_ids(new_body_stmt_ids)
873 mut edges := []FlatEdge{cap: 4}
874 edges = b.push_edge(edges, old_receiver_id)
875 edges = b.push_edge(edges, old_typ_id)
876 edges = b.push_edge(edges, old_attrs_id)
877 edges = b.push_edge(edges, new_stmts_list_id)
878 return b.emit(.stmt_fn_decl, old_node.pos, old_node.name_id, old_node.extra, old_node.aux,
879 old_node.flags, edges)
880}
881
882// emit_expr is the public wrapper around the legacy expr-to-flat conversion.
883// The flat-write port reaches for this when an expr variant still falls back
884// to legacy emission. Phase 4 sessions replace non-leaf arms in
885// transform_expr_to_flat one at a time with direct-emit logic that no longer
886// reaches for this helper.
887pub fn (mut b FlatBuilder) emit_expr(expr Expr) FlatNodeId {
888 return b.add_expr(expr)
889}
890
891// emit_attribute_list emits an aux_list of attribute flat nodes, bit-equal
892// to the encoding produced by add_stmt for stmts that carry attributes.
893// Empty inputs return the shared empty-list sentinel.
894pub fn (mut b FlatBuilder) emit_attribute_list(attrs []Attribute) FlatNodeId {
895 return b.make_list_attribute(attrs)
896}
897
898// emit_aux_list_from_ids builds an aux_list whose children are FlatNodeIds
899// already present in this builder. Empty inputs return the shared empty-list
900// sentinel.
901pub fn (mut b FlatBuilder) emit_aux_list_from_ids(ids []FlatNodeId) FlatNodeId {
902 if ids.len == 0 {
903 return b.get_empty_list()
904 }
905 mut edges := []FlatEdge{cap: ids.len}
906 for id in ids {
907 edges = b.push_edge(edges, id)
908 }
909 return b.emit_simple(.aux_list, token.Pos{}, edges)
910}
911
912// emit_field_decl_by_ids emits an aux_field_decl node from already-flat
913// child FlatNodeIds. Used by the flat-write port for stmts that carry
914// FieldDecls (GlobalDecl, StructDecl, ...) once their typ/value expressions
915// have been emitted directly. Mirrors the add_field_decl encoding exactly.
916pub fn (mut b FlatBuilder) emit_field_decl_by_ids(field FieldDecl, typ_id FlatNodeId, value_id FlatNodeId, attrs_id FlatNodeId) FlatNodeId {
917 mut edges := []FlatEdge{cap: 3}
918 edges = b.push_edge(edges, typ_id)
919 edges = b.push_edge(edges, value_id)
920 edges = b.push_edge(edges, attrs_id)
921 return b.emit(.aux_field_decl, token.Pos{}, b.intern(field.name), -1, 0,
922 field_decl_flags(field), edges)
923}
924
925// emit_global_decl_by_ids emits a stmt_global_decl node from already-flat
926// child FlatNodeIds (the attribute list and field-decl list). Mirrors the
927// add_stmt(GlobalDecl) encoding exactly, including the flag_is_public bit.
928pub fn (mut b FlatBuilder) emit_global_decl_by_ids(is_public bool, attrs_id FlatNodeId, fields_id FlatNodeId) FlatNodeId {
929 mut flags := u8(0)
930 if is_public {
931 flags |= flag_is_public
932 }
933 return b.emit_simple_with_flags(.stmt_global_decl, token.Pos{}, flags, [
934 FlatEdge{
935 child_id: attrs_id
936 },
937 FlatEdge{
938 child_id: fields_id
939 },
940 ])
941}
942
943// emit_field_init_by_id emits an aux_field_init node from an already-flat
944// value FlatNodeId. Used by the flat-write port for ConstDecl when the
945// field's value expression has been emitted directly. Mirrors the
946// add_field_init encoding exactly.
947pub fn (mut b FlatBuilder) emit_field_init_by_id(name string, value_id FlatNodeId) FlatNodeId {
948 return b.emit(.aux_field_init, token.Pos{}, b.intern(name), -1, 0, 0, [
949 FlatEdge{
950 child_id: value_id
951 },
952 ])
953}
954
955// emit_const_decl_by_ids emits a stmt_const_decl node from an already-flat
956// fields list FlatNodeId. Mirrors the add_stmt(ConstDecl) encoding exactly,
957// including the flag_is_public bit when `is_public` is true.
958pub fn (mut b FlatBuilder) emit_const_decl_by_ids(is_public bool, fields_id FlatNodeId) FlatNodeId {
959 mut flags := u8(0)
960 if is_public {
961 flags |= flag_is_public
962 }
963 return b.emit_simple_with_flags(.stmt_const_decl, token.Pos{}, flags, [
964 FlatEdge{
965 child_id: fields_id
966 },
967 ])
968}
969
970// emit_return_stmt_by_ids emits a stmt_return node from a slice of
971// already-flat returned-expression FlatNodeIds. Mirrors the
972// add_stmt(ReturnStmt) encoding exactly: pos is the zero `token.Pos{}` the
973// legacy add_stmt arm uses, edges are the returned exprs in order. Used by
974// the flat-write port to direct-emit the outer `ast.ReturnStmt` wrapper.
975pub fn (mut b FlatBuilder) emit_return_stmt_by_ids(expr_ids []FlatNodeId) FlatNodeId {
976 mut edges := []FlatEdge{cap: expr_ids.len}
977 for eid in expr_ids {
978 edges << FlatEdge{
979 child_id: eid
980 }
981 }
982 return b.emit_simple(.stmt_return, token.Pos{}, edges)
983}
984
985// emit_expr_stmt_by_id emits a stmt_expr node from an already-flat
986// FlatNodeId child. Mirrors the add_stmt(ExprStmt) encoding exactly: pos is
987// the zero `token.Pos{}` the legacy add_stmt arm uses, single edge to the
988// expression. Used by the flat-write port to direct-emit the
989// `ast.ExprStmt` wrapper at every expression-statement site.
990pub fn (mut b FlatBuilder) emit_expr_stmt_by_id(expr_id FlatNodeId) FlatNodeId {
991 return b.emit_simple(.stmt_expr, token.Pos{}, [
992 FlatEdge{
993 child_id: expr_id
994 },
995 ])
996}
997
998// emit_label_stmt_by_id emits a stmt_label node from an already-flat
999// FlatNodeId child stmt. Mirrors the add_stmt(LabelStmt) encoding exactly:
1000// pos is the zero `token.Pos{}` the legacy add_stmt arm uses, name is
1001// interned, single edge to the labelled stmt. Used by the flat-write port
1002// to direct-emit the `ast.LabelStmt` wrapper.
1003pub fn (mut b FlatBuilder) emit_label_stmt_by_id(name string, stmt_id FlatNodeId) FlatNodeId {
1004 return b.emit(.stmt_label, token.Pos{}, b.intern(name), -1, 0, 0, [
1005 FlatEdge{
1006 child_id: stmt_id
1007 },
1008 ])
1009}
1010
1011// emit_block_stmt_by_ids emits a stmt_block node from a slice of
1012// already-flat child stmt FlatNodeIds. Mirrors the add_stmt(BlockStmt)
1013// encoding exactly: pos is the zero `token.Pos{}` the legacy add_stmt arm
1014// uses, edges are the inner stmts in order. Used by the flat-write port to
1015// direct-emit the `ast.BlockStmt` wrapper.
1016pub fn (mut b FlatBuilder) emit_block_stmt_by_ids(stmt_ids []FlatNodeId) FlatNodeId {
1017 mut edges := []FlatEdge{cap: stmt_ids.len}
1018 for sid in stmt_ids {
1019 edges << FlatEdge{
1020 child_id: sid
1021 }
1022 }
1023 return b.emit_simple(.stmt_block, token.Pos{}, edges)
1024}
1025
1026// emit_assert_stmt_by_id emits a stmt_assert node from an already-flat child
1027// expr FlatNodeId. Mirrors the add_stmt(AssertStmt) encoding exactly: pos is
1028// the zero `token.Pos{}` the legacy add_stmt arm uses, edges are
1029// [expr_id, empty_expr_id]. The legacy fallback path in `transform_stmt`
1030// rebuilds `AssertStmt{expr: transformed}` without setting `extra`, so the
1031// extra slot is always the default `empty_expr` — emit that via the cached
1032// `add_expr(empty_expr)`. Used by the flat-write port to direct-emit the
1033// `ast.AssertStmt` fallback wrapper (most assert stmts are expanded in
1034// `transform_stmts` and never reach the dispatch arm).
1035pub fn (mut b FlatBuilder) emit_assert_stmt_by_id(expr_id FlatNodeId) FlatNodeId {
1036 extra_id := b.add_expr(empty_expr)
1037 return b.emit_simple(.stmt_assert, token.Pos{}, [
1038 FlatEdge{
1039 child_id: expr_id
1040 },
1041 FlatEdge{
1042 child_id: extra_id
1043 },
1044 ])
1045}
1046
1047// emit_for_stmt_by_ids emits a stmt_for node from already-flat init/post
1048// stmt FlatNodeIds, cond expr FlatNodeId, and body stmt FlatNodeIds.
1049// Mirrors the add_stmt(ForStmt) encoding exactly: pos = `token.Pos{}` the
1050// legacy arm uses, edges = [init, cond, post, body_stmts...].
1051pub fn (mut b FlatBuilder) emit_for_stmt_by_ids(init_id FlatNodeId, cond_id FlatNodeId, post_id FlatNodeId, stmt_ids []FlatNodeId) FlatNodeId {
1052 mut edges := []FlatEdge{cap: 3 + stmt_ids.len}
1053 edges << FlatEdge{
1054 child_id: init_id
1055 }
1056 edges << FlatEdge{
1057 child_id: cond_id
1058 }
1059 edges << FlatEdge{
1060 child_id: post_id
1061 }
1062 for sid in stmt_ids {
1063 edges << FlatEdge{
1064 child_id: sid
1065 }
1066 }
1067 return b.emit_simple(.stmt_for, token.Pos{}, edges)
1068}
1069
1070// emit_for_in_stmt_by_ids emits a stmt_for_in node from already-flat key,
1071// value, and iterable expression FlatNodeIds. Mirrors add_stmt(ForInStmt).
1072pub fn (mut b FlatBuilder) emit_for_in_stmt_by_ids(key_id FlatNodeId, value_id FlatNodeId, expr_id FlatNodeId) FlatNodeId {
1073 return b.emit_simple(.stmt_for_in, token.Pos{}, [
1074 FlatEdge{
1075 child_id: key_id
1076 },
1077 FlatEdge{
1078 child_id: value_id
1079 },
1080 FlatEdge{
1081 child_id: expr_id
1082 },
1083 ])
1084}
1085
1086// emit_flow_control_stmt emits a stmt_flow_control node. Mirrors
1087// add_stmt(FlowControlStmt): label in name_id, op in aux, no edges.
1088pub fn (mut b FlatBuilder) emit_flow_control_stmt(op token.Token, label string) FlatNodeId {
1089 return b.emit(.stmt_flow_control, token.Pos{}, b.intern(label), -1, u16(int(op)), 0, [])
1090}
1091
1092// emit_map_init_expr_by_ids emits an expr_map_init node from already-flat
1093// type FlatNodeId and parallel slices of key/value expression FlatNodeIds
1094// (with `key_ids.len == val_ids.len`). Mirrors the add_expr(MapInitExpr)
1095// encoding exactly: aux1=-1, aux2=keys.len, edges = [typ, keys..., vals...].
1096// The flat-write port for `transform_map_init_expr`'s identity branch
1097// (eval-backend map literal) uses this to skip the `ast.MapInitExpr`
1098// wrapper struct allocation.
1099pub fn (mut b FlatBuilder) emit_map_init_expr_by_ids(typ_id FlatNodeId, key_ids []FlatNodeId, val_ids []FlatNodeId, pos token.Pos) FlatNodeId {
1100 mut edges := []FlatEdge{cap: 1 + key_ids.len + val_ids.len}
1101 edges << FlatEdge{
1102 child_id: typ_id
1103 }
1104 for kid in key_ids {
1105 edges << FlatEdge{
1106 child_id: kid
1107 }
1108 }
1109 for vid in val_ids {
1110 edges << FlatEdge{
1111 child_id: vid
1112 }
1113 }
1114 return b.emit(.expr_map_init, pos, -1, key_ids.len, 0, 0, edges)
1115}
1116
1117// emit_if_expr_by_ids emits an expr_if node from already-flat cond, else,
1118// and stmt FlatNodeIds. Mirrors the add_expr(IfExpr) encoding exactly:
1119// edges = [cond, else_expr, stmts...]. The flat-write port for
1120// `transform_if_expr`'s identity branch (no value-position lowering: any
1121// `ast.IfExpr` returned by the helper — recursive normalisation, smartcast
1122// rewrite, default transformed_if rebuild) uses this to skip the
1123// `ast.IfExpr` wrapper struct allocation.
1124pub fn (mut b FlatBuilder) emit_if_expr_by_ids(cond_id FlatNodeId, else_id FlatNodeId, stmt_ids []FlatNodeId, pos token.Pos) FlatNodeId {
1125 mut edges := []FlatEdge{cap: 2 + stmt_ids.len}
1126 edges << FlatEdge{
1127 child_id: cond_id
1128 }
1129 edges << FlatEdge{
1130 child_id: else_id
1131 }
1132 for sid in stmt_ids {
1133 edges << FlatEdge{
1134 child_id: sid
1135 }
1136 }
1137 return b.emit_simple(.expr_if, pos, edges)
1138}
1139
1140// emit_call_expr_by_ids emits an expr_call node from already-flat lhs and
1141// args expression FlatNodeIds. Mirrors the add_expr(CallExpr) encoding
1142// exactly: edges = [lhs, args...]. The flat-write port for
1143// `transform_call_expr`'s many identity-shape rebuild branches (default
1144// fallback, generic-math inline result, smartcast method, etc.) uses this
1145// to skip the `ast.CallExpr` wrapper struct allocation.
1146pub fn (mut b FlatBuilder) emit_call_expr_by_ids(lhs_id FlatNodeId, arg_ids []FlatNodeId, pos token.Pos) FlatNodeId {
1147 mut edges := []FlatEdge{cap: 1 + arg_ids.len}
1148 edges << FlatEdge{
1149 child_id: lhs_id
1150 }
1151 for aid in arg_ids {
1152 edges << FlatEdge{
1153 child_id: aid
1154 }
1155 }
1156 return b.emit_simple(.expr_call, pos, edges)
1157}
1158
1159// emit_infix_expr_by_ids emits an expr_infix node from already-flat lhs and
1160// rhs expression FlatNodeIds. Mirrors the add_expr(InfixExpr) encoding
1161// exactly: aux1=-1, aux2=-1, meta=u16(op), edges = [lhs, rhs]. The
1162// flat-write port for `transform_infix_expr`'s identity branch
1163// (no rewrite triggered: just `transform(lhs)`/`transform(rhs)` rebuild)
1164// uses this to skip the `ast.InfixExpr` wrapper struct allocation.
1165pub fn (mut b FlatBuilder) emit_infix_expr_by_ids(op token.Token, lhs_id FlatNodeId, rhs_id FlatNodeId, pos token.Pos) FlatNodeId {
1166 return b.emit(.expr_infix, pos, -1, -1, u16(int(op)), 0, [
1167 FlatEdge{
1168 child_id: lhs_id
1169 },
1170 FlatEdge{
1171 child_id: rhs_id
1172 },
1173 ])
1174}
1175
1176// emit_keyword_operator_by_ids emits an expr_keyword_operator node from
1177// already-flat operand expression FlatNodeIds. Mirrors add_expr(KeywordOperator)
1178// exactly: aux1=-1, aux2=-1, meta=u16(op), edges = exprs.
1179pub fn (mut b FlatBuilder) emit_keyword_operator_by_ids(op token.Token, expr_ids []FlatNodeId, pos token.Pos) FlatNodeId {
1180 mut edges := []FlatEdge{cap: expr_ids.len}
1181 for id in expr_ids {
1182 edges << FlatEdge{
1183 child_id: id
1184 }
1185 }
1186 return b.emit(.expr_keyword_operator, pos, -1, -1, u16(int(op)), 0, edges)
1187}
1188
1189// emit_array_init_expr_by_ids emits an expr_array_init node from already-flat
1190// type/init/cap/len/update expression FlatNodeIds and a slice of already-flat
1191// element expression FlatNodeIds. Mirrors the add_expr(ArrayInitExpr)
1192// encoding exactly: edges = [typ, init, cap, len, update_expr, exprs...].
1193// The flat-write port for `transform_array_init_expr`'s identity branches
1194// (invalid data, fixed array, eval-backend dynamic array) uses this to skip
1195// the `ast.ArrayInitExpr` wrapper struct allocation.
1196pub fn (mut b FlatBuilder) emit_array_init_expr_by_ids(typ_id FlatNodeId, init_id FlatNodeId, cap_id FlatNodeId, len_id FlatNodeId, update_expr_id FlatNodeId, expr_ids []FlatNodeId, pos token.Pos) FlatNodeId {
1197 mut edges := []FlatEdge{cap: 5 + expr_ids.len}
1198 edges << FlatEdge{
1199 child_id: typ_id
1200 }
1201 edges << FlatEdge{
1202 child_id: init_id
1203 }
1204 edges << FlatEdge{
1205 child_id: cap_id
1206 }
1207 edges << FlatEdge{
1208 child_id: len_id
1209 }
1210 edges << FlatEdge{
1211 child_id: update_expr_id
1212 }
1213 for eid in expr_ids {
1214 edges << FlatEdge{
1215 child_id: eid
1216 }
1217 }
1218 return b.emit_simple(.expr_array_init, pos, edges)
1219}
1220
1221// emit_array_type_by_elem_id emits a typ_array node from an already-flat
1222// element type expression FlatNodeId. Mirrors add_type(ArrayType).
1223pub fn (mut b FlatBuilder) emit_array_type_by_elem_id(elem_type_id FlatNodeId) FlatNodeId {
1224 return b.emit_simple(.typ_array, token.Pos{}, [
1225 FlatEdge{
1226 child_id: elem_type_id
1227 },
1228 ])
1229}
1230
1231// emit_assign_stmt_by_ids emits a stmt_assign node from already-flat
1232// lhs/rhs expression FlatNodeIds. Mirrors the add_stmt(AssignStmt)
1233// encoding exactly: aux1=-1, aux2=lhs.len (lhs/rhs boundary in edges),
1234// meta=u16(op), edges = lhs in order followed by rhs in order.
1235pub fn (mut b FlatBuilder) emit_assign_stmt_by_ids(op token.Token, lhs_ids []FlatNodeId, rhs_ids []FlatNodeId, pos token.Pos) FlatNodeId {
1236 mut edges := []FlatEdge{cap: lhs_ids.len + rhs_ids.len}
1237 for lid in lhs_ids {
1238 edges << FlatEdge{
1239 child_id: lid
1240 }
1241 }
1242 for rid in rhs_ids {
1243 edges << FlatEdge{
1244 child_id: rid
1245 }
1246 }
1247 return b.emit(.stmt_assign, pos, -1, lhs_ids.len, u16(int(op)), 0, edges)
1248}
1249
1250// emit_comptime_stmt_by_id emits a stmt_comptime node wrapping an
1251// already-flat child stmt FlatNodeId. Mirrors the add_stmt(ComptimeStmt)
1252// encoding exactly: pos is the zero `token.Pos{}` the legacy add_stmt arm
1253// uses, single edge to the inner stmt. Used by the flat-write port to
1254// direct-emit the `ast.ComptimeStmt` wrapper (the `$for` branch — the
1255// non-`$for` branch drops the wrapper entirely and recurses).
1256pub fn (mut b FlatBuilder) emit_comptime_stmt_by_id(stmt_id FlatNodeId) FlatNodeId {
1257 return b.emit_simple(.stmt_comptime, token.Pos{}, [
1258 FlatEdge{
1259 child_id: stmt_id
1260 },
1261 ])
1262}
1263
1264// emit_defer_stmt_by_ids emits a stmt_defer node from a slice of already-flat
1265// child stmt FlatNodeIds. Mirrors the add_stmt(DeferStmt) encoding exactly:
1266// pos is the zero `token.Pos{}` the legacy add_stmt arm uses, flags carry
1267// `flag_defer_func` when mode is `.function`, edges are the inner stmts in
1268// order. Used by the flat-write port to direct-emit the `ast.DeferStmt`
1269// wrapper.
1270pub fn (mut b FlatBuilder) emit_defer_stmt_by_ids(mode DeferMode, stmt_ids []FlatNodeId) FlatNodeId {
1271 mut flags := u8(0)
1272 if mode == .function {
1273 flags |= flag_defer_func
1274 }
1275 mut edges := []FlatEdge{cap: stmt_ids.len}
1276 for sid in stmt_ids {
1277 edges << FlatEdge{
1278 child_id: sid
1279 }
1280 }
1281 return b.emit_simple_with_flags(.stmt_defer, token.Pos{}, flags, edges)
1282}
1283
1284// emit_parameter is the pub wrapper over the private add_parameter, used by
1285// the flat-write port when an FnDecl's receiver is emitted directly.
1286pub fn (mut b FlatBuilder) emit_parameter(param Parameter) FlatNodeId {
1287 return b.add_parameter(param)
1288}
1289
1290// emit_type is the pub wrapper over the private add_type, used by the
1291// flat-write port when an FnDecl's signature is emitted directly. The
1292// FnDecl encoding wraps `FnType` as `Type(stmt.typ)` so callers convert.
1293pub fn (mut b FlatBuilder) emit_type(typ Type) FlatNodeId {
1294 return b.add_type(typ)
1295}
1296
1297// emit_ident_by_name emits an expr_ident node from a name string and pos.
1298// Mirrors the add_expr(Ident) encoding exactly: name is interned into the
1299// data_id slot, extra is -1, no edges. Used by the flat-write port to
1300// direct-emit synthesised `ast.Ident{name, pos}` wrappers (smartcast
1301// rewrites, module/enum lookups, `unsafe { nil }`, ...).
1302pub fn (mut b FlatBuilder) emit_ident_by_name(name string, pos token.Pos) FlatNodeId {
1303 return b.emit(.expr_ident, pos, b.intern(name), -1, 0, 0, []FlatEdge{})
1304}
1305
1306// emit_basic_literal_by_value emits an expr_basic_literal node from a kind/value/pos.
1307// Mirrors the add_expr(BasicLiteral) encoding exactly: value is interned into
1308// the data_id slot, extra is -1, meta packs the kind token, no edges. Used by
1309// the flat-write port to direct-emit synthesised `ast.BasicLiteral{kind,
1310// value, pos}` wrappers (`$if res {...}` → `false`, etc.).
1311pub fn (mut b FlatBuilder) emit_basic_literal_by_value(kind token.Token, value string, pos token.Pos) FlatNodeId {
1312 return b.emit(.expr_basic_literal, pos, b.intern(value), -1, u16(int(kind)), 0, []FlatEdge{})
1313}
1314
1315// emit_string_literal_by_value emits an expr_string node from a kind/value/pos.
1316// Mirrors the add_expr(StringLiteral) encoding exactly: value is interned
1317// into the data_id slot, extra is -1, meta packs the StringLiteralKind, no
1318// edges. Used by the flat-write port to direct-emit synthesised
1319// `ast.StringLiteral{kind, value, pos}` wrappers (`$typeof(x)` → V type name
1320// string literal, etc.).
1321pub fn (mut b FlatBuilder) emit_string_literal_by_value(kind StringLiteralKind, value string, pos token.Pos) FlatNodeId {
1322 return b.emit(.expr_string, pos, b.intern(value), -1, u16(int(kind)), 0, []FlatEdge{})
1323}
1324
1325// emit_paren_expr_by_id emits an expr_paren node from an already-flat
1326// inner expression FlatNodeId. Mirrors the add_expr(ParenExpr) encoding
1327// exactly.
1328pub fn (mut b FlatBuilder) emit_paren_expr_by_id(inner_id FlatNodeId, pos token.Pos) FlatNodeId {
1329 return b.emit_simple(.expr_paren, pos, [
1330 FlatEdge{
1331 child_id: inner_id
1332 },
1333 ])
1334}
1335
1336// emit_prefix_expr_by_id emits an expr_prefix node from an already-flat
1337// inner expression FlatNodeId. Mirrors the add_expr(PrefixExpr) encoding
1338// exactly: the operator token is packed into the meta u16.
1339pub fn (mut b FlatBuilder) emit_prefix_expr_by_id(op token.Token, inner_id FlatNodeId, pos token.Pos) FlatNodeId {
1340 return b.emit(.expr_prefix, pos, -1, -1, u16(int(op)), 0, [
1341 FlatEdge{
1342 child_id: inner_id
1343 },
1344 ])
1345}
1346
1347// emit_modifier_expr_by_id emits an expr_modifier node from an already-flat
1348// inner expression FlatNodeId. Mirrors the add_expr(ModifierExpr) encoding
1349// exactly: the modifier kind token is packed into the meta u16.
1350pub fn (mut b FlatBuilder) emit_modifier_expr_by_id(kind token.Token, inner_id FlatNodeId, pos token.Pos) FlatNodeId {
1351 return b.emit(.expr_modifier, pos, -1, -1, u16(int(kind)), 0, [
1352 FlatEdge{
1353 child_id: inner_id
1354 },
1355 ])
1356}
1357
1358// emit_postfix_expr_by_id emits an expr_postfix node from an already-flat
1359// inner expression FlatNodeId. Mirrors the add_expr(PostfixExpr) encoding
1360// exactly: the operator token is packed into the meta u16.
1361pub fn (mut b FlatBuilder) emit_postfix_expr_by_id(op token.Token, inner_id FlatNodeId, pos token.Pos) FlatNodeId {
1362 return b.emit(.expr_postfix, pos, -1, -1, u16(int(op)), 0, [
1363 FlatEdge{
1364 child_id: inner_id
1365 },
1366 ])
1367}
1368
1369// emit_cast_expr_by_ids emits an expr_cast node from already-flat type
1370// expression and inner expression FlatNodeIds. Mirrors the
1371// add_expr(CastExpr) encoding exactly: edge[0] = typ, edge[1] = expr.
1372pub fn (mut b FlatBuilder) emit_cast_expr_by_ids(typ_id FlatNodeId, expr_id FlatNodeId, pos token.Pos) FlatNodeId {
1373 return b.emit_simple(.expr_cast, pos, [
1374 FlatEdge{
1375 child_id: typ_id
1376 },
1377 FlatEdge{
1378 child_id: expr_id
1379 },
1380 ])
1381}
1382
1383// emit_as_cast_expr_by_ids emits an expr_as_cast node from already-flat
1384// inner expression and type expression FlatNodeIds. Mirrors the
1385// add_expr(AsCastExpr) encoding exactly: edge[0] = expr, edge[1] = typ.
1386pub fn (mut b FlatBuilder) emit_as_cast_expr_by_ids(expr_id FlatNodeId, typ_id FlatNodeId, pos token.Pos) FlatNodeId {
1387 return b.emit_simple(.expr_as_cast, pos, [
1388 FlatEdge{
1389 child_id: expr_id
1390 },
1391 FlatEdge{
1392 child_id: typ_id
1393 },
1394 ])
1395}
1396
1397// emit_sql_expr_by_id emits an expr_sql node from an already-flat inner
1398// expression FlatNodeId. Mirrors the add_expr(SqlExpr) encoding exactly:
1399// table_name interned into aux1, is_count/is_create packed into flags,
1400// single edge to inner expr.
1401pub fn (mut b FlatBuilder) emit_sql_expr_by_id(table_name string, is_count bool, is_create bool, expr_id FlatNodeId, pos token.Pos) FlatNodeId {
1402 mut flags := u8(0)
1403 if is_count {
1404 flags |= flag_is_count
1405 }
1406 if is_create {
1407 flags |= flag_is_create
1408 }
1409 return b.emit(.expr_sql, pos, b.intern(table_name), -1, 0, flags, [
1410 FlatEdge{
1411 child_id: expr_id
1412 },
1413 ])
1414}
1415
1416// emit_unsafe_expr_by_ids emits an expr_unsafe node from already-flat
1417// stmt FlatNodeIds. Mirrors the add_expr(UnsafeExpr) encoding exactly:
1418// edges are the body stmts in order.
1419pub fn (mut b FlatBuilder) emit_unsafe_expr_by_ids(stmt_ids []FlatNodeId, pos token.Pos) FlatNodeId {
1420 mut edges := []FlatEdge{cap: stmt_ids.len}
1421 for sid in stmt_ids {
1422 edges << FlatEdge{
1423 child_id: sid
1424 }
1425 }
1426 return b.emit_simple(.expr_unsafe, pos, edges)
1427}
1428
1429// emit_lambda_expr_by_ids emits an expr_lambda node from an already-flat
1430// inner expression FlatNodeId and a slice of already-flat arg FlatNodeIds
1431// (each arg is an Ident). Mirrors the add_expr(LambdaExpr) encoding exactly:
1432// edge[0] is the inner expression, edge[1..] are the args.
1433pub fn (mut b FlatBuilder) emit_lambda_expr_by_ids(inner_id FlatNodeId, arg_ids []FlatNodeId, pos token.Pos) FlatNodeId {
1434 mut edges := []FlatEdge{cap: 1 + arg_ids.len}
1435 edges << FlatEdge{
1436 child_id: inner_id
1437 }
1438 for aid in arg_ids {
1439 edges << FlatEdge{
1440 child_id: aid
1441 }
1442 }
1443 return b.emit_simple(.expr_lambda, pos, edges)
1444}
1445
1446// emit_comptime_expr_by_id emits an expr_comptime node from an already-flat
1447// inner expression FlatNodeId. Mirrors the add_expr(ComptimeExpr) encoding
1448// exactly: a single edge to the inner expression.
1449pub fn (mut b FlatBuilder) emit_comptime_expr_by_id(inner_id FlatNodeId, pos token.Pos) FlatNodeId {
1450 return b.emit_simple(.expr_comptime, pos, [
1451 FlatEdge{
1452 child_id: inner_id
1453 },
1454 ])
1455}
1456
1457// emit_index_expr_by_ids emits an expr_index node from already-flat lhs
1458// and index expression FlatNodeIds. Mirrors the add_expr(IndexExpr) encoding
1459// exactly: edge[0] is the lhs, edge[1] is the index expression, and the
1460// `is_gated` flag is packed into the flags byte.
1461pub fn (mut b FlatBuilder) emit_index_expr_by_ids(lhs_id FlatNodeId, expr_id FlatNodeId, is_gated bool, pos token.Pos) FlatNodeId {
1462 flags := if is_gated { flag_is_gated } else { u8(0) }
1463 return b.emit(.expr_index, pos, -1, -1, 0, flags, [
1464 FlatEdge{
1465 child_id: lhs_id
1466 },
1467 FlatEdge{
1468 child_id: expr_id
1469 },
1470 ])
1471}
1472
1473// emit_selector_expr_by_ids emits an expr_selector node from an already-flat
1474// lhs expression and an already-flat rhs Ident expression. Mirrors the
1475// add_expr(SelectorExpr) encoding exactly: edge[0] is the lhs expr,
1476// edge[1] is the rhs (an Ident emitted as expr_ident).
1477pub fn (mut b FlatBuilder) emit_selector_expr_by_ids(lhs_id FlatNodeId, rhs_id FlatNodeId, pos token.Pos) FlatNodeId {
1478 return b.emit_simple(.expr_selector, pos, [
1479 FlatEdge{
1480 child_id: lhs_id
1481 },
1482 FlatEdge{
1483 child_id: rhs_id
1484 },
1485 ])
1486}
1487
1488// emit_init_expr_by_ids emits an expr_init node from an already-flat type
1489// FlatNodeId and a slice of already-flat aux_field_init FlatNodeIds. Mirrors
1490// the add_expr(InitExpr) encoding exactly: edge[0] is the type, edge[1..]
1491// are the field-init aux nodes. The flat-write port for struct literals uses
1492// this together with `emit_field_init_by_id` to avoid materialising an
1493// `ast.InitExpr` wrapper on the default path.
1494pub fn (mut b FlatBuilder) emit_init_expr_by_ids(typ_id FlatNodeId, field_ids []FlatNodeId, pos token.Pos) FlatNodeId {
1495 mut edges := []FlatEdge{cap: 1 + field_ids.len}
1496 edges << FlatEdge{
1497 child_id: typ_id
1498 }
1499 for fid in field_ids {
1500 edges << FlatEdge{
1501 child_id: fid
1502 }
1503 }
1504 return b.emit_simple(.expr_init, pos, edges)
1505}
1506
1507// emit_fn_literal_by_ids emits an expr_fn_literal node from an already-flat
1508// FnType FlatNodeId, slice of already-flat captured_var FlatNodeIds, and
1509// slice of already-flat stmt FlatNodeIds. Mirrors the add_expr(FnLiteral)
1510// encoding exactly: edge[0] is the type, edge[1..1+captured.len] are the
1511// captured vars, edge[1+captured.len..] are the stmts; `captured_vars.len`
1512// is packed into `extra` so the boundary is recoverable.
1513pub fn (mut b FlatBuilder) emit_fn_literal_by_ids(typ_id FlatNodeId, captured_var_ids []FlatNodeId, stmt_ids []FlatNodeId, pos token.Pos) FlatNodeId {
1514 mut edges := []FlatEdge{cap: 1 + captured_var_ids.len + stmt_ids.len}
1515 edges << FlatEdge{
1516 child_id: typ_id
1517 }
1518 for cv_id in captured_var_ids {
1519 edges << FlatEdge{
1520 child_id: cv_id
1521 }
1522 }
1523 for sid in stmt_ids {
1524 edges << FlatEdge{
1525 child_id: sid
1526 }
1527 }
1528 return b.emit(.expr_fn_literal, pos, -1, captured_var_ids.len, 0, 0, edges)
1529}
1530
1531// emit_string_inter_by_ids emits an aux_string_inter node from already-flat
1532// inter expression and format_expr FlatNodeIds. Mirrors add_string_inter
1533// exactly: edge[0] = expr, edge[1] = format_expr, edge[2] = width,
1534// edge[3] = precision; the format token packs into the meta u16;
1535// resolved_fmt interns into name.
1536pub fn (mut b FlatBuilder) emit_string_inter_by_ids(format StringInterFormat, width int, precision int, expr_id FlatNodeId, format_expr_id FlatNodeId, resolved_fmt string) FlatNodeId {
1537 mut edges := []FlatEdge{cap: 4}
1538 edges = b.push_edge(edges, expr_id)
1539 edges = b.push_edge(edges, format_expr_id)
1540 edges = b.push_edge(edges, b.emit_int(width))
1541 edges = b.push_edge(edges, b.emit_int(precision))
1542 return b.emit(.aux_string_inter, token.Pos{}, b.intern(resolved_fmt), -1, u16(int(format)), 0,
1543 edges)
1544}
1545
1546// emit_string_inter_literal_by_ids emits an expr_string_inter node from a
1547// verbatim values []string and a slice of already-flat StringInter FlatNodeIds.
1548// Mirrors the add_expr(StringInterLiteral) encoding exactly: edge[0] = values
1549// list (built via make_list_strings), edge[1] = inters list (built from the
1550// supplied FlatNodeIds via the standard aux_list shape).
1551pub fn (mut b FlatBuilder) emit_string_inter_literal_by_ids(kind StringLiteralKind, values []string, inter_ids []FlatNodeId, pos token.Pos) FlatNodeId {
1552 mut edges := []FlatEdge{cap: 2}
1553 edges = b.push_edge(edges, b.make_list_strings(values))
1554 edges = b.push_edge(edges, b.emit_aux_list_from_ids(inter_ids))
1555 return b.emit(.expr_string_inter, pos, -1, -1, u16(int(kind)), 0, edges)
1556}
1557
1558// emit_fn_decl_by_ids emits a stmt_fn_decl node from already-flat child
1559// FlatNodeIds (receiver parameter, FnType, attribute list, stmt list).
1560// Mirrors the add_stmt(FnDecl) encoding exactly, including the
1561// flag_is_public / flag_is_method / flag_is_static bits and the language
1562// enum carried in the meta u16.
1563pub fn (mut b FlatBuilder) emit_fn_decl_by_ids(name string, is_public bool, is_method bool, is_static bool, language Language, pos token.Pos, receiver_id FlatNodeId, typ_id FlatNodeId, attrs_id FlatNodeId, stmts_id FlatNodeId) FlatNodeId {
1564 mut flags := u8(0)
1565 if is_public {
1566 flags |= flag_is_public
1567 }
1568 if is_method {
1569 flags |= flag_is_method
1570 }
1571 if is_static {
1572 flags |= flag_is_static
1573 }
1574 return b.emit(.stmt_fn_decl, pos, b.intern(name), -1, u16(int(language)), flags, [
1575 FlatEdge{
1576 child_id: receiver_id
1577 },
1578 FlatEdge{
1579 child_id: typ_id
1580 },
1581 FlatEdge{
1582 child_id: attrs_id
1583 },
1584 FlatEdge{
1585 child_id: stmts_id
1586 },
1587 ])
1588}
1589
1590fn (mut b FlatBuilder) make_list_from_stmt_ids(stmt_ids []FlatNodeId) FlatNodeId {
1591 if stmt_ids.len == 0 {
1592 return b.get_empty_list()
1593 }
1594 mut edges := []FlatEdge{cap: stmt_ids.len}
1595 for id in stmt_ids {
1596 edges = b.push_edge(edges, id)
1597 }
1598 return b.emit_simple(.aux_list, token.Pos{}, edges)
1599}
1600
1601fn (mut b FlatBuilder) intern(s string) int {
1602 if s.len == 0 {
1603 return -1
1604 }
1605 if idx := b.string_ids[s] {
1606 return idx
1607 }
1608 idx := b.flat.strings.len
1609 b.flat.strings << s
1610 b.string_ids[s] = idx
1611 return idx
1612}
1613
1614// emit appends a node with the supplied cell payload + edges, returning its id.
1615fn (mut b FlatBuilder) emit(kind FlatNodeKind, pos token.Pos, name_id int, extra int, aux u16, flags u8, edges []FlatEdge) FlatNodeId {
1616 first_edge := b.flat.edges.len
1617 if edges.len > 0 {
1618 b.flat.edges << edges
1619 }
1620 node_id := FlatNodeId(b.flat.nodes.len)
1621 b.flat.nodes << FlatNode{
1622 kind: kind
1623 flags: flags
1624 aux: aux
1625 name_id: name_id
1626 extra: extra
1627 pos: pos
1628 first_edge: first_edge
1629 edge_count: edges.len
1630 }
1631 return node_id
1632}
1633
1634// emit_simple wraps emit() for nodes with no scalar payload beyond kind/pos/edges.
1635@[inline]
1636fn (mut b FlatBuilder) emit_simple(kind FlatNodeKind, pos token.Pos, edges []FlatEdge) FlatNodeId {
1637 return b.emit(kind, pos, -1, -1, 0, 0, edges)
1638}
1639
1640fn (mut b FlatBuilder) push_edge(edges []FlatEdge, child FlatNodeId) []FlatEdge {
1641 // Append-and-reassign pattern (callers always do `edges = b.push_edge(edges, ...)`),
1642 // so skipping the defensive clone is safe and avoids one copy per edge.
1643 mut updated := unsafe { edges }
1644 updated << FlatEdge{
1645 child_id: child
1646 }
1647 return updated
1648}
1649
1650fn (mut b FlatBuilder) push_expr(edges []FlatEdge, expr Expr) []FlatEdge {
1651 return b.push_edge(edges, b.add_expr(expr))
1652}
1653
1654fn (mut b FlatBuilder) push_stmt(edges []FlatEdge, stmt Stmt) []FlatEdge {
1655 return b.push_edge(edges, b.add_stmt(stmt))
1656}
1657
1658fn (mut b FlatBuilder) push_type(edges []FlatEdge, typ Type) []FlatEdge {
1659 return b.push_edge(edges, b.add_type(typ))
1660}
1661
1662// get_empty_list returns a shared aux_list node id used as a canonical
1663// "empty list". Many AST variants have multiple optional sub-lists; reusing
1664// one node avoids emitting thousands of zero-edge aux_list cells.
1665fn (mut b FlatBuilder) get_empty_list() FlatNodeId {
1666 if b.empty_list_id == invalid_flat_node_id {
1667 b.empty_list_id = b.emit_simple(.aux_list, token.Pos{}, []FlatEdge{})
1668 }
1669 return b.empty_list_id
1670}
1671
1672// make_list creates an aux_list node containing the supplied edges.
1673fn (mut b FlatBuilder) make_list_expr(items []Expr) FlatNodeId {
1674 if items.len == 0 {
1675 return b.get_empty_list()
1676 }
1677 mut edges := []FlatEdge{cap: items.len}
1678 for it in items {
1679 edges = b.push_expr(edges, it)
1680 }
1681 return b.emit_simple(.aux_list, token.Pos{}, edges)
1682}
1683
1684fn (mut b FlatBuilder) make_list_stmt(items []Stmt) FlatNodeId {
1685 if items.len == 0 {
1686 return b.get_empty_list()
1687 }
1688 mut edges := []FlatEdge{cap: items.len}
1689 for it in items {
1690 edges = b.push_stmt(edges, it)
1691 }
1692 return b.emit_simple(.aux_list, token.Pos{}, edges)
1693}
1694
1695fn (mut b FlatBuilder) make_list_attribute(items []Attribute) FlatNodeId {
1696 if items.len == 0 {
1697 return b.get_empty_list()
1698 }
1699 mut edges := []FlatEdge{cap: items.len}
1700 for it in items {
1701 edges = b.push_edge(edges, b.add_attribute(it))
1702 }
1703 return b.emit_simple(.aux_list, token.Pos{}, edges)
1704}
1705
1706fn (mut b FlatBuilder) make_list_field_init(items []FieldInit) FlatNodeId {
1707 if items.len == 0 {
1708 return b.get_empty_list()
1709 }
1710 mut edges := []FlatEdge{cap: items.len}
1711 for it in items {
1712 edges = b.push_edge(edges, b.add_field_init(it))
1713 }
1714 return b.emit_simple(.aux_list, token.Pos{}, edges)
1715}
1716
1717fn (mut b FlatBuilder) make_list_field_decl(items []FieldDecl) FlatNodeId {
1718 if items.len == 0 {
1719 return b.get_empty_list()
1720 }
1721 mut edges := []FlatEdge{cap: items.len}
1722 for it in items {
1723 edges = b.push_edge(edges, b.add_field_decl(it))
1724 }
1725 return b.emit_simple(.aux_list, token.Pos{}, edges)
1726}
1727
1728fn (mut b FlatBuilder) make_list_parameter(items []Parameter) FlatNodeId {
1729 if items.len == 0 {
1730 return b.get_empty_list()
1731 }
1732 mut edges := []FlatEdge{cap: items.len}
1733 for it in items {
1734 edges = b.push_edge(edges, b.add_parameter(it))
1735 }
1736 return b.emit_simple(.aux_list, token.Pos{}, edges)
1737}
1738
1739fn (mut b FlatBuilder) make_list_match_branch(items []MatchBranch) FlatNodeId {
1740 if items.len == 0 {
1741 return b.get_empty_list()
1742 }
1743 mut edges := []FlatEdge{cap: items.len}
1744 for it in items {
1745 edges = b.push_edge(edges, b.add_match_branch(it))
1746 }
1747 return b.emit_simple(.aux_list, token.Pos{}, edges)
1748}
1749
1750fn (mut b FlatBuilder) make_list_string_inter(items []StringInter) FlatNodeId {
1751 if items.len == 0 {
1752 return b.get_empty_list()
1753 }
1754 mut edges := []FlatEdge{cap: items.len}
1755 for it in items {
1756 edges = b.push_edge(edges, b.add_string_inter(it))
1757 }
1758 return b.emit_simple(.aux_list, token.Pos{}, edges)
1759}
1760
1761fn (mut b FlatBuilder) make_list_strings(items []string) FlatNodeId {
1762 if items.len == 0 {
1763 return b.get_empty_list()
1764 }
1765 mut edges := []FlatEdge{cap: items.len}
1766 for s in items {
1767 id := b.emit(.aux_string, token.Pos{}, b.intern(s), -1, 0, 0, []FlatEdge{})
1768 edges = b.push_edge(edges, id)
1769 }
1770 return b.emit_simple(.aux_list, token.Pos{}, edges)
1771}
1772
1773fn (mut b FlatBuilder) emit_int(value int) FlatNodeId {
1774 return b.emit(.aux_int, token.Pos{}, -1, value, 0, 0, []FlatEdge{})
1775}
1776
1777fn (mut b FlatBuilder) make_list_imports(items []ImportStmt) FlatNodeId {
1778 if items.len == 0 {
1779 return b.get_empty_list()
1780 }
1781 mut edges := []FlatEdge{cap: items.len}
1782 for it in items {
1783 edges = b.push_edge(edges, b.add_stmt(Stmt(it)))
1784 }
1785 return b.emit_simple(.aux_list, token.Pos{}, edges)
1786}
1787
1788// add_file emits the file root and returns its FlatNodeId.
1789fn (mut b FlatBuilder) add_file(file File) FlatNodeId {
1790 mut edges := []FlatEdge{}
1791 edges = b.push_edge(edges, b.make_list_attribute(file.attributes))
1792 edges = b.push_edge(edges, b.make_list_imports(file.imports))
1793 edges = b.push_edge(edges, b.make_list_stmt(file.stmts))
1794 return b.emit(.file, token.Pos{}, b.intern(file.name), b.intern(file.mod), 0, 0, edges)
1795}
1796
1797fn (mut b FlatBuilder) add_stmt(stmt Stmt) FlatNodeId {
1798 if stmt is []Attribute {
1799 return b.emit_simple(.stmt_attributes, token.Pos{}, [
1800 FlatEdge{
1801 child_id: b.make_list_attribute(stmt)
1802 },
1803 ])
1804 }
1805 match stmt {
1806 AsmStmt {
1807 return b.emit(.stmt_asm, token.Pos{}, b.intern(stmt.arch), -1, 0, 0, []FlatEdge{})
1808 }
1809 AssertStmt {
1810 mut edges := []FlatEdge{}
1811 edges = b.push_expr(edges, stmt.expr)
1812 edges = b.push_expr(edges, stmt.extra)
1813 return b.emit_simple(.stmt_assert, token.Pos{}, edges)
1814 }
1815 AssignStmt {
1816 mut edges := []FlatEdge{cap: stmt.lhs.len + stmt.rhs.len}
1817 for e in stmt.lhs {
1818 edges = b.push_expr(edges, e)
1819 }
1820 for e in stmt.rhs {
1821 edges = b.push_expr(edges, e)
1822 }
1823 return b.emit(.stmt_assign, stmt.pos, -1, stmt.lhs.len, u16(int(stmt.op)), 0, edges)
1824 }
1825 BlockStmt {
1826 mut edges := []FlatEdge{cap: stmt.stmts.len}
1827 for s in stmt.stmts {
1828 edges = b.push_stmt(edges, s)
1829 }
1830 return b.emit_simple(.stmt_block, token.Pos{}, edges)
1831 }
1832 ComptimeStmt {
1833 return b.emit_simple(.stmt_comptime, token.Pos{}, [
1834 FlatEdge{
1835 child_id: b.add_stmt(stmt.stmt)
1836 },
1837 ])
1838 }
1839 ConstDecl {
1840 mut flags := u8(0)
1841 if stmt.is_public {
1842 flags |= flag_is_public
1843 }
1844 return b.emit_simple_with_flags(.stmt_const_decl, token.Pos{}, flags, [
1845 FlatEdge{
1846 child_id: b.make_list_field_init(stmt.fields)
1847 },
1848 ])
1849 }
1850 DeferStmt {
1851 mut flags := u8(0)
1852 if stmt.mode == .function {
1853 flags |= flag_defer_func
1854 }
1855 mut edges := []FlatEdge{cap: stmt.stmts.len}
1856 for s in stmt.stmts {
1857 edges = b.push_stmt(edges, s)
1858 }
1859 return b.emit_simple_with_flags(.stmt_defer, token.Pos{}, flags, edges)
1860 }
1861 Directive {
1862 mut edges := []FlatEdge{}
1863 // ct_cond carried as a child aux_string node when non-empty
1864 if stmt.ct_cond.len > 0 {
1865 id := b.emit(.aux_string, token.Pos{}, b.intern(stmt.ct_cond), -1, 0, 0,
1866 []FlatEdge{})
1867 edges = b.push_edge(edges, id)
1868 }
1869 return b.emit(.stmt_directive, token.Pos{}, b.intern(stmt.name), b.intern(stmt.value),
1870 0, 0, edges)
1871 }
1872 EmptyStmt {
1873 if int(stmt) == 0 {
1874 if b.empty_stmt_id == invalid_flat_node_id {
1875 b.empty_stmt_id = b.emit(.stmt_empty, token.Pos{}, -1, 0, 0, 0, []FlatEdge{})
1876 }
1877 return b.empty_stmt_id
1878 }
1879 return b.emit(.stmt_empty, token.Pos{}, -1, int(stmt), 0, 0, []FlatEdge{})
1880 }
1881 EnumDecl {
1882 mut flags := u8(0)
1883 if stmt.is_public {
1884 flags |= flag_is_public
1885 }
1886 mut edges := []FlatEdge{}
1887 edges = b.push_expr(edges, stmt.as_type)
1888 edges = b.push_edge(edges, b.make_list_attribute(stmt.attributes))
1889 edges = b.push_edge(edges, b.make_list_field_decl(stmt.fields))
1890 return b.emit_simple_with_flags_name(.stmt_enum_decl, token.Pos{}, flags,
1891 b.intern(stmt.name), edges)
1892 }
1893 ExprStmt {
1894 return b.emit_simple(.stmt_expr, token.Pos{}, [
1895 FlatEdge{
1896 child_id: b.add_expr(stmt.expr)
1897 },
1898 ])
1899 }
1900 FlowControlStmt {
1901 return b.emit(.stmt_flow_control, token.Pos{}, b.intern(stmt.label), -1,
1902 u16(int(stmt.op)), 0, []FlatEdge{})
1903 }
1904 FnDecl {
1905 mut flags := u8(0)
1906 if stmt.is_public {
1907 flags |= flag_is_public
1908 }
1909 if stmt.is_method {
1910 flags |= flag_is_method
1911 }
1912 if stmt.is_static {
1913 flags |= flag_is_static
1914 }
1915 mut edges := []FlatEdge{}
1916 if stmt.is_method {
1917 edges = b.push_edge(edges, b.add_parameter(stmt.receiver))
1918 } else {
1919 // s251: a non-method FnDecl keeps the parser's zero `ast.Parameter{}`
1920 // receiver, whose `typ` is a zero-valued Expr (invalid sum-type tag,
1921 // null payload). Routing that through add_expr is unsafe on the arm64
1922 // self-host: an exhaustive match on an unmatched tag falls into the
1923 // first arm (ArrayInitExpr) and derefs the null payload. The receiver
1924 // edge is only read for methods, so emit a clean empty receiver
1925 // (typ = empty_expr, a valid EmptyExpr) for non-methods.
1926 edges = b.push_edge(edges, b.add_parameter(Parameter{
1927 typ: empty_expr
1928 }))
1929 }
1930 edges = b.push_edge(edges, b.add_type(Type(stmt.typ)))
1931 edges = b.push_edge(edges, b.make_list_attribute(stmt.attributes))
1932 edges = b.push_edge(edges, b.make_list_stmt(stmt.stmts))
1933 return b.emit(.stmt_fn_decl, stmt.pos, b.intern(stmt.name), -1,
1934 u16(int(stmt.language)), flags, edges)
1935 }
1936 ForInStmt {
1937 mut edges := []FlatEdge{cap: 3}
1938 edges = b.push_expr(edges, stmt.key)
1939 edges = b.push_expr(edges, stmt.value)
1940 edges = b.push_expr(edges, stmt.expr)
1941 return b.emit_simple(.stmt_for_in, token.Pos{}, edges)
1942 }
1943 ForStmt {
1944 mut edges := []FlatEdge{cap: 3 + stmt.stmts.len}
1945 edges = b.push_stmt(edges, stmt.init)
1946 edges = b.push_expr(edges, stmt.cond)
1947 edges = b.push_stmt(edges, stmt.post)
1948 for s in stmt.stmts {
1949 edges = b.push_stmt(edges, s)
1950 }
1951 return b.emit_simple(.stmt_for, token.Pos{}, edges)
1952 }
1953 GlobalDecl {
1954 mut flags := u8(0)
1955 if stmt.is_public {
1956 flags |= flag_is_public
1957 }
1958 mut edges := []FlatEdge{}
1959 edges = b.push_edge(edges, b.make_list_attribute(stmt.attributes))
1960 edges = b.push_edge(edges, b.make_list_field_decl(stmt.fields))
1961 return b.emit_simple_with_flags(.stmt_global_decl, token.Pos{}, flags, edges)
1962 }
1963 ImportStmt {
1964 mut flags := u8(0)
1965 if stmt.is_aliased {
1966 flags |= flag_is_aliased
1967 }
1968 mut edges := []FlatEdge{cap: stmt.symbols.len}
1969 for sym in stmt.symbols {
1970 edges = b.push_expr(edges, sym)
1971 }
1972 return b.emit(.stmt_import, token.Pos{}, b.intern(stmt.name), b.intern(stmt.alias), 0,
1973 flags, edges)
1974 }
1975 InterfaceDecl {
1976 mut flags := u8(0)
1977 if stmt.is_public {
1978 flags |= flag_is_public
1979 }
1980 mut edges := []FlatEdge{cap: 4}
1981 edges = b.push_edge(edges, b.make_list_attribute(stmt.attributes))
1982 edges = b.push_edge(edges, b.make_list_expr(stmt.generic_params))
1983 edges = b.push_edge(edges, b.make_list_expr(stmt.embedded))
1984 edges = b.push_edge(edges, b.make_list_field_decl(stmt.fields))
1985 return b.emit_simple_with_flags_name(.stmt_interface_decl, token.Pos{}, flags,
1986 b.intern(stmt.name), edges)
1987 }
1988 LabelStmt {
1989 return b.emit(.stmt_label, token.Pos{}, b.intern(stmt.name), -1, 0, 0, [
1990 FlatEdge{
1991 child_id: b.add_stmt(stmt.stmt)
1992 },
1993 ])
1994 }
1995 ModuleStmt {
1996 return b.emit(.stmt_module, token.Pos{}, b.intern(stmt.name), -1, 0, 0, []FlatEdge{})
1997 }
1998 ReturnStmt {
1999 mut edges := []FlatEdge{cap: stmt.exprs.len}
2000 for e in stmt.exprs {
2001 edges = b.push_expr(edges, e)
2002 }
2003 return b.emit_simple(.stmt_return, token.Pos{}, edges)
2004 }
2005 StructDecl {
2006 mut flags := u8(0)
2007 if stmt.is_public {
2008 flags |= flag_is_public
2009 }
2010 if stmt.is_union {
2011 flags |= flag_is_union
2012 }
2013 mut edges := []FlatEdge{cap: 5}
2014 edges = b.push_edge(edges, b.make_list_attribute(stmt.attributes))
2015 edges = b.push_edge(edges, b.make_list_expr(stmt.implements))
2016 edges = b.push_edge(edges, b.make_list_expr(stmt.embedded))
2017 edges = b.push_edge(edges, b.make_list_expr(stmt.generic_params))
2018 edges = b.push_edge(edges, b.make_list_field_decl(stmt.fields))
2019 return b.emit(.stmt_struct_decl, stmt.pos, b.intern(stmt.name), -1,
2020 u16(int(stmt.language)), flags, edges)
2021 }
2022 TypeDecl {
2023 mut flags := u8(0)
2024 if stmt.is_public {
2025 flags |= flag_is_public
2026 }
2027 mut edges := []FlatEdge{cap: 4}
2028 edges = b.push_expr(edges, stmt.base_type)
2029 edges = b.push_edge(edges, b.make_list_attribute([]Attribute{}))
2030 edges = b.push_edge(edges, b.make_list_expr(stmt.generic_params))
2031 edges = b.push_edge(edges, b.make_list_expr(stmt.variants))
2032 return b.emit(.stmt_type_decl, token.Pos{}, b.intern(stmt.name), -1,
2033 u16(int(stmt.language)), flags, edges)
2034 }
2035 []Attribute {
2036 // handled at top of function
2037 return invalid_flat_node_id
2038 }
2039 }
2040}
2041
2042@[inline]
2043fn (mut b FlatBuilder) emit_simple_with_flags(kind FlatNodeKind, pos token.Pos, flags u8, edges []FlatEdge) FlatNodeId {
2044 return b.emit(kind, pos, -1, -1, 0, flags, edges)
2045}
2046
2047@[inline]
2048fn (mut b FlatBuilder) emit_simple_with_flags_name(kind FlatNodeKind, pos token.Pos, flags u8, name_id int, edges []FlatEdge) FlatNodeId {
2049 return b.emit(kind, pos, name_id, -1, 0, flags, edges)
2050}
2051
2052fn (mut b FlatBuilder) add_expr(expr Expr) FlatNodeId {
2053 if expr_is_zero_value(expr) {
2054 if b.empty_expr_id == invalid_flat_node_id {
2055 b.empty_expr_id = b.emit(.expr_empty, token.Pos{}, -1, 0, 0, 0, []FlatEdge{})
2056 }
2057 return b.empty_expr_id
2058 }
2059 match expr {
2060 Type {
2061 return b.add_type(expr)
2062 }
2063 FieldInit {
2064 return b.add_field_init(expr)
2065 }
2066 else {}
2067 }
2068
2069 match expr {
2070 ArrayInitExpr {
2071 mut edges := []FlatEdge{cap: 5 + expr.exprs.len}
2072 edges = b.push_expr(edges, expr.typ)
2073 edges = b.push_expr(edges, expr.init)
2074 edges = b.push_expr(edges, expr.cap)
2075 edges = b.push_expr(edges, expr.len)
2076 edges = b.push_expr(edges, expr.update_expr)
2077 for e in expr.exprs {
2078 edges = b.push_expr(edges, e)
2079 }
2080 return b.emit_simple(.expr_array_init, expr.pos, edges)
2081 }
2082 AsCastExpr {
2083 mut edges := []FlatEdge{cap: 2}
2084 edges = b.push_expr(edges, expr.expr)
2085 edges = b.push_expr(edges, expr.typ)
2086 return b.emit_simple(.expr_as_cast, expr.pos, edges)
2087 }
2088 AssocExpr {
2089 mut edges := []FlatEdge{cap: 2 + expr.fields.len}
2090 edges = b.push_expr(edges, expr.typ)
2091 edges = b.push_expr(edges, expr.expr)
2092 for f in expr.fields {
2093 edges = b.push_edge(edges, b.add_field_init(f))
2094 }
2095 return b.emit_simple(.expr_assoc, expr.pos, edges)
2096 }
2097 BasicLiteral {
2098 return b.emit(.expr_basic_literal, expr.pos, b.intern(expr.value), -1,
2099 u16(int(expr.kind)), 0, []FlatEdge{})
2100 }
2101 CallExpr {
2102 mut edges := []FlatEdge{cap: 1 + expr.args.len}
2103 edges = b.push_expr(edges, expr.lhs)
2104 for a in expr.args {
2105 edges = b.push_expr(edges, a)
2106 }
2107 return b.emit_simple(.expr_call, expr.pos, edges)
2108 }
2109 CallOrCastExpr {
2110 mut edges := []FlatEdge{cap: 2}
2111 edges = b.push_expr(edges, expr.lhs)
2112 edges = b.push_expr(edges, expr.expr)
2113 return b.emit_simple(.expr_call_or_cast, expr.pos, edges)
2114 }
2115 CastExpr {
2116 mut edges := []FlatEdge{cap: 2}
2117 edges = b.push_expr(edges, expr.typ)
2118 edges = b.push_expr(edges, expr.expr)
2119 return b.emit_simple(.expr_cast, expr.pos, edges)
2120 }
2121 ComptimeExpr {
2122 return b.emit_simple(.expr_comptime, expr.pos, [
2123 FlatEdge{
2124 child_id: b.add_expr(expr.expr)
2125 },
2126 ])
2127 }
2128 EmptyExpr {
2129 // All EmptyExpr instances in the parser are `EmptyExpr(0)` (see
2130 // ast.empty_expr). Share one node — 65k+ duplicates per build.
2131 if int(expr) == 0 {
2132 if b.empty_expr_id == invalid_flat_node_id {
2133 b.empty_expr_id = b.emit(.expr_empty, token.Pos{}, -1, 0, 0, 0, []FlatEdge{})
2134 }
2135 return b.empty_expr_id
2136 }
2137 return b.emit(.expr_empty, token.Pos{}, -1, int(expr), 0, 0, []FlatEdge{})
2138 }
2139 FnLiteral {
2140 mut edges := []FlatEdge{}
2141 edges = b.push_edge(edges, b.add_type(Type(expr.typ)))
2142 for cv in expr.captured_vars {
2143 edges = b.push_expr(edges, cv)
2144 }
2145 for s in expr.stmts {
2146 edges = b.push_stmt(edges, s)
2147 }
2148 // extra stores captured_vars.len so the boundary between captured
2149 // vars and stmts is recoverable.
2150 return b.emit(.expr_fn_literal, expr.pos, -1, expr.captured_vars.len, 0, 0, edges)
2151 }
2152 GenericArgOrIndexExpr {
2153 mut edges := []FlatEdge{cap: 2}
2154 edges = b.push_expr(edges, expr.lhs)
2155 edges = b.push_expr(edges, expr.expr)
2156 return b.emit_simple(.expr_generic_arg_or_index, expr.pos, edges)
2157 }
2158 GenericArgs {
2159 mut edges := []FlatEdge{cap: 1 + expr.args.len}
2160 edges = b.push_expr(edges, expr.lhs)
2161 for a in expr.args {
2162 edges = b.push_expr(edges, a)
2163 }
2164 return b.emit_simple(.expr_generic_args, expr.pos, edges)
2165 }
2166 Ident {
2167 return b.emit(.expr_ident, expr.pos, b.intern(expr.name), -1, 0, 0, []FlatEdge{})
2168 }
2169 IfExpr {
2170 mut edges := []FlatEdge{cap: 2 + expr.stmts.len}
2171 edges = b.push_expr(edges, expr.cond)
2172 edges = b.push_expr(edges, expr.else_expr)
2173 for s in expr.stmts {
2174 edges = b.push_stmt(edges, s)
2175 }
2176 return b.emit_simple(.expr_if, expr.pos, edges)
2177 }
2178 IfGuardExpr {
2179 return b.emit_simple(.expr_if_guard, expr.pos, [
2180 FlatEdge{
2181 child_id: b.add_stmt(Stmt(expr.stmt))
2182 },
2183 ])
2184 }
2185 IndexExpr {
2186 mut flags := u8(0)
2187 if expr.is_gated {
2188 flags |= flag_is_gated
2189 }
2190 mut edges := []FlatEdge{cap: 2}
2191 edges = b.push_expr(edges, expr.lhs)
2192 edges = b.push_expr(edges, expr.expr)
2193 return b.emit(.expr_index, expr.pos, -1, -1, 0, flags, edges)
2194 }
2195 InfixExpr {
2196 mut edges := []FlatEdge{cap: 2}
2197 edges = b.push_expr(edges, expr.lhs)
2198 edges = b.push_expr(edges, expr.rhs)
2199 return b.emit(.expr_infix, expr.pos, -1, -1, u16(int(expr.op)), 0, edges)
2200 }
2201 InitExpr {
2202 mut edges := []FlatEdge{cap: 1 + expr.fields.len}
2203 edges = b.push_expr(edges, expr.typ)
2204 for f in expr.fields {
2205 edges = b.push_edge(edges, b.add_field_init(f))
2206 }
2207 return b.emit_simple(.expr_init, expr.pos, edges)
2208 }
2209 Keyword {
2210 return b.emit(.expr_keyword, token.Pos{}, -1, -1, u16(int(expr.tok)), 0, []FlatEdge{})
2211 }
2212 KeywordOperator {
2213 mut edges := []FlatEdge{cap: expr.exprs.len}
2214 for e in expr.exprs {
2215 edges = b.push_expr(edges, e)
2216 }
2217 return b.emit(.expr_keyword_operator, expr.pos, -1, -1, u16(int(expr.op)), 0, edges)
2218 }
2219 LambdaExpr {
2220 mut edges := []FlatEdge{cap: 1 + expr.args.len}
2221 edges = b.push_expr(edges, expr.expr)
2222 for a in expr.args {
2223 edges = b.push_expr(edges, Expr(a))
2224 }
2225 return b.emit_simple(.expr_lambda, expr.pos, edges)
2226 }
2227 LifetimeExpr {
2228 return b.emit(.expr_lifetime, expr.pos, b.intern(expr.name), -1, 0, 0, []FlatEdge{})
2229 }
2230 LockExpr {
2231 mut edges := []FlatEdge{cap: expr.lock_exprs.len + expr.rlock_exprs.len + expr.stmts.len}
2232 for e in expr.lock_exprs {
2233 edges = b.push_expr(edges, e)
2234 }
2235 for e in expr.rlock_exprs {
2236 edges = b.push_expr(edges, e)
2237 }
2238 for s in expr.stmts {
2239 edges = b.push_stmt(edges, s)
2240 }
2241 // Pack (lock.len, rlock.len) into extra; stmts.len = edge_count - lock - rlock.
2242 lock_len := u32(expr.lock_exprs.len) & 0xFFFF
2243 rlock_len := u32(expr.rlock_exprs.len) & 0xFFFF
2244 packed := int(lock_len | (rlock_len << 16))
2245 return b.emit(.expr_lock, expr.pos, -1, packed, 0, 0, edges)
2246 }
2247 MapInitExpr {
2248 mut edges := []FlatEdge{cap: 1 + expr.keys.len + expr.vals.len}
2249 edges = b.push_expr(edges, expr.typ)
2250 for k in expr.keys {
2251 edges = b.push_expr(edges, k)
2252 }
2253 for v in expr.vals {
2254 edges = b.push_expr(edges, v)
2255 }
2256 return b.emit(.expr_map_init, expr.pos, -1, expr.keys.len, 0, 0, edges)
2257 }
2258 MatchExpr {
2259 mut edges := []FlatEdge{cap: 1 + expr.branches.len}
2260 edges = b.push_expr(edges, expr.expr)
2261 for br in expr.branches {
2262 edges = b.push_edge(edges, b.add_match_branch(br))
2263 }
2264 return b.emit_simple(.expr_match, expr.pos, edges)
2265 }
2266 ModifierExpr {
2267 mut edges := []FlatEdge{cap: 1}
2268 edges = b.push_expr(edges, expr.expr)
2269 return b.emit(.expr_modifier, expr.pos, -1, -1, u16(int(expr.kind)), 0, edges)
2270 }
2271 OrExpr {
2272 mut edges := []FlatEdge{cap: 1 + expr.stmts.len}
2273 edges = b.push_expr(edges, expr.expr)
2274 for s in expr.stmts {
2275 edges = b.push_stmt(edges, s)
2276 }
2277 return b.emit_simple(.expr_or, expr.pos, edges)
2278 }
2279 ParenExpr {
2280 return b.emit_simple(.expr_paren, expr.pos, [
2281 FlatEdge{
2282 child_id: b.add_expr(expr.expr)
2283 },
2284 ])
2285 }
2286 PostfixExpr {
2287 return b.emit(.expr_postfix, expr.pos, -1, -1, u16(int(expr.op)), 0, [
2288 FlatEdge{
2289 child_id: b.add_expr(expr.expr)
2290 },
2291 ])
2292 }
2293 PrefixExpr {
2294 return b.emit(.expr_prefix, expr.pos, -1, -1, u16(int(expr.op)), 0, [
2295 FlatEdge{
2296 child_id: b.add_expr(expr.expr)
2297 },
2298 ])
2299 }
2300 RangeExpr {
2301 mut edges := []FlatEdge{cap: 2}
2302 edges = b.push_expr(edges, expr.start)
2303 edges = b.push_expr(edges, expr.end)
2304 return b.emit(.expr_range, expr.pos, -1, -1, u16(int(expr.op)), 0, edges)
2305 }
2306 SelectExpr {
2307 mut edges := []FlatEdge{cap: 2 + expr.stmts.len}
2308 edges = b.push_stmt(edges, expr.stmt)
2309 edges = b.push_expr(edges, expr.next)
2310 for s in expr.stmts {
2311 edges = b.push_stmt(edges, s)
2312 }
2313 return b.emit_simple(.expr_select, expr.pos, edges)
2314 }
2315 SelectorExpr {
2316 mut edges := []FlatEdge{cap: 2}
2317 edges = b.push_expr(edges, expr.lhs)
2318 edges = b.push_expr(edges, Expr(expr.rhs))
2319 return b.emit_simple(.expr_selector, expr.pos, edges)
2320 }
2321 SqlExpr {
2322 mut flags := u8(0)
2323 if expr.is_count {
2324 flags |= flag_is_count
2325 }
2326 if expr.is_create {
2327 flags |= flag_is_create
2328 }
2329 return b.emit(.expr_sql, expr.pos, b.intern(expr.table_name), -1, 0, flags, [
2330 FlatEdge{
2331 child_id: b.add_expr(expr.expr)
2332 },
2333 ])
2334 }
2335 StringInterLiteral {
2336 mut edges := []FlatEdge{cap: 2}
2337 edges = b.push_edge(edges, b.make_list_strings(expr.values))
2338 edges = b.push_edge(edges, b.make_list_string_inter(expr.inters))
2339 return b.emit(.expr_string_inter, expr.pos, -1, -1, u16(int(expr.kind)), 0, edges)
2340 }
2341 StringLiteral {
2342 return b.emit(.expr_string, expr.pos, b.intern(expr.value), -1, u16(int(expr.kind)), 0,
2343 []FlatEdge{})
2344 }
2345 Tuple {
2346 mut edges := []FlatEdge{cap: expr.exprs.len}
2347 for e in expr.exprs {
2348 edges = b.push_expr(edges, e)
2349 }
2350 return b.emit_simple(.expr_tuple, expr.pos, edges)
2351 }
2352 UnsafeExpr {
2353 mut edges := []FlatEdge{cap: expr.stmts.len}
2354 for s in expr.stmts {
2355 edges = b.push_stmt(edges, s)
2356 }
2357 return b.emit_simple(.expr_unsafe, expr.pos, edges)
2358 }
2359 FieldInit, Type {
2360 // handled at top of function
2361 return invalid_flat_node_id
2362 }
2363 }
2364}
2365
2366@[inline]
2367fn expr_is_zero_value(expr Expr) bool {
2368 tag_word := unsafe { (&u64(&expr))[0] }
2369 data_word := unsafe { (&u64(&expr))[1] }
2370 return tag_word == 0 && data_word == 0
2371}
2372
2373fn (mut b FlatBuilder) add_type(typ Type) FlatNodeId {
2374 match typ {
2375 AnonStructType {
2376 mut edges := []FlatEdge{cap: 3}
2377 edges = b.push_edge(edges, b.make_list_expr(typ.generic_params))
2378 edges = b.push_edge(edges, b.make_list_expr(typ.embedded))
2379 edges = b.push_edge(edges, b.make_list_field_decl(typ.fields))
2380 return b.emit_simple(.typ_anon_struct, token.Pos{}, edges)
2381 }
2382 ArrayFixedType {
2383 mut edges := []FlatEdge{cap: 2}
2384 edges = b.push_expr(edges, typ.len)
2385 edges = b.push_expr(edges, typ.elem_type)
2386 return b.emit_simple(.typ_array_fixed, token.Pos{}, edges)
2387 }
2388 ArrayType {
2389 mut edges := []FlatEdge{cap: 1}
2390 edges = b.push_expr(edges, typ.elem_type)
2391 return b.emit_simple(.typ_array, token.Pos{}, edges)
2392 }
2393 ChannelType {
2394 mut edges := []FlatEdge{cap: 2}
2395 edges = b.push_expr(edges, typ.cap)
2396 edges = b.push_expr(edges, typ.elem_type)
2397 return b.emit_simple(.typ_channel, token.Pos{}, edges)
2398 }
2399 FnType {
2400 mut edges := []FlatEdge{cap: 3}
2401 edges = b.push_edge(edges, b.make_list_expr(typ.generic_params))
2402 edges = b.push_edge(edges, b.make_list_parameter(typ.params))
2403 edges = b.push_expr(edges, typ.return_type)
2404 return b.emit_simple(.typ_fn, token.Pos{}, edges)
2405 }
2406 GenericType {
2407 mut edges := []FlatEdge{cap: 1 + typ.params.len}
2408 edges = b.push_expr(edges, typ.name)
2409 for p in typ.params {
2410 edges = b.push_expr(edges, p)
2411 }
2412 return b.emit_simple(.typ_generic, token.Pos{}, edges)
2413 }
2414 MapType {
2415 mut edges := []FlatEdge{cap: 2}
2416 edges = b.push_expr(edges, typ.key_type)
2417 edges = b.push_expr(edges, typ.value_type)
2418 return b.emit_simple(.typ_map, token.Pos{}, edges)
2419 }
2420 NilType {
2421 return b.emit_simple(.typ_nil, token.Pos{}, []FlatEdge{})
2422 }
2423 NoneType {
2424 return b.emit_simple(.typ_none, token.Pos{}, []FlatEdge{})
2425 }
2426 OptionType {
2427 mut edges := []FlatEdge{cap: 1}
2428 edges = b.push_expr(edges, typ.base_type)
2429 return b.emit_simple(.typ_option, token.Pos{}, edges)
2430 }
2431 PointerType {
2432 mut edges := []FlatEdge{cap: 1}
2433 edges = b.push_expr(edges, typ.base_type)
2434 return b.emit(.typ_pointer, token.Pos{}, b.intern(typ.lifetime), -1, 0, 0, edges)
2435 }
2436 ResultType {
2437 mut edges := []FlatEdge{cap: 1}
2438 edges = b.push_expr(edges, typ.base_type)
2439 return b.emit_simple(.typ_result, token.Pos{}, edges)
2440 }
2441 ThreadType {
2442 mut edges := []FlatEdge{cap: 1}
2443 edges = b.push_expr(edges, typ.elem_type)
2444 return b.emit_simple(.typ_thread, token.Pos{}, edges)
2445 }
2446 TupleType {
2447 mut edges := []FlatEdge{cap: typ.types.len}
2448 for t in typ.types {
2449 edges = b.push_expr(edges, t)
2450 }
2451 return b.emit_simple(.typ_tuple, token.Pos{}, edges)
2452 }
2453 }
2454}
2455
2456fn (mut b FlatBuilder) add_attribute(attr Attribute) FlatNodeId {
2457 mut edges := []FlatEdge{cap: 2}
2458 edges = b.push_expr(edges, attr.value)
2459 edges = b.push_expr(edges, attr.comptime_cond)
2460 return b.emit(.aux_attribute, token.Pos{}, b.intern(attr.name), -1, 0, 0, edges)
2461}
2462
2463fn (mut b FlatBuilder) add_field_init(field FieldInit) FlatNodeId {
2464 mut edges := []FlatEdge{cap: 1}
2465 edges = b.push_expr(edges, field.value)
2466 return b.emit(.aux_field_init, token.Pos{}, b.intern(field.name), -1, 0, 0, edges)
2467}
2468
2469fn field_decl_flags(field FieldDecl) u8 {
2470 mut flags := u8(0)
2471 if field.is_public {
2472 flags |= flag_is_public
2473 }
2474 if field.is_mut {
2475 flags |= flag_is_mut
2476 }
2477 if field.is_module_mut {
2478 flags |= flag_field_is_module_mut
2479 }
2480 if field.is_interface_method {
2481 flags |= flag_field_is_interface_method
2482 }
2483 return flags
2484}
2485
2486fn (mut b FlatBuilder) add_field_decl(field FieldDecl) FlatNodeId {
2487 mut edges := []FlatEdge{cap: 3}
2488 edges = b.push_expr(edges, field.typ)
2489 edges = b.push_expr(edges, field.value)
2490 edges = b.push_edge(edges, b.make_list_attribute(field.attributes))
2491 return b.emit(.aux_field_decl, token.Pos{}, b.intern(field.name), -1, 0,
2492 field_decl_flags(field), edges)
2493}
2494
2495fn (mut b FlatBuilder) add_parameter(param Parameter) FlatNodeId {
2496 mut flags := u8(0)
2497 if param.is_mut {
2498 flags |= flag_is_mut
2499 }
2500 mut edges := []FlatEdge{cap: 1}
2501 edges = b.push_expr(edges, param.typ)
2502 return b.emit(.aux_parameter, param.pos, b.intern(param.name), -1, 0, flags, edges)
2503}
2504
2505fn (mut b FlatBuilder) add_match_branch(branch MatchBranch) FlatNodeId {
2506 mut edges := []FlatEdge{cap: 2}
2507 edges = b.push_edge(edges, b.make_list_expr(branch.cond))
2508 edges = b.push_edge(edges, b.make_list_stmt(branch.stmts))
2509 return b.emit_simple(.aux_match_branch, branch.pos, edges)
2510}
2511
2512fn (mut b FlatBuilder) add_string_inter(inter StringInter) FlatNodeId {
2513 mut edges := []FlatEdge{cap: 4}
2514 edges = b.push_expr(edges, inter.expr)
2515 edges = b.push_expr(edges, inter.format_expr)
2516 edges = b.push_edge(edges, b.emit_int(inter.width))
2517 edges = b.push_edge(edges, b.emit_int(inter.precision))
2518 return b.emit(.aux_string_inter, token.Pos{}, b.intern(inter.resolved_fmt), -1,
2519 u16(int(inter.format)), 0, edges)
2520}
2521
2522// LegacyAstWalker estimates dynamic memory usage of the recursive AST for diagnostics.
2523struct LegacyAstWalker {
2524mut:
2525 stats LegacyAstStats
2526}
2527
2528fn (mut w LegacyAstWalker) walk_files(files []File) {
2529 w.stats.files = files.len
2530 w.stats.node_bytes += u64(files.len) * u64(sizeof(File))
2531 w.add_array_storage(sizeof(File), files.len)
2532 for file in files {
2533 w.walk_file(file)
2534 }
2535 w.stats.bytes_estimate = w.stats.node_bytes + w.stats.array_bytes + w.stats.string_bytes
2536}
2537
2538fn (mut w LegacyAstWalker) add_array_storage(elem_size u32, len int) {
2539 if len <= 0 || elem_size == 0 {
2540 return
2541 }
2542 w.stats.array_bytes += u64(elem_size) * u64(len)
2543 w.stats.allocs++
2544}
2545
2546fn (mut w LegacyAstWalker) add_string(s string) {
2547 w.stats.string_entries++
2548 w.stats.string_bytes += u64(s.len)
2549 if s.len > 0 {
2550 w.stats.allocs++
2551 }
2552}
2553
2554// add_variant_payload accounts for one heap allocation behind a sumtype slot
2555// (e.g., AssignStmt, CallExpr, ArrayType) plus its struct bytes.
2556fn (mut w LegacyAstWalker) add_variant_payload(size u32) {
2557 w.stats.node_bytes += u64(size)
2558 w.stats.allocs++
2559}
2560
2561fn (mut w LegacyAstWalker) walk_file(file File) {
2562 w.add_string(file.mod)
2563 w.add_string(file.name)
2564 w.walk_attribute_array(file.attributes)
2565 w.walk_import_array(file.imports)
2566 w.walk_stmt_array(file.stmts)
2567}
2568
2569fn (mut w LegacyAstWalker) walk_stmt(stmt Stmt) {
2570 if stmt is []Attribute {
2571 w.walk_attribute_array(stmt)
2572 return
2573 }
2574 w.stats.stmt_nodes++
2575 // Sumtype slot is counted by parent (array storage / field sizeof).
2576 // Add only the heap-allocated variant payload.
2577 match stmt {
2578 AssignStmt {
2579 w.add_variant_payload(sizeof(AssignStmt))
2580 w.walk_expr_array(stmt.lhs)
2581 w.walk_expr_array(stmt.rhs)
2582 }
2583 AssertStmt {
2584 w.add_variant_payload(sizeof(AssertStmt))
2585 w.walk_expr(stmt.expr)
2586 w.walk_expr(stmt.extra)
2587 }
2588 AsmStmt {
2589 w.add_variant_payload(sizeof(AsmStmt))
2590 w.add_string(stmt.arch)
2591 }
2592 BlockStmt {
2593 w.add_variant_payload(sizeof(BlockStmt))
2594 w.walk_stmt_array(stmt.stmts)
2595 }
2596 ComptimeStmt {
2597 w.add_variant_payload(sizeof(ComptimeStmt))
2598 w.walk_stmt(stmt.stmt)
2599 }
2600 ConstDecl {
2601 w.add_variant_payload(sizeof(ConstDecl))
2602 w.walk_field_init_array(stmt.fields)
2603 }
2604 DeferStmt {
2605 w.add_variant_payload(sizeof(DeferStmt))
2606 w.walk_stmt_array(stmt.stmts)
2607 }
2608 Directive {
2609 w.add_variant_payload(sizeof(Directive))
2610 w.add_string(stmt.name)
2611 w.add_string(stmt.value)
2612 w.add_string(stmt.ct_cond)
2613 }
2614 EmptyStmt {}
2615 EnumDecl {
2616 w.add_variant_payload(sizeof(EnumDecl))
2617 w.walk_attribute_array(stmt.attributes)
2618 w.add_string(stmt.name)
2619 w.walk_expr(stmt.as_type)
2620 w.walk_field_decl_array(stmt.fields)
2621 }
2622 ExprStmt {
2623 w.add_variant_payload(sizeof(ExprStmt))
2624 w.walk_expr(stmt.expr)
2625 }
2626 FlowControlStmt {
2627 w.add_variant_payload(sizeof(FlowControlStmt))
2628 w.add_string(stmt.label)
2629 }
2630 FnDecl {
2631 w.add_variant_payload(sizeof(FnDecl))
2632 w.walk_attribute_array(stmt.attributes)
2633 if stmt.is_method {
2634 w.walk_parameter(stmt.receiver)
2635 } else {
2636 w.walk_parameter(Parameter{
2637 typ: empty_expr
2638 })
2639 }
2640 w.add_string(stmt.name)
2641 w.walk_type(Type(stmt.typ))
2642 w.walk_stmt_array(stmt.stmts)
2643 }
2644 ForInStmt {
2645 w.add_variant_payload(sizeof(ForInStmt))
2646 w.walk_expr(stmt.key)
2647 w.walk_expr(stmt.value)
2648 w.walk_expr(stmt.expr)
2649 }
2650 ForStmt {
2651 w.add_variant_payload(sizeof(ForStmt))
2652 w.walk_stmt(stmt.init)
2653 w.walk_expr(stmt.cond)
2654 w.walk_stmt(stmt.post)
2655 w.walk_stmt_array(stmt.stmts)
2656 }
2657 GlobalDecl {
2658 w.add_variant_payload(sizeof(GlobalDecl))
2659 w.walk_attribute_array(stmt.attributes)
2660 w.walk_field_decl_array(stmt.fields)
2661 }
2662 ImportStmt {
2663 w.add_variant_payload(sizeof(ImportStmt))
2664 w.add_string(stmt.name)
2665 w.add_string(stmt.alias)
2666 w.walk_expr_array(stmt.symbols)
2667 }
2668 InterfaceDecl {
2669 w.add_variant_payload(sizeof(InterfaceDecl))
2670 w.walk_attribute_array(stmt.attributes)
2671 w.add_string(stmt.name)
2672 w.walk_expr_array(stmt.generic_params)
2673 w.walk_expr_array(stmt.embedded)
2674 w.walk_field_decl_array(stmt.fields)
2675 }
2676 LabelStmt {
2677 w.add_variant_payload(sizeof(LabelStmt))
2678 w.add_string(stmt.name)
2679 w.walk_stmt(stmt.stmt)
2680 }
2681 ModuleStmt {
2682 w.add_variant_payload(sizeof(ModuleStmt))
2683 w.add_string(stmt.name)
2684 }
2685 ReturnStmt {
2686 w.add_variant_payload(sizeof(ReturnStmt))
2687 w.walk_expr_array(stmt.exprs)
2688 }
2689 StructDecl {
2690 w.add_variant_payload(sizeof(StructDecl))
2691 w.walk_attribute_array(stmt.attributes)
2692 w.add_string(stmt.name)
2693 w.walk_expr_array(stmt.implements)
2694 w.walk_expr_array(stmt.embedded)
2695 w.walk_expr_array(stmt.generic_params)
2696 w.walk_field_decl_array(stmt.fields)
2697 }
2698 TypeDecl {
2699 w.add_variant_payload(sizeof(TypeDecl))
2700 w.add_string(stmt.name)
2701 w.walk_expr(stmt.base_type)
2702 w.walk_expr_array(stmt.generic_params)
2703 w.walk_expr_array(stmt.variants)
2704 }
2705 []Attribute {}
2706 }
2707}
2708
2709fn (mut w LegacyAstWalker) walk_expr(expr Expr) {
2710 match expr {
2711 Type {
2712 w.walk_type(expr)
2713 return
2714 }
2715 FieldInit {
2716 w.walk_field_init(expr)
2717 return
2718 }
2719 else {}
2720 }
2721
2722 w.stats.expr_nodes++
2723 // Sumtype slot is counted by parent; add only heap variant payload.
2724 match expr {
2725 ArrayInitExpr {
2726 w.add_variant_payload(sizeof(ArrayInitExpr))
2727 w.walk_expr(expr.typ)
2728 w.walk_expr_array(expr.exprs)
2729 w.walk_expr(expr.init)
2730 w.walk_expr(expr.cap)
2731 w.walk_expr(expr.len)
2732 w.walk_expr(expr.update_expr)
2733 }
2734 AsCastExpr {
2735 w.add_variant_payload(sizeof(AsCastExpr))
2736 w.walk_expr(expr.expr)
2737 w.walk_expr(expr.typ)
2738 }
2739 AssocExpr {
2740 w.add_variant_payload(sizeof(AssocExpr))
2741 w.walk_expr(expr.typ)
2742 w.walk_expr(expr.expr)
2743 w.walk_field_init_array(expr.fields)
2744 }
2745 BasicLiteral {
2746 w.add_variant_payload(sizeof(BasicLiteral))
2747 w.add_string(expr.value)
2748 }
2749 CallExpr {
2750 w.add_variant_payload(sizeof(CallExpr))
2751 w.walk_expr(expr.lhs)
2752 w.walk_expr_array(expr.args)
2753 }
2754 CallOrCastExpr {
2755 w.add_variant_payload(sizeof(CallOrCastExpr))
2756 w.walk_expr(expr.lhs)
2757 w.walk_expr(expr.expr)
2758 }
2759 CastExpr {
2760 w.add_variant_payload(sizeof(CastExpr))
2761 w.walk_expr(expr.typ)
2762 w.walk_expr(expr.expr)
2763 }
2764 ComptimeExpr {
2765 w.add_variant_payload(sizeof(ComptimeExpr))
2766 w.walk_expr(expr.expr)
2767 }
2768 EmptyExpr {}
2769 FnLiteral {
2770 w.add_variant_payload(sizeof(FnLiteral))
2771 w.walk_type(Type(expr.typ))
2772 w.walk_expr_array(expr.captured_vars)
2773 w.walk_stmt_array(expr.stmts)
2774 }
2775 GenericArgOrIndexExpr {
2776 w.add_variant_payload(sizeof(GenericArgOrIndexExpr))
2777 w.walk_expr(expr.lhs)
2778 w.walk_expr(expr.expr)
2779 }
2780 GenericArgs {
2781 w.add_variant_payload(sizeof(GenericArgs))
2782 w.walk_expr(expr.lhs)
2783 w.walk_expr_array(expr.args)
2784 }
2785 Ident {
2786 w.add_variant_payload(sizeof(Ident))
2787 w.add_string(expr.name)
2788 }
2789 IfExpr {
2790 w.add_variant_payload(sizeof(IfExpr))
2791 w.walk_expr(expr.cond)
2792 w.walk_expr(expr.else_expr)
2793 w.walk_stmt_array(expr.stmts)
2794 }
2795 IfGuardExpr {
2796 w.add_variant_payload(sizeof(IfGuardExpr))
2797 w.walk_stmt(Stmt(expr.stmt))
2798 }
2799 IndexExpr {
2800 w.add_variant_payload(sizeof(IndexExpr))
2801 w.walk_expr(expr.lhs)
2802 w.walk_expr(expr.expr)
2803 }
2804 InfixExpr {
2805 w.add_variant_payload(sizeof(InfixExpr))
2806 w.walk_expr(expr.lhs)
2807 w.walk_expr(expr.rhs)
2808 }
2809 InitExpr {
2810 w.add_variant_payload(sizeof(InitExpr))
2811 w.walk_expr(expr.typ)
2812 w.walk_field_init_array(expr.fields)
2813 }
2814 Keyword {}
2815 KeywordOperator {
2816 w.add_variant_payload(sizeof(KeywordOperator))
2817 w.walk_expr_array(expr.exprs)
2818 }
2819 LambdaExpr {
2820 w.add_variant_payload(sizeof(LambdaExpr))
2821 w.walk_ident_array(expr.args)
2822 w.walk_expr(expr.expr)
2823 }
2824 LifetimeExpr {
2825 w.add_variant_payload(sizeof(LifetimeExpr))
2826 w.add_string(expr.name)
2827 }
2828 LockExpr {
2829 w.add_variant_payload(sizeof(LockExpr))
2830 w.walk_expr_array(expr.lock_exprs)
2831 w.walk_expr_array(expr.rlock_exprs)
2832 w.walk_stmt_array(expr.stmts)
2833 }
2834 MapInitExpr {
2835 w.add_variant_payload(sizeof(MapInitExpr))
2836 w.walk_expr(expr.typ)
2837 w.walk_expr_array(expr.keys)
2838 w.walk_expr_array(expr.vals)
2839 }
2840 MatchExpr {
2841 w.add_variant_payload(sizeof(MatchExpr))
2842 w.walk_expr(expr.expr)
2843 w.walk_match_branch_array(expr.branches)
2844 }
2845 ModifierExpr {
2846 w.add_variant_payload(sizeof(ModifierExpr))
2847 w.walk_expr(expr.expr)
2848 }
2849 OrExpr {
2850 w.add_variant_payload(sizeof(OrExpr))
2851 w.walk_expr(expr.expr)
2852 w.walk_stmt_array(expr.stmts)
2853 }
2854 ParenExpr {
2855 w.add_variant_payload(sizeof(ParenExpr))
2856 w.walk_expr(expr.expr)
2857 }
2858 PostfixExpr {
2859 w.add_variant_payload(sizeof(PostfixExpr))
2860 w.walk_expr(expr.expr)
2861 }
2862 PrefixExpr {
2863 w.add_variant_payload(sizeof(PrefixExpr))
2864 w.walk_expr(expr.expr)
2865 }
2866 RangeExpr {
2867 w.add_variant_payload(sizeof(RangeExpr))
2868 w.walk_expr(expr.start)
2869 w.walk_expr(expr.end)
2870 }
2871 SelectExpr {
2872 w.add_variant_payload(sizeof(SelectExpr))
2873 w.walk_stmt(expr.stmt)
2874 w.walk_stmt_array(expr.stmts)
2875 w.walk_expr(expr.next)
2876 }
2877 SelectorExpr {
2878 w.add_variant_payload(sizeof(SelectorExpr))
2879 w.walk_expr(expr.lhs)
2880 w.walk_ident(expr.rhs)
2881 }
2882 SqlExpr {
2883 w.add_variant_payload(sizeof(SqlExpr))
2884 w.walk_expr(expr.expr)
2885 w.add_string(expr.table_name)
2886 }
2887 StringInterLiteral {
2888 w.add_variant_payload(sizeof(StringInterLiteral))
2889 w.walk_string_array(expr.values)
2890 w.walk_string_inter_array(expr.inters)
2891 }
2892 StringLiteral {
2893 w.add_variant_payload(sizeof(StringLiteral))
2894 w.add_string(expr.value)
2895 }
2896 Tuple {
2897 w.add_variant_payload(sizeof(Tuple))
2898 w.walk_expr_array(expr.exprs)
2899 }
2900 UnsafeExpr {
2901 w.add_variant_payload(sizeof(UnsafeExpr))
2902 w.walk_stmt_array(expr.stmts)
2903 }
2904 FieldInit, Type {}
2905 }
2906}
2907
2908fn (mut w LegacyAstWalker) walk_type(typ Type) {
2909 w.stats.type_nodes++
2910 // Sumtype slot is counted by parent; add only heap variant payload.
2911 match typ {
2912 AnonStructType {
2913 w.add_variant_payload(sizeof(AnonStructType))
2914 w.walk_expr_array(typ.generic_params)
2915 w.walk_expr_array(typ.embedded)
2916 w.walk_field_decl_array(typ.fields)
2917 }
2918 ArrayFixedType {
2919 w.add_variant_payload(sizeof(ArrayFixedType))
2920 w.walk_expr(typ.len)
2921 w.walk_expr(typ.elem_type)
2922 }
2923 ArrayType {
2924 w.add_variant_payload(sizeof(ArrayType))
2925 w.walk_expr(typ.elem_type)
2926 }
2927 ChannelType {
2928 w.add_variant_payload(sizeof(ChannelType))
2929 w.walk_expr(typ.cap)
2930 w.walk_expr(typ.elem_type)
2931 }
2932 FnType {
2933 w.add_variant_payload(sizeof(FnType))
2934 w.walk_expr_array(typ.generic_params)
2935 w.walk_parameter_array(typ.params)
2936 w.walk_expr(typ.return_type)
2937 }
2938 GenericType {
2939 w.add_variant_payload(sizeof(GenericType))
2940 w.walk_expr(typ.name)
2941 w.walk_expr_array(typ.params)
2942 }
2943 MapType {
2944 w.add_variant_payload(sizeof(MapType))
2945 w.walk_expr(typ.key_type)
2946 w.walk_expr(typ.value_type)
2947 }
2948 NilType {}
2949 NoneType {}
2950 OptionType {
2951 w.add_variant_payload(sizeof(OptionType))
2952 w.walk_expr(typ.base_type)
2953 }
2954 PointerType {
2955 w.add_variant_payload(sizeof(PointerType))
2956 w.walk_expr(typ.base_type)
2957 w.add_string(typ.lifetime)
2958 }
2959 ResultType {
2960 w.add_variant_payload(sizeof(ResultType))
2961 w.walk_expr(typ.base_type)
2962 }
2963 ThreadType {
2964 w.add_variant_payload(sizeof(ThreadType))
2965 w.walk_expr(typ.elem_type)
2966 }
2967 TupleType {
2968 w.add_variant_payload(sizeof(TupleType))
2969 w.walk_expr_array(typ.types)
2970 }
2971 }
2972}
2973
2974// Plain-struct walkers: storage is counted at parent (inline field) or array
2975// level. Only scan for dynamic content here.
2976
2977fn (mut w LegacyAstWalker) walk_attribute(attr Attribute) {
2978 w.stats.aux_nodes++
2979 w.add_string(attr.name)
2980 w.walk_expr(attr.value)
2981 w.walk_expr(attr.comptime_cond)
2982}
2983
2984fn (mut w LegacyAstWalker) walk_field_init(field FieldInit) {
2985 w.stats.aux_nodes++
2986 w.add_string(field.name)
2987 w.walk_expr(field.value)
2988}
2989
2990fn (mut w LegacyAstWalker) walk_field_decl(field FieldDecl) {
2991 w.stats.aux_nodes++
2992 w.add_string(field.name)
2993 w.walk_expr(field.typ)
2994 w.walk_expr(field.value)
2995 w.walk_attribute_array(field.attributes)
2996}
2997
2998fn (mut w LegacyAstWalker) walk_parameter(param Parameter) {
2999 w.stats.aux_nodes++
3000 w.add_string(param.name)
3001 w.walk_expr(param.typ)
3002}
3003
3004fn (mut w LegacyAstWalker) walk_match_branch(branch MatchBranch) {
3005 w.stats.aux_nodes++
3006 w.walk_expr_array(branch.cond)
3007 w.walk_stmt_array(branch.stmts)
3008}
3009
3010fn (mut w LegacyAstWalker) walk_string_inter(inter StringInter) {
3011 w.stats.aux_nodes++
3012 w.walk_expr(inter.expr)
3013 w.walk_expr(inter.format_expr)
3014 w.add_string(inter.resolved_fmt)
3015}
3016
3017fn (mut w LegacyAstWalker) walk_ident(ident Ident) {
3018 w.walk_expr(Expr(ident))
3019}
3020
3021fn (mut w LegacyAstWalker) walk_expr_array(items []Expr) {
3022 w.add_array_storage(sizeof(Expr), items.len)
3023 for item in items {
3024 w.walk_expr(item)
3025 }
3026}
3027
3028fn (mut w LegacyAstWalker) walk_stmt_array(items []Stmt) {
3029 w.add_array_storage(sizeof(Stmt), items.len)
3030 for item in items {
3031 w.walk_stmt(item)
3032 }
3033}
3034
3035fn (mut w LegacyAstWalker) walk_attribute_array(items []Attribute) {
3036 w.add_array_storage(sizeof(Attribute), items.len)
3037 for item in items {
3038 w.walk_attribute(item)
3039 }
3040}
3041
3042fn (mut w LegacyAstWalker) walk_field_init_array(items []FieldInit) {
3043 w.add_array_storage(sizeof(FieldInit), items.len)
3044 for item in items {
3045 w.walk_field_init(item)
3046 }
3047}
3048
3049fn (mut w LegacyAstWalker) walk_field_decl_array(items []FieldDecl) {
3050 w.add_array_storage(sizeof(FieldDecl), items.len)
3051 for item in items {
3052 w.walk_field_decl(item)
3053 }
3054}
3055
3056fn (mut w LegacyAstWalker) walk_parameter_array(items []Parameter) {
3057 w.add_array_storage(sizeof(Parameter), items.len)
3058 for item in items {
3059 w.walk_parameter(item)
3060 }
3061}
3062
3063fn (mut w LegacyAstWalker) walk_match_branch_array(items []MatchBranch) {
3064 w.add_array_storage(sizeof(MatchBranch), items.len)
3065 for item in items {
3066 w.walk_match_branch(item)
3067 }
3068}
3069
3070fn (mut w LegacyAstWalker) walk_string_inter_array(items []StringInter) {
3071 w.add_array_storage(sizeof(StringInter), items.len)
3072 for item in items {
3073 w.walk_string_inter(item)
3074 }
3075}
3076
3077fn (mut w LegacyAstWalker) walk_ident_array(items []Ident) {
3078 w.add_array_storage(sizeof(Ident), items.len)
3079 for item in items {
3080 w.walk_ident(item)
3081 }
3082}
3083
3084fn (mut w LegacyAstWalker) walk_import_array(items []ImportStmt) {
3085 w.add_array_storage(sizeof(ImportStmt), items.len)
3086 for item in items {
3087 w.walk_stmt(Stmt(item))
3088 }
3089}
3090
3091fn (mut w LegacyAstWalker) walk_string_array(items []string) {
3092 w.add_array_storage(sizeof(string), items.len)
3093 for item in items {
3094 w.add_string(item)
3095 }
3096}
3097