mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-08-25 02:20:33 +00:00
utilizing trailing type annotations more
This commit is contained in:
+135
-270
@@ -22,42 +22,30 @@
|
||||
-- Uses `debug.getinfo` to find this file's own directory, so it works both standalone and when require'd from the orchestrator.
|
||||
-- Bootstrap: load `duffle_paths.lua` via `debug.getinfo(1, "S").source` (works both standalone + when require'd).
|
||||
-- duffle_paths.lua sets package.path then returns `require("duffle")` at the bottom, so the dofile value IS the duffle module.
|
||||
--- @type string
|
||||
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./"
|
||||
--- @type DuffleExport
|
||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
||||
local _bootstrap_dir = debug.getinfo(1, "S").source:match("^@?(.*[/\\])") or "./" ---@type string
|
||||
local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua") ---@type DuffleExport
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- Constants
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
-- Atom component declaration identifiers.
|
||||
--- @type string
|
||||
local ATOM_COMP_PROC = "MipsAtomComp_Proc_"
|
||||
--- @type string
|
||||
local MIPS_ATOM = "Slice_MipsCode" -- prefix on the function declaration that wraps an AtomComp_Proc_
|
||||
local ATOM_COMP_PROC = "MipsAtomComp_Proc_" ---@type string
|
||||
local MIPS_ATOM = "Slice_MipsCode" ---@type string -- prefix on the function declaration that wraps an AtomComp_Proc_
|
||||
|
||||
-- Component-name prefixes.
|
||||
--- @type string
|
||||
local AC_PREFIX = "ac_" -- arg to MipsAtomComp_(ac_X); the X is the atom name
|
||||
--- @type integer
|
||||
local AC_PREFIX_LEN = 3
|
||||
--- @type string
|
||||
local MAC_PREFIX = "mac_" -- prefix on generated macros; the rest is the atom name
|
||||
--- @type integer
|
||||
local MAC_PREFIX_LEN = 4
|
||||
local AC_PREFIX = "ac_" ---@type string -- arg to MipsAtomComp_(ac_X); the X is the atom name
|
||||
local AC_PREFIX_LEN = 3 ---@type integer
|
||||
local MAC_PREFIX = "mac_" ---@type string -- prefix on generated macros; the rest is the atom name
|
||||
local MAC_PREFIX_LEN = 4 ---@type integer
|
||||
|
||||
-- ASCII byte values used in tokenization.
|
||||
--- @type integer
|
||||
local BYTE_NEWLINE = 10
|
||||
--- @type integer
|
||||
local BYTE_SLASH = 47
|
||||
local BYTE_NEWLINE = 10 ---@type integer
|
||||
local BYTE_SLASH = 47 ---@type integer
|
||||
|
||||
-- Output gen subdirectory + filename (per-directory aggregation; the directory name is the namespace).
|
||||
--- @type string
|
||||
local GEN_SUBDIR = "gen"
|
||||
--- @type string
|
||||
local MACS_FILENAME = "macs.h"
|
||||
local GEN_SUBDIR = "gen" ---@type string
|
||||
local MACS_FILENAME = "macs.h" ---@type string
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- Type declarations
|
||||
@@ -100,8 +88,7 @@ local MACS_FILENAME = "macs.h"
|
||||
-- Local helpers (file I/O + path normalization)
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
--- @type ComponentsPass
|
||||
local M = {}
|
||||
local M = {} ---@type ComponentsPass
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- Back-walk helpers (composed into the entry point below: find_function_args_for)
|
||||
@@ -123,8 +110,7 @@ local M = {}
|
||||
--- @param before_pos integer
|
||||
--- @return string|nil
|
||||
local function find_function_args_for(source, name, before_pos)
|
||||
--- @type string|nil, string|nil
|
||||
local _, args_inner = duffle.find_function_decl_for(source, before_pos, #MIPS_ATOM)
|
||||
local _, args_inner = duffle.find_function_decl_for(source, before_pos, #MIPS_ATOM) ---@type string|nil, string|nil
|
||||
return args_inner
|
||||
end
|
||||
|
||||
@@ -140,31 +126,24 @@ end
|
||||
--- @return string[]|nil
|
||||
local function extract_arg_names(args_str)
|
||||
if not args_str or args_str == "" then return nil end
|
||||
--- @type string[]
|
||||
local names = {}
|
||||
--- @type string[]
|
||||
local tokens = duffle.split_top_level_commas(args_str)
|
||||
--- @type integer, string
|
||||
for _, tok in ipairs(tokens) do
|
||||
--- @type string
|
||||
local trimmed = duffle.trim(tok)
|
||||
local names = {} ---@type string[]
|
||||
local tokens = duffle.split_top_level_commas(args_str) ---@type string[]
|
||||
for _, tok in ipairs(tokens) do ---@type integer, string
|
||||
local trimmed = duffle.trim(tok) ---@type string
|
||||
if trimmed ~= "" then
|
||||
-- Strip trailing block comment (/* ... */) from the token, if present.
|
||||
-- split_top_level_commas only skips block comments at TOP LEVEL (between commas),
|
||||
-- not block comments embedded WITHIN a token between a parameter and a trailing comma.
|
||||
-- Without this strip, the identifier-walk below stops at the `/` of `*/` and returns
|
||||
-- the wrong name (or nothing). See `test_extract_arg_names_handles_trailing_block_comments`.
|
||||
--- @type integer
|
||||
local trimmed_end = #trimmed
|
||||
local trimmed_end = #trimmed ---@type integer
|
||||
if trimmed_end >= 2 and trimmed:sub(trimmed_end - 1, trimmed_end) == "*/" then
|
||||
-- Find the matching `/*` that opens the trailing comment.
|
||||
-- Walk back from the `*/` looking for `/*` (whitespace + `/*`).
|
||||
--- @type integer
|
||||
local close_pos = trimmed_end - 1 -- position of the second-to-last char
|
||||
local close_pos = trimmed_end - 1 ---@type integer -- position of the second-to-last char
|
||||
-- Walk back: skip trailing whitespace, then look for the `/*` opener.
|
||||
while close_pos > 1 do
|
||||
--- @type string
|
||||
local ch = trimmed:sub(close_pos, close_pos)
|
||||
local ch = trimmed:sub(close_pos, close_pos) ---@type string
|
||||
if ch == " " or ch == "\t" or ch == "\n" or ch == "\r" then
|
||||
close_pos = close_pos - 1
|
||||
else
|
||||
@@ -172,10 +151,8 @@ local function extract_arg_names(args_str)
|
||||
end
|
||||
end
|
||||
-- Now scan back from close_pos for the `/*` opener (slashes are at close_pos-1 and close_pos-2).
|
||||
--- @type integer|nil
|
||||
local opener_pos = nil
|
||||
--- @type integer
|
||||
local scan = close_pos - 3
|
||||
local opener_pos = nil ---@type integer|nil
|
||||
local scan = close_pos - 3 ---@type integer
|
||||
while scan >= 1 do
|
||||
if trimmed:sub(scan, scan + 1) == "/*" then
|
||||
opener_pos = scan
|
||||
@@ -194,11 +171,9 @@ local function extract_arg_names(args_str)
|
||||
trimmed_end = #trimmed
|
||||
if trimmed_end >= 4 and trimmed:sub(trimmed_end, trimmed_end) == "]" then
|
||||
-- Walk back: skip digits, expect `[`.
|
||||
--- @type integer
|
||||
local bracket_pos = trimmed_end - 1
|
||||
local bracket_pos = trimmed_end - 1 ---@type integer
|
||||
while bracket_pos > 1 do
|
||||
--- @type string
|
||||
local ch = trimmed:sub(bracket_pos, bracket_pos)
|
||||
local ch = trimmed:sub(bracket_pos, bracket_pos) ---@type string
|
||||
if ch >= "0" and ch <= "9" then
|
||||
bracket_pos = bracket_pos - 1
|
||||
else
|
||||
@@ -212,22 +187,18 @@ local function extract_arg_names(args_str)
|
||||
if trimmed == "" then goto continue end
|
||||
-- Find the identifier at the end: walk back over trailers (whitespace + `*` + `[]`),
|
||||
-- then walk back over the identifier chars (alnum + `_`).
|
||||
--- @type integer
|
||||
local ident_end = #trimmed
|
||||
local ident_end = #trimmed ---@type integer
|
||||
while ident_end > 0 do
|
||||
--- @type string
|
||||
local ch = trimmed:sub(ident_end, ident_end)
|
||||
local ch = trimmed:sub(ident_end, ident_end) ---@type string
|
||||
if ch == " " or ch == "\t" or ch == "*" or ch == "]" or ch == "[" then
|
||||
ident_end = ident_end - 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
--- @type integer
|
||||
local ident_start = ident_end
|
||||
local ident_start = ident_end ---@type integer
|
||||
while ident_start > 0 do
|
||||
--- @type string
|
||||
local ch = trimmed:sub(ident_start, ident_start)
|
||||
local ch = trimmed:sub(ident_start, ident_start) ---@type string
|
||||
if duffle.is_alnum_byte(string.byte(ch)) or ch == "_" then
|
||||
ident_start = ident_start - 1
|
||||
else
|
||||
@@ -235,8 +206,7 @@ local function extract_arg_names(args_str)
|
||||
end
|
||||
end
|
||||
ident_start = ident_start + 1
|
||||
--- @type string
|
||||
local name = trimmed:sub(ident_start, ident_end)
|
||||
local name = trimmed:sub(ident_start, ident_end) ---@type string
|
||||
if name ~= "" then names[#names + 1] = name end
|
||||
::continue::
|
||||
end
|
||||
@@ -248,8 +218,7 @@ end
|
||||
--- @param args_str string|nil
|
||||
--- @return string[]|nil
|
||||
local function formal_arg_names(args_str)
|
||||
--- @type string[]|nil
|
||||
local names = extract_arg_names(args_str)
|
||||
local names = extract_arg_names(args_str) ---@type string[]|nil
|
||||
if not names then return nil end
|
||||
if names[1] == "ab" then table.remove(names, 1) end
|
||||
if #names == 0 then return nil end
|
||||
@@ -271,10 +240,8 @@ end
|
||||
--- @param scan SourceScan
|
||||
--- @return Component[]
|
||||
local function project_components(source, scan)
|
||||
--- @type Component[]
|
||||
local out = {}
|
||||
--- @type integer, AtomEntry
|
||||
for _, a in ipairs(scan.atoms) do
|
||||
local out = {} ---@type Component[]
|
||||
for _, a in ipairs(scan.atoms) do ---@type integer, AtomEntry
|
||||
-- Only `MipsAtomComp_(ac_X)` (kind="comp_bare") and `MipsAtomComp_Proc_(ac_X, ...)` (kind="comp_proc")
|
||||
-- are COMPONENTS — they get inlined via `mac_<name>` aliases inside atom bodies.
|
||||
-- `MipsAtom_Proc_` (kind="atom_proc") is an ATOM (ends with `mac_yield()`); it gets emitted via
|
||||
@@ -285,12 +252,10 @@ local function project_components(source, scan)
|
||||
-- Function-args lookup is meaningful for `MipsAtomComp_Proc_` components
|
||||
-- (the macro sits inside `FI_ Slice_MipsCode ac_X(...)`); the alias expansion
|
||||
-- discards the `ab` (atom-builder) arg the same way both forms do.
|
||||
--- @type string|nil
|
||||
local args = find_function_args_for(source, a.raw_name, a.ident_pos)
|
||||
local args = find_function_args_for(source, a.raw_name, a.ident_pos) ---@type string|nil
|
||||
-- Comment ownership: scan_source.lua stamps `declaration_comment` on the record by walking backward past any associated bare marker.
|
||||
-- The pass reads `declaration_comment` directly.
|
||||
--- @type string
|
||||
local comment = a.declaration_comment or ""
|
||||
local comment = a.declaration_comment or "" ---@type string
|
||||
out[#out + 1] = {
|
||||
line = a.line,
|
||||
name = a.name,
|
||||
@@ -321,31 +286,23 @@ end
|
||||
--- @param s string
|
||||
--- @return string
|
||||
local function convert_line_comments_to_block(s)
|
||||
--- @type string
|
||||
local result = s
|
||||
--- @type integer
|
||||
local pos = 1
|
||||
--- @type integer
|
||||
local len = #result
|
||||
local result = s ---@type string
|
||||
local pos = 1 ---@type integer
|
||||
local len = #result ---@type integer
|
||||
while pos <= len do
|
||||
--- @type boolean
|
||||
local is_double_slash = result:byte(pos) == BYTE_SLASH
|
||||
local is_double_slash = result:byte(pos) == BYTE_SLASH ---@type boolean
|
||||
and pos + 1 <= len and result:byte(pos + 1) == BYTE_SLASH
|
||||
if not is_double_slash then
|
||||
pos = pos + 1
|
||||
else
|
||||
-- Find end of line.
|
||||
--- @type integer
|
||||
local eol = pos
|
||||
local eol = pos ---@type integer
|
||||
while eol <= len and result:byte(eol) ~= BYTE_NEWLINE do
|
||||
eol = eol + 1
|
||||
end
|
||||
--- @type string
|
||||
local before = result:sub(1, pos - 1)
|
||||
--- @type string
|
||||
local comment = result:sub(pos + 2, eol - 1) -- skip the `//`
|
||||
--- @type string
|
||||
local after
|
||||
local before = result:sub(1, pos - 1) ---@type string
|
||||
local comment = result:sub(pos + 2, eol - 1) ---@type string -- skip the `//`
|
||||
local after ---@type string
|
||||
if eol <= len and result:byte(eol) == BYTE_NEWLINE then
|
||||
after = " */" .. result:sub(eol) -- keep the newline
|
||||
else
|
||||
@@ -382,14 +339,11 @@ end
|
||||
--- @param tok string
|
||||
--- @return string
|
||||
local function strip_leading_delay_marker(tok)
|
||||
--- @type string|nil
|
||||
local ident = duffle.read_ident(tok, 1)
|
||||
local ident = duffle.read_ident(tok, 1) ---@type string|nil
|
||||
if not ident or not duffle.DELAY_MARKERS[ident] then return tok end
|
||||
--- @type string
|
||||
local rest = tok:sub(#ident + 1):match("^%s*(.*)$") or ""
|
||||
local rest = tok:sub(#ident + 1):match("^%s*(.*)$") or "" ---@type string
|
||||
while rest:sub(1, 2) == "/*" do
|
||||
--- @type integer|nil
|
||||
local close = rest:find("*/", 3, true)
|
||||
local close = rest:find("*/", 3, true) ---@type integer|nil
|
||||
if not close then return "" end
|
||||
rest = rest:sub(close + 2):match("^%s*(.*)$") or ""
|
||||
end
|
||||
@@ -406,24 +360,17 @@ end
|
||||
local function word_count_rec(name, comp_by_name, wc, cache)
|
||||
if cache[name] ~= nil then return cache[name] end
|
||||
cache[name] = -1 -- mark in-progress (cycle detection)
|
||||
--- @type Component|nil
|
||||
local cc = comp_by_name[name]
|
||||
--- @type integer
|
||||
local n
|
||||
local cc = comp_by_name[name] ---@type Component|nil
|
||||
local n ---@type integer
|
||||
if cc then
|
||||
n = 0
|
||||
--- @type BodyToken[]
|
||||
local tokens = cc.body_tokens
|
||||
--- @type integer, BodyToken
|
||||
for _, t in ipairs(tokens) do
|
||||
--- @type string
|
||||
local trimmed = t.tok
|
||||
local tokens = cc.body_tokens ---@type BodyToken[]
|
||||
for _, t in ipairs(tokens) do ---@type integer, BodyToken
|
||||
local trimmed = t.tok ---@type string
|
||||
if trimmed ~= "" then
|
||||
--- @type string
|
||||
local work = trimmed
|
||||
local work = trimmed ---@type string
|
||||
while true do
|
||||
--- @type string|nil
|
||||
local marker = duffle.read_ident(work, 1)
|
||||
local marker = duffle.read_ident(work, 1) ---@type string|nil
|
||||
if marker and duffle.DELAY_MARKERS[marker] then
|
||||
work = strip_leading_delay_marker(work)
|
||||
if work == "" then break end
|
||||
@@ -432,8 +379,7 @@ local function word_count_rec(name, comp_by_name, wc, cache)
|
||||
end
|
||||
end
|
||||
if work ~= "" then
|
||||
--- @type string|nil
|
||||
local lookup = strip_mac_prefix(duffle.read_ident(work, 1))
|
||||
local lookup = strip_mac_prefix(duffle.read_ident(work, 1)) ---@type string|nil
|
||||
if lookup == "atom_label" or lookup == "atom_offset" then
|
||||
-- Pure metaprogram anchors; emit zero words.
|
||||
elseif lookup and comp_by_name[lookup] then
|
||||
@@ -466,16 +412,11 @@ end
|
||||
--- @param wc WordCounts
|
||||
--- @return table<string, integer> -- bag: bare component name -> word count
|
||||
local function count_all_components(components, wc)
|
||||
--- @type table<string, Component>
|
||||
local comp_by_name = {}
|
||||
--- @type integer, Component
|
||||
for _, cc in ipairs(components) do comp_by_name[cc.name] = cc end
|
||||
--- @type table<string, integer> -- bag: memo; -1 in-progress sentinel
|
||||
local cache = {}
|
||||
--- @type table<string, integer> -- bag: bare name -> word count
|
||||
local counts = {}
|
||||
--- @type integer, Component
|
||||
for _, c in ipairs(components) do
|
||||
local comp_by_name = {} ---@type table<string, Component>
|
||||
for _, cc in ipairs(components) do comp_by_name[cc.name] = cc end ---@type integer, Component
|
||||
local cache = {} ---@type table<string, integer> -- bag: memo; -1 in-progress sentinel
|
||||
local counts = {} ---@type table<string, integer> -- bag: bare name -> word count
|
||||
for _, c in ipairs(components) do ---@type integer, Component
|
||||
counts[c.name] = word_count_rec(c.name, comp_by_name, wc, cache)
|
||||
end
|
||||
return counts
|
||||
@@ -504,34 +445,23 @@ end
|
||||
local function component_meta_rec(name, comp_by_name, latency, cache)
|
||||
if cache[name] ~= nil then return cache[name] end
|
||||
cache[name] = { cycle_cost = -1, gp0_contrib = -1 }
|
||||
--- @type Component|nil
|
||||
local cc = comp_by_name[name]
|
||||
--- @type integer
|
||||
local cycle_cost
|
||||
--- @type integer
|
||||
local gp0_contrib
|
||||
local cc = comp_by_name[name] ---@type Component|nil
|
||||
local cycle_cost ---@type integer
|
||||
local gp0_contrib ---@type integer
|
||||
if cc then
|
||||
--- @type boolean
|
||||
local skip_cycle = (name == "yield")
|
||||
--- @type boolean
|
||||
local skip_gp0 = name:match("^insert_ot_tag") ~= nil
|
||||
local skip_cycle = (name == "yield") ---@type boolean
|
||||
local skip_gp0 = name:match("^insert_ot_tag") ~= nil ---@type boolean
|
||||
cycle_cost = 0
|
||||
gp0_contrib = 0
|
||||
if not skip_cycle or not skip_gp0 then
|
||||
--- @type BodyToken[]
|
||||
local tokens = cc.body_tokens
|
||||
--- @type integer, BodyToken
|
||||
for _, t in ipairs(tokens) do
|
||||
--- @type string
|
||||
local trimmed = t.tok
|
||||
local tokens = cc.body_tokens ---@type BodyToken[]
|
||||
for _, t in ipairs(tokens) do ---@type integer, BodyToken
|
||||
local trimmed = t.tok ---@type string
|
||||
if trimmed ~= "" then
|
||||
--- @type string|nil
|
||||
local ident = duffle.read_ident(trimmed, 1)
|
||||
local ident = duffle.read_ident(trimmed, 1) ---@type string|nil
|
||||
if ident and ident:sub(1, MAC_PREFIX_LEN) == MAC_PREFIX then
|
||||
--- @type string
|
||||
local nested = ident:sub(MAC_PREFIX_LEN + 1)
|
||||
--- @type ComponentMeta
|
||||
local nested_meta = component_meta_rec(nested, comp_by_name, latency, cache)
|
||||
local nested = ident:sub(MAC_PREFIX_LEN + 1) ---@type string
|
||||
local nested_meta = component_meta_rec(nested, comp_by_name, latency, cache) ---@type ComponentMeta
|
||||
if not skip_cycle then
|
||||
cycle_cost = cycle_cost + nested_meta.cycle_cost
|
||||
end
|
||||
@@ -540,10 +470,8 @@ local function component_meta_rec(name, comp_by_name, latency, cache)
|
||||
end
|
||||
else
|
||||
if not skip_cycle then
|
||||
--- @type InstructionRow|nil
|
||||
local isa = duffle.instr(ident)
|
||||
--- @type GteCommandRow|nil
|
||||
local gte = duffle.gte(ident)
|
||||
local isa = duffle.instr(ident) ---@type InstructionRow|nil
|
||||
local gte = duffle.gte(ident) ---@type GteCommandRow|nil
|
||||
cycle_cost = cycle_cost + ((isa and isa.cycles) or (gte and gte.cycles) or latency[ident] or 1)
|
||||
end
|
||||
if not skip_gp0 then
|
||||
@@ -578,16 +506,11 @@ end
|
||||
--- @param latency table<string, integer> -- bag: ident -> cycle cost
|
||||
--- @return ComponentMetaMap
|
||||
local function compute_components_metadata(components, latency)
|
||||
--- @type table<string, Component>
|
||||
local comp_by_name = {}
|
||||
--- @type integer, Component
|
||||
for _, cc in ipairs(components) do comp_by_name[cc.name] = cc end
|
||||
--- @type ComponentMetaMap
|
||||
local cache = {}
|
||||
--- @type ComponentMetaMap
|
||||
local out = {}
|
||||
--- @type integer, Component
|
||||
for _, c in ipairs(components) do
|
||||
local comp_by_name = {} ---@type table<string, Component>
|
||||
for _, cc in ipairs(components) do comp_by_name[cc.name] = cc end ---@type integer, Component
|
||||
local cache = {} ---@type ComponentMetaMap
|
||||
local out = {} ---@type ComponentMetaMap
|
||||
for _, c in ipairs(components) do ---@type integer, Component
|
||||
out[c.name] = component_meta_rec(c.name, comp_by_name, latency, cache)
|
||||
end
|
||||
return out
|
||||
@@ -602,15 +525,11 @@ end
|
||||
--- @param s string
|
||||
--- @return string[]
|
||||
local function split_comment_lines(s)
|
||||
--- @type string[]
|
||||
local out = {}
|
||||
--- @type integer
|
||||
local pos = 1
|
||||
--- @type integer
|
||||
local s_len = #s
|
||||
local out = {} ---@type string[]
|
||||
local pos = 1 ---@type integer
|
||||
local s_len = #s ---@type integer
|
||||
while pos <= s_len do
|
||||
--- @type integer|nil
|
||||
local nl = s:find("\n", pos, true)
|
||||
local nl = s:find("\n", pos, true) ---@type integer|nil
|
||||
if not nl then
|
||||
out[#out + 1] = s:sub(pos)
|
||||
break
|
||||
@@ -629,8 +548,7 @@ end
|
||||
--- @param args_str string|nil
|
||||
--- @return string
|
||||
local function signature_from_args(args_str)
|
||||
--- @type string[]|nil
|
||||
local names = formal_arg_names(args_str)
|
||||
local names = formal_arg_names(args_str) ---@type string[]|nil
|
||||
if names then
|
||||
return table.concat(names, ", ")
|
||||
end
|
||||
@@ -642,8 +560,7 @@ end
|
||||
--- @param lines string[]
|
||||
--- @return nil
|
||||
local function strip_trailing_continuation(lines)
|
||||
--- @type string
|
||||
local last = lines[#lines]
|
||||
local last = lines[#lines] ---@type string
|
||||
if last:sub(-2) == " \\" then
|
||||
lines[#lines] = last:sub(1, -3)
|
||||
end
|
||||
@@ -669,37 +586,30 @@ end
|
||||
--- @param tok string -- a single token from split_top_level_commas (already trimmed at the start, may contain trailing whitespace + block comment)
|
||||
--- @return boolean
|
||||
local function is_pure_delay_marker_token(tok)
|
||||
--- @type table<string, boolean> -- bag: delay-marker ident -> true
|
||||
local markers = duffle.DELAY_MARKERS
|
||||
local markers = duffle.DELAY_MARKERS ---@type table<string, boolean> -- bag: delay-marker ident -> true
|
||||
if type(markers) ~= "table" then return false end
|
||||
|
||||
-- Identify a leading delay-marker identifier (e.g. `GteDelay_`).
|
||||
--- @type integer
|
||||
local ident_end = 1
|
||||
local ident_end = 1 ---@type integer
|
||||
while ident_end <= #tok do
|
||||
--- @type string
|
||||
local ch = tok:sub(ident_end, ident_end)
|
||||
local ch = tok:sub(ident_end, ident_end) ---@type string
|
||||
if ch:match("[%w_]") then
|
||||
ident_end = ident_end + 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
--- @type string
|
||||
local ident = tok:sub(1, ident_end - 1)
|
||||
local ident = tok:sub(1, ident_end - 1) ---@type string
|
||||
if not markers[ident] then return false end
|
||||
|
||||
-- Walk the remainder: only whitespace and block comments are allowed.
|
||||
--- @type integer
|
||||
local scan = ident_end
|
||||
local scan = ident_end ---@type integer
|
||||
while scan <= #tok do
|
||||
--- @type string
|
||||
local ch = tok:sub(scan, scan)
|
||||
local ch = tok:sub(scan, scan) ---@type string
|
||||
if ch:match("%s") then
|
||||
scan = scan + 1
|
||||
elseif ch == "/" and tok:sub(scan + 1, scan + 1) == "*" then
|
||||
--- @type integer|nil
|
||||
local close = tok:find("*/", scan + 2, true)
|
||||
local close = tok:find("*/", scan + 2, true) ---@type integer|nil
|
||||
if not close then return false end
|
||||
scan = close + 2
|
||||
else
|
||||
@@ -743,17 +653,14 @@ end
|
||||
--- @param tokens string[]
|
||||
--- @return nil
|
||||
local function emit_macro_body(lines, c, sig, tokens)
|
||||
--- @type integer
|
||||
for tok_idx = 1, #tokens do
|
||||
for tok_idx = 1, #tokens do ---@type integer
|
||||
tokens[tok_idx] = convert_line_comments_to_block(tokens[tok_idx])
|
||||
end
|
||||
if #tokens == 0 then return end
|
||||
lines[#lines + 1] = "#define mac_" .. c.name .. "(" .. sig .. ") \\"
|
||||
lines[#lines + 1] = "\t" .. tokens[1] .. " \\"
|
||||
--- @type integer
|
||||
for tok_idx = 2, #tokens do
|
||||
--- @type string
|
||||
local sep = token_skips_leading_comma(tokens[tok_idx]) and "\t" or ",\t"
|
||||
for tok_idx = 2, #tokens do ---@type integer
|
||||
local sep = token_skips_leading_comma(tokens[tok_idx]) and "\t" or ",\t" ---@type string
|
||||
lines[#lines + 1] = sep .. tokens[tok_idx] .. " \\"
|
||||
end
|
||||
strip_trailing_continuation(lines)
|
||||
@@ -768,8 +675,7 @@ end
|
||||
--- @param counts table<string, integer> -- bag: bare component name -> word count
|
||||
--- @return string[] -- list of lines for this component
|
||||
local function build_component_lines(c, counts)
|
||||
--- @type string[]
|
||||
local lines = {}
|
||||
local lines = {} ---@type string[]
|
||||
|
||||
-- Marker comment: emitted once for every skipped component.
|
||||
-- The marker is scanner-owned (declared by `atom_dbg_skip` immediately before the declaration in the source);
|
||||
@@ -779,21 +685,16 @@ local function build_component_lines(c, counts)
|
||||
end
|
||||
|
||||
if c.comment and c.comment ~= "" then
|
||||
--- @type integer, string
|
||||
for _, line in ipairs(split_comment_lines(c.comment)) do
|
||||
for _, line in ipairs(split_comment_lines(c.comment)) do ---@type integer, string
|
||||
lines[#lines + 1] = line
|
||||
end
|
||||
end
|
||||
|
||||
--- @type string[]
|
||||
local tokens = duffle.split_top_level_commas(c.body)
|
||||
--- @type integer
|
||||
for i = 1, #tokens do tokens[i] = duffle.trim(tokens[i]) end
|
||||
--- @type string
|
||||
local sig = signature_from_args(c.args)
|
||||
local tokens = duffle.split_top_level_commas(c.body) ---@type string[]
|
||||
for i = 1, #tokens do tokens[i] = duffle.trim(tokens[i]) end ---@type integer
|
||||
local sig = signature_from_args(c.args) ---@type string
|
||||
-- Direct lookup against the per-source precomputed `counts` table (built once by count_all_components).
|
||||
--- @type integer
|
||||
local n = counts[c.name]
|
||||
local n = counts[c.name] ---@type integer
|
||||
|
||||
if n > 0 then
|
||||
emit_macro_body(lines, c, sig, tokens)
|
||||
@@ -816,14 +717,11 @@ end
|
||||
--- @param sources SourceFile[] -- Sources contributing to this directory (for the header comment)
|
||||
--- @return string[]
|
||||
local function header_boilerplate(dir, sources)
|
||||
--- @type string[]
|
||||
local source_lines = { "// Directory: " .. duffle.to_absolute_path(dir) .. "/" }
|
||||
--- @type integer, SourceFile
|
||||
for _, src in ipairs(sources) do
|
||||
local source_lines = { "// Directory: " .. duffle.to_absolute_path(dir) .. "/" } ---@type string[]
|
||||
for _, src in ipairs(sources) do ---@type integer, SourceFile
|
||||
source_lines[#source_lines + 1] = "// source: " .. duffle.to_absolute_path(src.path)
|
||||
end
|
||||
--- @type string
|
||||
local source_blob = table.concat(source_lines, "\n")
|
||||
local source_blob = table.concat(source_lines, "\n") ---@type string
|
||||
return {
|
||||
-- #pragma once wrapped in #ifdef INTELLISENSE_DIRECTIVES, matching the convention in lottes_tape.h.
|
||||
-- The build does manual unity includes (the user controls include order), so the pragma is only active for IDE/tooling.
|
||||
@@ -851,10 +749,8 @@ end
|
||||
--- @return string -- Output directory
|
||||
--- @return string -- Full output path
|
||||
local function compute_macs_h_path(dir)
|
||||
--- @type string
|
||||
local out_dir = dir .. "/" .. GEN_SUBDIR
|
||||
--- @type string
|
||||
local out_path = out_dir .. "/" .. MACS_FILENAME
|
||||
local out_dir = dir .. "/" .. GEN_SUBDIR ---@type string
|
||||
local out_path = out_dir .. "/" .. MACS_FILENAME ---@type string
|
||||
return out_dir, out_path
|
||||
end
|
||||
|
||||
@@ -868,21 +764,16 @@ end
|
||||
--- @return string|nil -- Path to the written file (nil if no components)
|
||||
local function emit_component_macros_h(ctx, dir, sources, components, counts)
|
||||
if #components == 0 then return nil end
|
||||
--- @type string, string
|
||||
local out_dir, out_path = compute_macs_h_path(dir)
|
||||
--- @type string[]
|
||||
local lines = header_boilerplate(dir, sources)
|
||||
local out_dir, out_path = compute_macs_h_path(dir) ---@type string, string
|
||||
local lines = header_boilerplate(dir, sources) ---@type string[]
|
||||
|
||||
--- @type integer, Component
|
||||
for _, c in ipairs(components) do
|
||||
--- @type integer, string
|
||||
for _, l in ipairs(build_component_lines(c, counts)) do
|
||||
for _, c in ipairs(components) do ---@type integer, Component
|
||||
for _, l in ipairs(build_component_lines(c, counts)) do ---@type integer, string
|
||||
lines[#lines + 1] = l
|
||||
end
|
||||
end
|
||||
|
||||
--- @type string
|
||||
local content = table.concat(lines, "\n") .. "\n"
|
||||
local content = table.concat(lines, "\n") .. "\n" ---@type string
|
||||
duffle.ensure_dir(out_dir)
|
||||
duffle.write_file_lf(out_path, content)
|
||||
print(string.format(" -> %s", out_path))
|
||||
@@ -900,12 +791,9 @@ end
|
||||
--- @param counts table<string, integer> -- bag: bare component name -> word count
|
||||
--- @return nil
|
||||
local function update_canonical_word_counts(corpus, components, counts)
|
||||
--- @type WordCounts
|
||||
local wc = corpus.word_counts
|
||||
--- @type integer, Component
|
||||
for _, c in ipairs(components) do
|
||||
--- @type string
|
||||
local key = "mac_" .. c.name
|
||||
local wc = corpus.word_counts ---@type WordCounts
|
||||
for _, c in ipairs(components) do ---@type integer, Component
|
||||
local key = "mac_" .. c.name ---@type string
|
||||
if wc[key] == nil then
|
||||
wc[key] = counts[c.name]
|
||||
end
|
||||
@@ -932,15 +820,12 @@ end
|
||||
--- @param metadata ComponentMetaMap
|
||||
--- @return nil
|
||||
local function update_canonical_components(corpus, src, components, metadata)
|
||||
--- @type string
|
||||
local rel_path = src.path:gsub("\\", "/")
|
||||
--- @type integer, Component
|
||||
for _, c in ipairs(components) do
|
||||
local rel_path = src.path:gsub("\\", "/") ---@type string
|
||||
for _, c in ipairs(components) do ---@type integer, Component
|
||||
-- Keyed by bare name (e.g. `yield`, `load_tri_indices`).
|
||||
-- The atoms_source_map pass looks up components by bare name from the corpus;
|
||||
-- `mac_` prefix lives at the call-site identifier and is stripped before lookup.
|
||||
--- @type ComponentMeta|nil
|
||||
local m = metadata and metadata[c.name] or nil
|
||||
local m = metadata and metadata[c.name] or nil ---@type ComponentMeta|nil
|
||||
if corpus.components[c.name] == nil then
|
||||
corpus.components[c.name] = {
|
||||
name = c.name,
|
||||
@@ -954,13 +839,10 @@ local function update_canonical_components(corpus, src, components, metadata)
|
||||
else
|
||||
-- A second declaration of the same bare name: record a typed collision so static-analysis + the report can surface it.
|
||||
-- Identical-shape declarations (same path + line) reuse the first-wins entry without a collision record.
|
||||
--- @type ComponentDef
|
||||
local existing = corpus.components[c.name]
|
||||
local existing = corpus.components[c.name] ---@type ComponentDef
|
||||
if existing.path ~= rel_path or existing.line ~= c.line then
|
||||
--- @type string
|
||||
local kind = c.kind or "comp_bare"
|
||||
--- @type string
|
||||
local first_kind = existing.kind or "comp_bare"
|
||||
local kind = c.kind or "comp_bare" ---@type string
|
||||
local first_kind = existing.kind or "comp_bare" ---@type string
|
||||
corpus.collisions[#corpus.collisions + 1] = {
|
||||
kind = "component",
|
||||
name = c.name,
|
||||
@@ -983,10 +865,8 @@ end
|
||||
--- @param scan SourceScan
|
||||
--- @return nil
|
||||
local function update_canonical_component_body_index(corpus, src, components, scan)
|
||||
--- @type (fun(pos: integer): integer)|nil
|
||||
local line_of = scan and scan.line_of
|
||||
--- @type integer, Component
|
||||
for _, c in ipairs(components) do
|
||||
local line_of = scan and scan.line_of ---@type (fun(pos: integer): integer)|nil
|
||||
for _, c in ipairs(components) do ---@type integer, Component
|
||||
if corpus.component_body_index[c.name] == nil then
|
||||
corpus.component_body_index[c.name] = {
|
||||
body_tokens = c.body_tokens,
|
||||
@@ -1004,16 +884,12 @@ end
|
||||
--- @param ctx PassCtx
|
||||
--- @return PassResult
|
||||
function M.run(ctx)
|
||||
--- @type MacsOutput[]
|
||||
local outputs = {}
|
||||
--- @type PassFinding[]
|
||||
local errors = {}
|
||||
--- @type PassFinding[]
|
||||
local warnings = {}
|
||||
local outputs = {} ---@type MacsOutput[]
|
||||
local errors = {} ---@type PassFinding[]
|
||||
local warnings = {} ---@type PassFinding[]
|
||||
|
||||
-- Corpus ownership gate.
|
||||
--- @type Corpus|nil
|
||||
local corpus = ctx.shared and ctx.shared.corpus
|
||||
local corpus = ctx.shared and ctx.shared.corpus ---@type Corpus|nil
|
||||
if type(corpus) ~= "table" then
|
||||
error("components.run requires ctx.shared.corpus.", 0)
|
||||
end
|
||||
@@ -1034,22 +910,15 @@ function M.run(ctx)
|
||||
|
||||
-- Per-directory aggregation: every source in the same directory contributes to one `gen/macs.h`.
|
||||
-- The directory itself is the namespace. `corpus.sources_by_dir` preserves source-order within each bucket (matches `corpus.source_order`).
|
||||
--- @type table<string, SourceFile[]>
|
||||
local sources_by_dir = corpus.sources_by_dir or duffle.group_sources_by_dir(corpus.source_order)
|
||||
--- @type string, SourceFile[]
|
||||
for dir, sources in pairs(sources_by_dir) do
|
||||
local sources_by_dir = corpus.sources_by_dir or duffle.group_sources_by_dir(corpus.source_order) ---@type table<string, SourceFile[]>
|
||||
for dir, sources in pairs(sources_by_dir) do ---@type string, SourceFile[]
|
||||
-- Aggregate components from every source in this directory.
|
||||
-- `project_components` returns nil for sources with no `MipsAtomComp_` declarations; we skip those.
|
||||
--- @type Component[]
|
||||
local aggregated_components = {}
|
||||
--- @type table<SourceFile, ComponentMetaMap>
|
||||
local metadata_per_source = {}
|
||||
--- @type integer, SourceFile
|
||||
for _, src in ipairs(sources) do
|
||||
--- @type Component[]
|
||||
local per_source = project_components(src.text, src.scan) or {}
|
||||
--- @type integer, Component
|
||||
for _, c in ipairs(per_source) do
|
||||
local aggregated_components = {} ---@type Component[]
|
||||
local metadata_per_source = {} ---@type table<SourceFile, ComponentMetaMap>
|
||||
for _, src in ipairs(sources) do ---@type integer, SourceFile
|
||||
local per_source = project_components(src.text, src.scan) or {} ---@type Component[]
|
||||
for _, c in ipairs(per_source) do ---@type integer, Component
|
||||
aggregated_components[#aggregated_components + 1] = c
|
||||
end
|
||||
if #per_source > 0 then
|
||||
@@ -1059,18 +928,14 @@ function M.run(ctx)
|
||||
if #aggregated_components > 0 then
|
||||
-- Compute word counts across the aggregated set. `corpus.word_counts` carries the
|
||||
-- same-source + prior-directory entries so the recursive lookup sees both.
|
||||
--- @type table<string, integer> -- bag: bare name -> word count
|
||||
local counts = count_all_components(aggregated_components, corpus.word_counts)
|
||||
--- @type string|nil
|
||||
local macs_path = emit_component_macros_h(ctx, dir, sources, aggregated_components, counts)
|
||||
local counts = count_all_components(aggregated_components, corpus.word_counts) ---@type table<string, integer> -- bag: bare name -> word count
|
||||
local macs_path = emit_component_macros_h(ctx, dir, sources, aggregated_components, counts) ---@type string|nil
|
||||
if macs_path then
|
||||
outputs[#outputs + 1] = { macs_h = macs_path }
|
||||
-- Populate the projections AFTER disk emission (byte-identical `.macs.h` contract).
|
||||
update_canonical_word_counts(corpus, aggregated_components, counts)
|
||||
--- @type integer, SourceFile
|
||||
for _, src in ipairs(sources) do
|
||||
--- @type Component[]
|
||||
local per_source = project_components(src.text, src.scan) or {}
|
||||
for _, src in ipairs(sources) do ---@type integer, SourceFile
|
||||
local per_source = project_components(src.text, src.scan) or {} ---@type Component[]
|
||||
if #per_source > 0 then
|
||||
update_canonical_components(corpus, src, per_source, metadata_per_source[src])
|
||||
update_canonical_component_body_index(corpus, src, per_source, src.scan)
|
||||
|
||||
Reference in New Issue
Block a user