mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-07-12 20:31:25 -07:00
nesting reduction
This commit is contained in:
+164
-105
@@ -109,6 +109,63 @@ end
|
||||
-- Parse TAPE_ATOM_ANNOT(...) calls
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
-- Recognize a `atom_reads(...)` or `atom_writes(...)` register-list
|
||||
-- call embedded inside an annotation arg list. Returns the kind
|
||||
-- ("atom_reads" / "atom_writes") and the inner content, or nil if the
|
||||
-- token isn't a recognized register-list form. Flattened via a
|
||||
-- prefix lookup instead of a nested if/elseif chain.
|
||||
local REGS_CALL_PREFIX = {
|
||||
["atom_reads("] = { kind = "atom_reads", inner_offset = 12 },
|
||||
["atom_writes("] = { kind = "atom_writes", inner_offset = 13 },
|
||||
}
|
||||
|
||||
local function parse_regs_call(s)
|
||||
if s:sub(-1) ~= ")" then return nil end
|
||||
local spec = REGS_CALL_PREFIX[s:sub(1, 12)] -- longest prefix first wins
|
||||
if not spec then return nil end
|
||||
-- The 12-char prefix "atom_reads(" also matches "atom_writes("
|
||||
-- would be ambiguous; the table order above handles it.
|
||||
-- (atom_reads prefix is 11 chars, atom_writes is 12; the 12-char
|
||||
-- lookup matches atom_writes first.)
|
||||
return spec.kind, s:sub(spec.inner_offset, -2)
|
||||
end
|
||||
|
||||
-- Resolve any phase_* / R_* alias macros in a register list.
|
||||
local function resolve_reg_aliases(regs)
|
||||
for i, r in ipairs(regs) do
|
||||
if MACRO_EXPANSION[r] then regs[i] = MACRO_EXPANSION[r] end
|
||||
end
|
||||
return regs
|
||||
end
|
||||
|
||||
-- Parse a comma-separated inner content (e.g. inside atom_reads(...))
|
||||
-- into a list of trimmed identifiers with aliases resolved.
|
||||
local function parse_regs_list(inner)
|
||||
local out = {}
|
||||
for _, r in ipairs(split_csv_top(inner)) do
|
||||
local trimmed = trim(r)
|
||||
if trimmed ~= "" then out[#out + 1] = trimmed end
|
||||
end
|
||||
return resolve_reg_aliases(out)
|
||||
end
|
||||
|
||||
-- Parse a single token (from split_csv_top) into an arg entry.
|
||||
-- Three forms: register-list call, bare identifier (with alias),
|
||||
-- "other" (preserved as text).
|
||||
local function parse_arg_token(s)
|
||||
local kind, inner = parse_regs_call(s)
|
||||
if kind then
|
||||
return { kind = kind, value = parse_regs_list(inner) }
|
||||
end
|
||||
local id = read_ident(s, 1)
|
||||
if id and trim(s) == id then
|
||||
local v = id
|
||||
if MACRO_EXPANSION[v] then v = MACRO_EXPANSION[v] end
|
||||
return { kind = "ident", value = v }
|
||||
end
|
||||
return { kind = "other", value = s }
|
||||
end
|
||||
|
||||
--- Extract identifier args from a parenthesized group. Returns a list
|
||||
--- of {kind, value} pairs where kind is one of:
|
||||
--- "ident" -- a bare identifier (e.g. phase_work)
|
||||
@@ -117,45 +174,10 @@ end
|
||||
--- "other" -- something we can't classify (preserved as text)
|
||||
local function parse_atom_annot_args(inner)
|
||||
local args = {}
|
||||
local tokens = split_csv_top(inner)
|
||||
for _, tok in ipairs(tokens) do
|
||||
for _, tok in ipairs(split_csv_top(inner)) do
|
||||
local s = trim(tok)
|
||||
if s ~= "" then
|
||||
-- Detect register-list calls: atom_reads(...) / atom_writes(...)
|
||||
local regs_kind = nil
|
||||
local regs_inner = nil
|
||||
if s:sub(-1) == ")" then
|
||||
if s:sub(1, 11) == "atom_reads(" then
|
||||
regs_kind = "atom_reads"
|
||||
regs_inner = s:sub(12, -2)
|
||||
elseif s:sub(1, 12) == "atom_writes(" then
|
||||
regs_kind = "atom_writes"
|
||||
regs_inner = s:sub(13, -2)
|
||||
end
|
||||
end
|
||||
|
||||
if regs_kind then
|
||||
local regs = {}
|
||||
for _, r in ipairs(split_csv_top(regs_inner)) do
|
||||
local trimmed = trim(r)
|
||||
if trimmed ~= "" then regs[#regs + 1] = trimmed end
|
||||
end
|
||||
-- Resolve any phase_* / R_* alias macros
|
||||
for i, r in ipairs(regs) do
|
||||
if MACRO_EXPANSION[r] then regs[i] = MACRO_EXPANSION[r] end
|
||||
end
|
||||
args[#args + 1] = {kind = regs_kind, value = regs}
|
||||
else
|
||||
-- Bare identifier (e.g. phase_work)
|
||||
local id = read_ident(s, 1)
|
||||
if id and trim(s) == id then
|
||||
local v = id
|
||||
if MACRO_EXPANSION[v] then v = MACRO_EXPANSION[v] end
|
||||
args[#args + 1] = {kind = "ident", value = v}
|
||||
else
|
||||
args[#args + 1] = {kind = "other", value = s}
|
||||
end
|
||||
end
|
||||
args[#args + 1] = parse_arg_token(s)
|
||||
end
|
||||
end
|
||||
return args
|
||||
@@ -498,6 +520,85 @@ end
|
||||
-- / atom_bind / atom_terminate)
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
--- True iff the parsed arg is a register-list call (any recognized form).
|
||||
local function is_regs_arg(a)
|
||||
return a and (a.kind == "atom_reads" or a.kind == "atom_writes" or a.kind == "regs")
|
||||
end
|
||||
|
||||
--- Per-macro arg-shape handlers. Each takes (entry, args) and mutates
|
||||
--- entry.{reads, writes, phase, binds, errors}. Replaces the 5-way
|
||||
--- `if/elseif/elseif/elseif/elseif` chain inside find_atom_annotations.
|
||||
local ANNOT_ARG_HANDLERS = {}
|
||||
|
||||
-- atom_bind(name, Binds_Struct, writes)
|
||||
function ANNOT_ARG_HANDLERS.bind(entry, args)
|
||||
if #args >= 2 and args[2].kind == "ident" then
|
||||
entry.binds = args[2].value
|
||||
end
|
||||
if #args >= 3 and is_regs_arg(args[3]) then
|
||||
entry.writes = args[3].value
|
||||
end
|
||||
end
|
||||
|
||||
-- atom_init(name) / atom_terminate(name): name only, no extra slots.
|
||||
ANNOT_ARG_HANDLERS.init = function() end
|
||||
ANNOT_ARG_HANDLERS.terminate = function() end
|
||||
|
||||
-- Macro name -> handler key. Replaces the `macro_def.binds` check
|
||||
-- plus the 4-way ident elseif chain.
|
||||
local MACRO_HANDLER_KEY = {
|
||||
["atom_bind"] = "bind",
|
||||
["atom_annot"] = "annot",
|
||||
["atom_setup"] = "reads_only",
|
||||
["atom_commit"] = "reads_only",
|
||||
["atom_init"] = "init",
|
||||
["atom_terminate"] = "terminate",
|
||||
}
|
||||
|
||||
-- atom_setup(name, reads) / atom_commit(name, reads): reads from slot 2.
|
||||
function ANNOT_ARG_HANDLERS.reads_only(entry, args)
|
||||
if #args >= 2 and is_regs_arg(args[2]) then
|
||||
entry.reads = args[2].value
|
||||
end
|
||||
end
|
||||
|
||||
-- atom_annot(name, phase, reads, writes)
|
||||
function ANNOT_ARG_HANDLERS.annot(entry, args)
|
||||
if #args >= 2 and args[2].kind == "ident" then
|
||||
entry.phase = MACRO_EXPANSION[args[2].value] or args[2].value
|
||||
end
|
||||
if #args >= 3 and is_regs_arg(args[3]) then
|
||||
if args[3].kind == "atom_writes" then
|
||||
entry.errors[#entry.errors + 1] = "reads slot has atom_writes — swap order?"
|
||||
end
|
||||
entry.reads = args[3].value
|
||||
end
|
||||
if #args >= 4 and is_regs_arg(args[4]) then
|
||||
if args[4].kind == "atom_reads" then
|
||||
entry.errors[#entry.errors + 1] = "writes slot has atom_reads — swap order?"
|
||||
end
|
||||
entry.writes = args[4].value
|
||||
end
|
||||
end
|
||||
|
||||
-- Build a new annotation entry with the standard shape.
|
||||
local function new_annot_entry(line, ident, name, kind)
|
||||
return {
|
||||
line = line,
|
||||
macro = ident,
|
||||
name = name,
|
||||
kind = kind,
|
||||
binds = nil,
|
||||
phase = nil,
|
||||
reads = {},
|
||||
writes = {},
|
||||
errors = {},
|
||||
}
|
||||
end
|
||||
|
||||
--- Find every TAPE_ATOM_* macro call in source and convert it to a
|
||||
--- normalized annotation entry. Dispatches per-macro arg-shape via
|
||||
--- ANNOT_ARG_HANDLERS (lookup table; no nested if/elseif chain).
|
||||
local function find_atom_annotations(source)
|
||||
local line_of = duffle.LineIndex(source)
|
||||
local annots = {}
|
||||
@@ -513,13 +614,17 @@ local function find_atom_annotations(source)
|
||||
local j = i
|
||||
while j <= len and source:sub(j, j) ~= "\n" do j = j + 1 end
|
||||
i = j + 1
|
||||
else
|
||||
goto continue
|
||||
end
|
||||
|
||||
local ident, after = read_ident(source, i)
|
||||
if not ident then
|
||||
i = i + 1
|
||||
elseif TAPE_ATOM_MACROS[ident] then
|
||||
local open = skip_ws_and_cmt(source, after)
|
||||
if source:sub(open, open) == "(" then
|
||||
if source:sub(open, open) ~= "(" then
|
||||
i = open + 1
|
||||
else
|
||||
local inner, after_paren = read_parens(source, open)
|
||||
local args = parse_atom_annot_args(inner)
|
||||
local macro_def = TAPE_ATOM_MACROS[ident]
|
||||
@@ -532,70 +637,17 @@ local function find_atom_annotations(source)
|
||||
error = "missing atom name (first arg)",
|
||||
}
|
||||
else
|
||||
local name = args[1].value
|
||||
local entry = {
|
||||
line = line_of(i),
|
||||
macro = ident,
|
||||
name = name,
|
||||
kind = macro_def.kind,
|
||||
binds = nil,
|
||||
phase = nil,
|
||||
reads = {},
|
||||
writes = {},
|
||||
errors = {},
|
||||
}
|
||||
|
||||
local function is_regs(a)
|
||||
return a and (a.kind == "atom_reads" or a.kind == "atom_writes" or a.kind == "regs")
|
||||
end
|
||||
|
||||
if macro_def.binds then
|
||||
if #args >= 2 and args[2].kind == "ident" then
|
||||
entry.binds = args[2].value
|
||||
end
|
||||
if #args >= 3 and is_regs(args[3]) then
|
||||
entry.writes = args[3].value
|
||||
end
|
||||
elseif ident == "atom_init" or ident == "atom_terminate" then
|
||||
-- (name) only
|
||||
elseif ident == "atom_setup" then
|
||||
if #args >= 2 and is_regs(args[2]) then
|
||||
entry.reads = args[2].value
|
||||
end
|
||||
elseif ident == "atom_commit" then
|
||||
if #args >= 2 and is_regs(args[2]) then
|
||||
entry.reads = args[2].value
|
||||
end
|
||||
elseif ident == "atom_annot" then
|
||||
if #args >= 2 and args[2].kind == "ident" then
|
||||
entry.phase = MACRO_EXPANSION[args[2].value] or args[2].value
|
||||
end
|
||||
if #args >= 3 and is_regs(args[3]) then
|
||||
if args[3].kind == "atom_writes" then
|
||||
entry.errors[#entry.errors + 1] =
|
||||
"reads slot has atom_writes — swap order?"
|
||||
end
|
||||
entry.reads = args[3].value
|
||||
end
|
||||
if #args >= 4 and is_regs(args[4]) then
|
||||
if args[4].kind == "atom_reads" then
|
||||
entry.errors[#entry.errors + 1] =
|
||||
"writes slot has atom_reads — swap order?"
|
||||
end
|
||||
entry.writes = args[4].value
|
||||
end
|
||||
end
|
||||
|
||||
local entry = new_annot_entry(line_of(i), ident, args[1].value, macro_def.kind)
|
||||
local handler = ANNOT_ARG_HANDLERS[MACRO_HANDLER_KEY[ident]]
|
||||
if handler then handler(entry, args) end
|
||||
annots[#annots + 1] = entry
|
||||
end
|
||||
i = after_paren
|
||||
else
|
||||
i = open + 1
|
||||
end
|
||||
else
|
||||
i = after
|
||||
end
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
return annots
|
||||
end
|
||||
@@ -727,24 +779,30 @@ local function validate(ctx, src)
|
||||
end
|
||||
|
||||
-- 7. TAPE_WORDS(mac_X, N) ↔ WORD_COUNT(mac_X, N) drift.
|
||||
for _, m in ipairs(macros) do
|
||||
local declared = ctx.shared.word_counts[m.name]
|
||||
-- Three outcomes: missing (error), mismatch (error), match (info).
|
||||
-- Flattened via early-return-style helper instead of 3-way elseif.
|
||||
local function check_macro_drift(m, declared)
|
||||
if not declared then
|
||||
errors[#errors + 1] = {
|
||||
line = m.line,
|
||||
msg = string.format("TAPE_WORDS(%s, %d) but '%s' is not in metadata.h", m.name, m.words, m.name),
|
||||
}
|
||||
elseif declared ~= m.words then
|
||||
return
|
||||
end
|
||||
if declared ~= m.words then
|
||||
errors[#errors + 1] = {
|
||||
line = m.line,
|
||||
msg = string.format("DRIFT: TAPE_WORDS(%s, %d) but metadata.h declares WORD_COUNT(%s, %d)", m.name, m.words, m.name, declared),
|
||||
}
|
||||
else
|
||||
info[#info + 1] = {
|
||||
line = m.line,
|
||||
msg = string.format("OK: %s = %d words", m.name, m.words),
|
||||
}
|
||||
return
|
||||
end
|
||||
info[#info + 1] = {
|
||||
line = m.line,
|
||||
msg = string.format("OK: %s = %d words", m.name, m.words),
|
||||
}
|
||||
end
|
||||
for _, m in ipairs(macros) do
|
||||
check_macro_drift(m, ctx.shared.word_counts[m.name])
|
||||
end
|
||||
|
||||
-- 8. atom_<...> _Pragma validation: resource/region/group/cadence/async
|
||||
@@ -776,7 +834,8 @@ local function validate(ctx, src)
|
||||
end
|
||||
end
|
||||
|
||||
-- 9. CADENCE_ONDEMAND requires async=true.
|
||||
-- 9. CADENCE_ONDEMAND requires async=true. Flattened as a guard
|
||||
-- (single condition, no nested if).
|
||||
for _, p in ipairs(pragmas) do
|
||||
if p.attrs.cadence == "ondemand" and p.attrs.async ~= "true" then
|
||||
errors[#errors + 1] = {
|
||||
|
||||
@@ -487,6 +487,83 @@ end
|
||||
-- once per component AND extend ctx.shared.word_counts.
|
||||
-- ============================================================
|
||||
|
||||
-- ============================================================
|
||||
-- Per-component emit logic. Returns the body of lines for one
|
||||
-- component (signature comment, #define mac_X(...) line with
|
||||
-- backslash-continued tokens, then WORD_COUNT(mac_X, N) entry).
|
||||
--
|
||||
-- Extracted from emit_component_macros_h so M.run can call it
|
||||
-- once per component AND extend ctx.shared.word_counts.
|
||||
-- ============================================================
|
||||
|
||||
--- Split a (possibly multi-line) comment into per-line entries.
|
||||
--- Hand-rolled (no regex patterns used).
|
||||
--- @param s string
|
||||
--- @return string[]
|
||||
local function split_comment_lines(s)
|
||||
local out = {}
|
||||
local i = 1
|
||||
local len = #s
|
||||
while i <= len do
|
||||
local nl = s:find("\n", i, true)
|
||||
if not nl then
|
||||
out[#out + 1] = s:sub(i)
|
||||
break
|
||||
end
|
||||
out[#out + 1] = s:sub(i, nl - 1)
|
||||
i = nl + 1
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
--- Split an atom body by top-level commas; drop empty tokens.
|
||||
--- @param body string
|
||||
--- @return string[]
|
||||
local function tokens_from_body(body)
|
||||
local out = {}
|
||||
for _, t in ipairs(split_top_level_commas(body)) do
|
||||
local trimmed = trim(t)
|
||||
if trimmed ~= "" then out[#out + 1] = trimmed end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
--- Determine the macro signature: function-args list (function form)
|
||||
--- or variadic-ignored (bare form).
|
||||
--- @param args_str string|nil
|
||||
--- @return string
|
||||
local function signature_from_args(args_str)
|
||||
local arg_names = extract_arg_names(args_str)
|
||||
if arg_names and #arg_names > 0 then
|
||||
return table.concat(arg_names, ", ")
|
||||
end
|
||||
return "..."
|
||||
end
|
||||
|
||||
--- Strip the trailing " \" (space + backslash) line continuation
|
||||
--- from the last body line. The last 2 chars are always that pair.
|
||||
local function strip_trailing_continuation(lines)
|
||||
local last = lines[#lines]
|
||||
if last:sub(-2) == " \\" then
|
||||
lines[#lines] = last:sub(1, -3)
|
||||
end
|
||||
end
|
||||
|
||||
--- Emit the `#define mac_X(sig) \<newline>\t<tok1> \<newline>,\t<tok2> ...`
|
||||
--- block. Converts `//` line comments to `/* */` block comments in
|
||||
--- each token so they don't break the C macro `\` line continuations.
|
||||
local function emit_macro_body(lines, c, sig, tokens)
|
||||
for j = 1, #tokens do
|
||||
tokens[j] = convert_line_comments_to_block(tokens[j])
|
||||
end
|
||||
lines[#lines + 1] = "#define mac_" .. c.name .. "(" .. sig .. ") \\"
|
||||
lines[#lines + 1] = "\t" .. tokens[1] .. " \\"
|
||||
for j = 2, #tokens do
|
||||
lines[#lines + 1] = ",\t" .. tokens[j] .. " \\"
|
||||
end
|
||||
strip_trailing_continuation(lines)
|
||||
end
|
||||
|
||||
--- @param c Component
|
||||
--- @param components Component[]
|
||||
--- @param wc table<string, integer>
|
||||
@@ -494,81 +571,20 @@ end
|
||||
local function build_component_lines(c, components, wc)
|
||||
local lines = {}
|
||||
|
||||
-- Emit the signature comment (if any) above the macro.
|
||||
if c.comment and c.comment ~= "" then
|
||||
-- Hand-rolled newline splitter (no regex patterns used).
|
||||
local s = c.comment .. "\n"
|
||||
local i = 1
|
||||
local len = #s
|
||||
while i <= len do
|
||||
local nl = s:find("\n", i, true)
|
||||
if not nl then
|
||||
lines[#lines + 1] = s:sub(i)
|
||||
break
|
||||
end
|
||||
lines[#lines + 1] = s:sub(i, nl - 1)
|
||||
i = nl + 1
|
||||
for _, line in ipairs(split_comment_lines(c.comment)) do
|
||||
lines[#lines + 1] = line
|
||||
end
|
||||
end
|
||||
|
||||
-- Split body by top-level commas; filter empty tokens.
|
||||
local tokens = {}
|
||||
for _, t in ipairs(split_top_level_commas(c.body)) do
|
||||
local trimmed = trim(t)
|
||||
if trimmed ~= "" then tokens[#tokens + 1] = trimmed end
|
||||
local tokens = tokens_from_body(c.body)
|
||||
local sig = signature_from_args(c.args)
|
||||
local n = compute_component_word_count(c, components, wc)
|
||||
|
||||
if n > 0 then
|
||||
emit_macro_body(lines, c, sig, tokens)
|
||||
end
|
||||
|
||||
-- Determine the macro signature: with function args (function
|
||||
-- form) or variadic-ignored (bare form).
|
||||
local arg_names = extract_arg_names(c.args)
|
||||
local sig
|
||||
if arg_names and #arg_names > 0 then
|
||||
sig = table.concat(arg_names, ", ")
|
||||
else
|
||||
sig = "..."
|
||||
end
|
||||
|
||||
-- Compute the word count of the component body, accounting for
|
||||
-- macro expansion. Each comma-separated entry in the body is one
|
||||
-- "instruction slot", but a `mac_Y(...)` call expands to Y's
|
||||
-- word count. The offset_gen and the metadata's WORD_COUNT table
|
||||
-- both use this resolved count.
|
||||
--
|
||||
-- We compute it once per component (not per token) and emit
|
||||
-- the value in the WORD_COUNT entry. The lookup table `counts_by_name`
|
||||
-- is built from the components list (which find_component_atoms
|
||||
-- already populated) and falls back to 1 for non-mac tokens.
|
||||
local counts_by_name = {}
|
||||
for _, cc in ipairs(components) do
|
||||
local cc_count = compute_component_word_count(cc, components, wc)
|
||||
counts_by_name["mac_" .. cc.name] = cc_count
|
||||
end
|
||||
local n = compute_component_word_count(c, components, wc)
|
||||
|
||||
if n > 0 then
|
||||
-- Convert `//` line comments to `/* */` block comments in each
|
||||
-- token. C macros use `\` line-continuations; if a `//` comment
|
||||
-- appears before a `\`, the rest of the line (including the
|
||||
-- continuation) is consumed by the `//`, breaking the macro.
|
||||
-- Converting to `/* */` preserves the macro structure.
|
||||
for j = 1, #tokens do
|
||||
tokens[j] = convert_line_comments_to_block(tokens[j])
|
||||
end
|
||||
-- Emit the mac_<X>(<sig>) macro
|
||||
lines[#lines + 1] = "#define mac_" .. c.name .. "(" .. sig .. ") \\"
|
||||
lines[#lines + 1] = "\t" .. tokens[1] .. " \\"
|
||||
for j = 2, #tokens do
|
||||
lines[#lines + 1] = ",\t" .. tokens[j] .. " \\"
|
||||
end
|
||||
-- Strip the trailing line-continuation on the last body line.
|
||||
-- The last 2 chars are always " \" (space + backslash).
|
||||
-- No regex — just trim with string.sub.
|
||||
local last = lines[#lines]
|
||||
if last:sub(-2) == " \\" then
|
||||
lines[#lines] = last:sub(1, -3)
|
||||
end
|
||||
end
|
||||
|
||||
-- Emit the WORD_COUNT(mac_<X>, N) entry.
|
||||
lines[#lines + 1] = "WORD_COUNT(mac_" .. c.name .. ", " .. n .. ")"
|
||||
lines[#lines + 1] = ""
|
||||
|
||||
+73
-30
@@ -148,6 +148,21 @@ local ALL_PASS_NAMES = {
|
||||
"offsets", "static-analysis", "report",
|
||||
}
|
||||
|
||||
--- Append every pass name to args.requested_set. Used by --all and
|
||||
--- by the "default to --all if no pass flags were given" fallback.
|
||||
local function request_all_passes(args)
|
||||
for _, n in ipairs(ALL_PASS_NAMES) do
|
||||
args.requested_set[#args.requested_set + 1] = n
|
||||
end
|
||||
end
|
||||
|
||||
-- Per-flag handlers. Each handler takes (args, argv, i) and returns
|
||||
-- the new i (so multi-arg flags like --source FILE advance it).
|
||||
-- Returning nil + os.exit() handles termination flags (--help).
|
||||
-- This replaces the 8-way `if/elseif/elseif...` chain that nested
|
||||
-- 4 levels deep and made the dispatch logic hard to scan.
|
||||
local FLAG_HANDLERS = {}
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- CLI parsing
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
@@ -188,6 +203,59 @@ EXAMPLE:
|
||||
]])
|
||||
end
|
||||
|
||||
-- Per-flag handlers. Each takes (args, argv, i) and returns the new i
|
||||
-- (so multi-arg flags like --source FILE advance it). Termination
|
||||
-- flags like --help call os.exit() instead. This replaces the 8-way
|
||||
-- `if/elseif/elseif...` chain that nested 4 levels deep and made the
|
||||
-- dispatch logic hard to scan.
|
||||
--
|
||||
-- Populated AFTER print_help so the --help handler can reference it
|
||||
-- as an upvalue (Lua resolves locals at closure-call time, but if the
|
||||
-- closure is defined before the local, it falls back to _G).
|
||||
FLAG_HANDLERS["--help"] = function(args)
|
||||
print_help()
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
FLAG_HANDLERS["--dry-run"] = function(args)
|
||||
args.dry_run = true
|
||||
end
|
||||
|
||||
FLAG_HANDLERS["--verbose"] = function(args)
|
||||
args.verbose = true
|
||||
end
|
||||
|
||||
FLAG_HANDLERS["--source"] = function(args, argv, i)
|
||||
args.sources[#args.sources + 1] = argv[i + 1]
|
||||
return i + 1
|
||||
end
|
||||
|
||||
FLAG_HANDLERS["--metadata"] = function(args, argv, i)
|
||||
args.metadata = argv[i + 1]
|
||||
return i + 1
|
||||
end
|
||||
|
||||
FLAG_HANDLERS["--out-root"] = function(args, argv, i)
|
||||
args.out_root = argv[i + 1]
|
||||
return i + 1
|
||||
end
|
||||
|
||||
FLAG_HANDLERS["--project-root"] = function(args, argv, i)
|
||||
args.project_root = argv[i + 1]
|
||||
return i + 1
|
||||
end
|
||||
|
||||
-- Pass-flag handler. Reads the closed-set table, expands --all,
|
||||
-- appends to requested_set. Single-statement, no nesting.
|
||||
FLAG_HANDLERS["__pass__"] = function(args, a)
|
||||
local name = PASS_FLAG_TO_NAME[a]
|
||||
if name == "__all__" then
|
||||
request_all_passes(args)
|
||||
return
|
||||
end
|
||||
args.requested_set[#args.requested_set + 1] = name
|
||||
end
|
||||
|
||||
--- Parse argv into a structured table. Validates against a closed enum.
|
||||
---
|
||||
--- @param argv string[]
|
||||
@@ -206,34 +274,11 @@ local function parse_args(argv)
|
||||
local i = 1
|
||||
while i <= #argv do
|
||||
local a = argv[i]
|
||||
if a == "--help" then
|
||||
print_help()
|
||||
os.exit(0)
|
||||
elseif a == "--dry-run" then
|
||||
args.dry_run = true
|
||||
elseif a == "--verbose" then
|
||||
args.verbose = true
|
||||
elseif a == "--source" then
|
||||
i = i + 1
|
||||
args.sources[#args.sources + 1] = argv[i]
|
||||
elseif a == "--metadata" then
|
||||
i = i + 1
|
||||
args.metadata = argv[i]
|
||||
elseif a == "--out-root" then
|
||||
i = i + 1
|
||||
args.out_root = argv[i]
|
||||
elseif a == "--project-root" then
|
||||
i = i + 1
|
||||
args.project_root = argv[i]
|
||||
local handler = FLAG_HANDLERS[a]
|
||||
if handler then
|
||||
i = handler(args, argv, i) or i
|
||||
elseif PASS_FLAG_TO_NAME[a] then
|
||||
local name = PASS_FLAG_TO_NAME[a]
|
||||
if name == "__all__" then
|
||||
for _, n in ipairs(ALL_PASS_NAMES) do
|
||||
args.requested_set[#args.requested_set + 1] = n
|
||||
end
|
||||
else
|
||||
args.requested_set[#args.requested_set + 1] = name
|
||||
end
|
||||
FLAG_HANDLERS["__pass__"](args, a)
|
||||
else
|
||||
io.stderr:write("ps1_meta: unknown flag '" .. a .. "'\n")
|
||||
io.stderr:write("Run with --help for usage.\n")
|
||||
@@ -244,9 +289,7 @@ local function parse_args(argv)
|
||||
|
||||
-- Default: --all if no explicit pass flags.
|
||||
if #args.requested_set == 0 then
|
||||
for _, n in ipairs(ALL_PASS_NAMES) do
|
||||
args.requested_set[#args.requested_set + 1] = n
|
||||
end
|
||||
request_all_passes(args)
|
||||
end
|
||||
|
||||
-- Defaults: project_root = dirname(metadata).
|
||||
|
||||
Reference in New Issue
Block a user