diff --git a/scripts/duffle_emit.lua b/scripts/duffle_emit.lua index 7b9e34c..0c3bb98 100644 --- a/scripts/duffle_emit.lua +++ b/scripts/duffle_emit.lua @@ -1041,6 +1041,16 @@ function M.find_atom_proc_decl_for(source, before_pos, mips_atom_ptr_len) if source:sub(next_pos, next_pos) == "(" then local inner, after_paren = M.read_parens(source, next_pos) ---@type string|nil, integer if inner then + if ident == "AtomBundleEntry_" then + local tmpl = M.split_top_level_commas(inner) ---@type string[] + if #tmpl ~= 2 then return nil end + local name = M.trim(tmpl[1]) .. "_" .. M.trim(tmpl[2]) ---@type string + local formals_pos = M.skip_ws_and_cmt(source, after_paren) ---@type integer + if source:sub(formals_pos, formals_pos) ~= "(" then return nil end + local real_inner, after_real = M.read_parens(source, formals_pos) ---@type string|nil, integer + if not real_inner then return nil end + return name, real_inner, name, after_real + end return ident, inner, ident, after_paren end end diff --git a/scripts/passes/report.lua b/scripts/passes/report.lua index 0f40bf0..bfa57b3 100644 --- a/scripts/passes/report.lua +++ b/scripts/passes/report.lua @@ -544,6 +544,35 @@ local function render_section_binds(add, view) if not wrote then add("_(none)_"); add("") end end +--- @param add fun(s: string): nil +--- @param view ModuleView +--- @return nil +local function render_section_atom_bundles(add, view) + local bundles = (view.corpus and view.corpus.atom_bundles) or {} ---@type table + local names = {} ---@type string[] + for name, bundle in pairs(bundles) do ---@type string, AtomBundle + if path_in_module(bundle.path, view) then + names[#names + 1] = name + end + end + if #names == 0 then add("_(none)_"); add(""); return end + table.sort(names) + for _, name in ipairs(names) do ---@type integer, string + local bundle = bundles[name] ---@type AtomBundle + local entries = bundle.entries or {} ---@type table + add(string.format("### %s", name)) + for _, slot in ipairs(bundle.slots or {}) do ---@type integer, string + local ident = entries[slot] ---@type string|nil + if ident then + add(string.format("- `%s` `%s`", slot, ident)) + else + add(string.format("- `%s`", slot)) + end + end + add("") + end +end + --- @param add fun(s: string): nil --- @param view ModuleView --- @return nil @@ -895,6 +924,7 @@ local SECTION_RENDERERS = { ---@type SectionRenderer[] { header = "## Annotations", render = render_section_annotations }, { header = "## Component annotations", render = render_section_component_annotations }, { header = "## Binds_* structs", render = render_section_binds }, + { header = "## Atom bundles", render = render_section_atom_bundles }, { header = "## Phases / views / ctx", render = render_section_phases }, { header = "## Register aliases", render = render_section_aliases }, { header = "## Auto-reg", render = render_section_autoreg }, diff --git a/scripts/passes/scan_source.lua b/scripts/passes/scan_source.lua index c7b6e74..e391d11 100644 --- a/scripts/passes/scan_source.lua +++ b/scripts/passes/scan_source.lua @@ -127,6 +127,13 @@ local parse_enum_int_literal ---@type fun(text: string, start: integer): (intege --- @field body string --- @field bytes integer|nil +--- @class AtomBundle +--- @field name string +--- @field slots string[] -- typedef order +--- @field line integer +--- @field path string +--- @field entries table|nil -- slot → B_E when an AtomBundleEntry_ proc exists + --- @class AliasEntry --- @field name string --- @field code integer @@ -208,6 +215,7 @@ local parse_enum_int_literal ---@type fun(text: string, start: integer): (intege --- @field reg_use_schemas table --- @field reg_use_errors RegUseError[] --- @field tape_chains TapeChain[] +--- @field atom_bundles table --- @field _source_file string|nil --- @field _code_macros table|nil -- bag --- @field _code_macro_bodies table|nil -- bag @@ -2439,11 +2447,76 @@ local function parse_typedef_array(source, pos, id2_end, line_of, out, after_typ return semi and (semi + 1) or after_paren end +--- Parse `MipsAtom *slot, …;` from an AtomBundle_ typedef body. +--- Ignores the MipsAtom type token. Slot name is the ident after `*`. +--- @param body string +--- @return string[] +local function parse_atom_bundle_slots(body) + local slots = {} ---@type string[] + local pos = 1 ---@type integer + while pos <= #body do + pos = duffle.skip_ws_and_cmt(body, pos) + if pos > #body then break end + local b = body:byte(pos) ---@type integer + if b == BYTE_SEMI then + break + elseif b == BYTE_COMMA then + pos = pos + 1 + elseif b == BYTE_STAR then + local after_star = duffle.skip_ws_and_cmt(body, pos + 1) ---@type integer + local ident, ident_end = duffle.read_ident(body, after_star) ---@type string|nil, integer + if ident then + slots[#slots + 1] = ident + pos = ident_end + else + pos = pos + 1 + end + else + local ident, ident_end = duffle.read_ident(body, pos) ---@type string|nil, integer + if ident then + pos = ident_end + else + pos = pos + 1 + end + end + end + return slots +end + +-- Shape 5: `typedef AtomBundle_() { MipsAtom *slot, … };` +--- @param source string +--- @param pos integer +--- @param id2_end integer +--- @param line_of fun(pos: integer): integer +--- @param out SourceScan +--- @param after_typedef integer +--- @return integer +local function parse_typedef_atom_bundle(source, pos, id2_end, line_of, out, after_typedef) + local inner, after_paren, open_paren = read_parens_after(source, id2_end, id2_end) ---@type string|nil, integer, integer + if not inner then return id2_end end + local name = duffle.trim(inner) ---@type string + + local body, after_brace = find_body_braces(source, after_paren, open_paren + 1) ---@type string|nil, integer + if not body then return after_brace end + if name ~= "" and out.atom_bundles[name] == nil then + local bundle = { ---@type AtomBundle + name = name, + slots = parse_atom_bundle_slots(body), + line = line_of(pos), + path = out._source_file or "", + } + out.atom_bundles[name] = bundle + end + attach_debug_skip_marker(out, "unrelated") + return after_brace +end + local TYPE_FORMS = { ---@type table - Struct_ = parse_typedef_struct, - Enum_ = parse_typedef_enum, - TSet_ = parse_typedef_tset, - Array_ = parse_typedef_array, + Struct_ = parse_typedef_struct, + Enum_ = parse_typedef_enum, + TSet_ = parse_typedef_tset, + Array_ = parse_typedef_array, + AtomBundle_ = parse_typedef_atom_bundle, } --- Parse: `typedef` declarations. @@ -2924,6 +2997,7 @@ local function scan_source(source, source_file, code_macros, code_macro_bodies) atoms = {}, raw_atoms = {}, binds = {}, + atom_bundles = {}, atom_infos = {}, component_atom_infos = {}, macros = {}, @@ -3238,6 +3312,7 @@ local function merge_corpus_registries(corpus) corpus.reg_use_schemas = corpus.reg_use_schemas or {} corpus.reg_use_errors = corpus.reg_use_errors or {} corpus.tape_chains = corpus.tape_chains or {} + corpus.atom_bundles = corpus.atom_bundles or {} -- Replace the existing corpus collections with empty tables so a re-run on the same corpus produces identical state (deterministic merge). -- This is safe because M.run is the only writer to these tables within a single orchestrator invocation. @@ -3255,6 +3330,7 @@ local function merge_corpus_registries(corpus) "reg_use_schemas", "reg_use_errors", "tape_chains", + "atom_bundles", }) do corpus[key] = {} end @@ -3362,6 +3438,28 @@ local function merge_corpus_registries(corpus) for _, chain in ipairs(scan.tape_chains or {}) do ---@type integer, TapeChain corpus.tape_chains[#corpus.tape_chains + 1] = chain end + + -- atom_bundles: keyed by typedef name. First-wins per name (like components). + for name, bundle in pairs(scan.atom_bundles or {}) do ---@type string, AtomBundle + if corpus.atom_bundles[name] == nil then + corpus.atom_bundles[name] = bundle + end + end + end + end + + -- Join slot roles to AtomBundleEntry_ identities after catalogs and atoms exist. + -- Do not invent a catalog from an entry that has no typedef. + for _, bundle in pairs(corpus.atom_bundles) do ---@type string, AtomBundle + local entries = {} ---@type table + for _, slot in ipairs(bundle.slots or {}) do ---@type integer, string + local atom_name = bundle.name .. "_" .. slot ---@type string + if corpus.atoms_by_name[atom_name] then + entries[slot] = atom_name + end + end + if next(entries) then + bundle.entries = entries end end end diff --git a/scripts/ps1_meta.lua b/scripts/ps1_meta.lua index f1cd390..309d694 100644 --- a/scripts/ps1_meta.lua +++ b/scripts/ps1_meta.lua @@ -77,6 +77,7 @@ local PASS_FLAG_DISPATCH_KEY = "__pass__" ---@type string --- @field atom_phases table --- @field word_counts WordCounts --- @field components table +--- @field atom_bundles table|nil --- @field collisions CorpusCollision[] --- @field resolver SourceResolver --- @field component_atom_infos AtomInfoEntry[]|nil @@ -665,6 +666,7 @@ local function build_ctx(args) atom_phases = {}, word_counts = {}, components = {}, + atom_bundles = {}, collisions = {}, resolver = resolution.resolver, }