mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-07-13 12:51:26 -07:00
final pass on metaprogram
This commit is contained in:
+303
-250
@@ -1,6 +1,6 @@
|
||||
--- passes/scan_source.lua — Source pre-scan pass (the "mega entity" pass).
|
||||
---
|
||||
--- Single source-walk pass that produces the fat `SourceScan` payload consumed by all downstream passes. Walks each `ctx.sources` entry once,
|
||||
--- Single source-walk pass that produces the fat `SourceScan` payload consumed by all downstream passes. Walks each `ctx.sources` entry once,
|
||||
--- extracting every construct type the metaprograms need:
|
||||
---
|
||||
--- MipsAtom_ (kind = "atom", with optional atom_info inner)
|
||||
@@ -14,8 +14,7 @@
|
||||
--- This is the first pass in the dep graph (no deps).
|
||||
--- Every other pass that reads source structure depends on this one — see `ps1_meta.lua :: PASSES`.
|
||||
---
|
||||
--- **Conventions**: tabs (1/level), EmmyLua annotations, no regex,
|
||||
--- Lua 5.3 compatible.
|
||||
--- **Conventions**: tabs (1/level), EmmyLua annotations, no regex, Lua 5.3 compatible
|
||||
|
||||
-- Bootstrap: same as entry scripts. See `ps1_meta.lua` for the rationale.
|
||||
-- Bootstrap: load `scripts/duffle_paths.lua` (sets package.path + package.cpath).
|
||||
@@ -35,7 +34,7 @@ local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
||||
--- @field raw_atoms AtomEntry[] -- MipsCode code_<name> { body } (offsets pass only)
|
||||
--- @field binds BindsEntry[] -- typedef Struct_(Binds_X) { fields } (fields pre-parsed)
|
||||
--- @field atom_infos AtomInfoEntry[] -- MipsAtom_(name) atom_info(...) (sub-calls pre-parsed)
|
||||
--- @field macros MacroEntry[] -- #pragma mac_X tape_atom words=N + _Pragma("...")
|
||||
--- @field macros MacroEntry[] -- #pragma mac_X tape_atom words=N + _Pragma("...")
|
||||
--- @field line_of fun(pos: integer): integer -- shared LineIndex closure
|
||||
|
||||
--- @class SourceFile
|
||||
@@ -74,7 +73,7 @@ local duffle = dofile(_bootstrap_dir .. "../duffle_paths.lua")
|
||||
--- @field comment string|nil -- populated by components pass (backward lookup)
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- Local helpers
|
||||
-- Local helpers (shared by per-form parsers)
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
-- C qualifier keywords that may precede a MipsAtom_ / MipsCode declaration.
|
||||
@@ -86,6 +85,22 @@ local QUALIFIER_KEYWORDS = {
|
||||
["internal"] = true, ["LP_"] = true, ["global"] = true, ["gkknown"] = true,
|
||||
}
|
||||
|
||||
-- "ac_" prefix length on component names (e.g., `MipsAtomComp_(ac_X, ...)`). The components pass strips
|
||||
-- this prefix to derive the macro name (e.g., `mac_X`). Single source of truth — was duplicated in two
|
||||
-- branches of the pre-refactor scan_source.
|
||||
local AC_PREFIX = "ac_"
|
||||
local AC_PREFIX_LEN = 3
|
||||
|
||||
-- Strip the "ac_" prefix from a component name. Returns the input unchanged if it doesn't start with the prefix.
|
||||
-- @param raw_name string
|
||||
-- @return string
|
||||
local function strip_ac_prefix(raw_name)
|
||||
if #raw_name > AC_PREFIX_LEN and raw_name:sub(1, AC_PREFIX_LEN) == AC_PREFIX then
|
||||
return raw_name:sub(AC_PREFIX_LEN + 1)
|
||||
end
|
||||
return raw_name
|
||||
end
|
||||
|
||||
-- Parse the U4 fields from a Binds_X body. Returns (fields, byte_count).
|
||||
local function scan_binds_fields(body)
|
||||
local fields = {}
|
||||
@@ -147,13 +162,13 @@ local function scan_atom_info_subcalls(info_inner)
|
||||
if info_inner:sub(sub_open, sub_open) == "(" then
|
||||
local sub_inner, sub_after2 = duffle.read_parens(info_inner, sub_open)
|
||||
-- scan: atom_bind(<Binds_X>)
|
||||
binds = duffle.trim(sub_inner)
|
||||
binds = duffle.trim(sub_inner)
|
||||
sub_pos = sub_after2
|
||||
else
|
||||
sub_pos = sub_open + 1
|
||||
end
|
||||
elseif sub_ident == "atom_reads" or sub_ident == "atom_writes" then
|
||||
local kind = sub_ident
|
||||
local kind = sub_ident
|
||||
local sub_open = duffle.skip_ws_and_cmt(info_inner, sub_end)
|
||||
if info_inner:sub(sub_open, sub_open) == "(" then
|
||||
local sub_inner, sub_after2 = duffle.read_parens(info_inner, sub_open)
|
||||
@@ -181,6 +196,262 @@ local function scan_skip_qualifiers(source, pos)
|
||||
end
|
||||
end
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- Per-form parsers (the DECL_PARSERS table's payload)
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
--
|
||||
-- Each parser has the uniform signature:
|
||||
-- parser(source, pos, ident_end, line_of, out) -> new_pos
|
||||
-- where:
|
||||
-- source -- the full source text
|
||||
-- pos -- position of the construct's leading ident (e.g., `M` of `MipsAtom_`)
|
||||
-- ident_end -- position past the leading ident (where the `(` should be)
|
||||
-- line_of -- closure over LineIndex(source) for 1-based line lookups
|
||||
-- out -- the SourceScan out table (mutated in place: out.atoms / out.raw_atoms / out.binds / out.atom_infos / out.macros)
|
||||
-- returns -- new position after the construct
|
||||
--
|
||||
-- All parsers read source-as-written via the duffle primitives (skip_ws_and_cmt / read_parens / read_braces / read_balanced).
|
||||
-- No regex per the no_regex constraint; no hand-rolled depth tracking (the MipsAtomComp_Proc_ brace matcher
|
||||
-- now uses duffle.read_braces instead of bespoke byte-dispatch).
|
||||
--
|
||||
-- Adding a new construct = 1 row in DECL_PARSERS + 1 parser function. The scan_source() loop never needs editing.
|
||||
|
||||
--- Parse: `MipsAtom_(<name>) [atom_info(<binds>, <reads>, <writes>)] { <body> }`
|
||||
--- @param source string
|
||||
--- @param pos integer
|
||||
--- @param ident_end integer
|
||||
--- @param line_of fun(pos: integer): integer
|
||||
--- @param out SourceScan
|
||||
--- @return integer
|
||||
local function parse_mips_atom(source, pos, ident_end, line_of, out)
|
||||
local open_paren = duffle.skip_ws_and_cmt(source, ident_end)
|
||||
if source:sub(open_paren, open_paren) ~= "(" then return open_paren + 1 end
|
||||
local inner, after_paren = duffle.read_parens(source, open_paren)
|
||||
|
||||
local raw_name = duffle.read_ident(inner, 1)
|
||||
|
||||
-- Lookahead for atom_info(...) between `)` and `{`. Captures sub-calls; updates brace search start.
|
||||
local brace_search_pos = after_paren
|
||||
local lookahead = duffle.skip_ws_and_cmt(source, after_paren)
|
||||
local look_ident, look_end = duffle.read_ident(source, lookahead)
|
||||
if look_ident == "atom_info" then
|
||||
local info_open = duffle.skip_ws_and_cmt(source, look_end)
|
||||
if source:sub(info_open, info_open) == "(" then
|
||||
local info_inner, info_after = duffle.read_parens(source, info_open)
|
||||
local ai_binds, ai_reads, ai_writes = scan_atom_info_subcalls(info_inner)
|
||||
out.atom_infos[#out.atom_infos + 1] = {
|
||||
atom_name = raw_name or "?", binds = ai_binds,
|
||||
reads = ai_reads or {}, writes = ai_writes or {},
|
||||
info_line = line_of(lookahead),
|
||||
}
|
||||
brace_search_pos = info_after
|
||||
end
|
||||
end
|
||||
|
||||
local brace = duffle.scan_to_char(source, "{", brace_search_pos)
|
||||
if not brace then return open_paren + 1 end
|
||||
|
||||
local body, after_brace = duffle.read_braces(source, brace)
|
||||
if raw_name and raw_name ~= "" then
|
||||
out.atoms[#out.atoms + 1] = {
|
||||
line = line_of(pos), name = raw_name, body = body, body_off = brace + 1,
|
||||
kind = "atom", raw_name = raw_name,
|
||||
ident_pos = pos, after_paren = after_paren,
|
||||
}
|
||||
end
|
||||
|
||||
return after_brace
|
||||
end
|
||||
|
||||
--- Parse: `MipsAtomComp_(<name>) { <body> }`
|
||||
--- @param source string
|
||||
--- @param pos integer
|
||||
--- @param ident_end integer
|
||||
--- @param line_of fun(pos: integer): integer
|
||||
--- @param out SourceScan
|
||||
--- @return integer
|
||||
local function parse_mips_atom_comp(source, pos, ident_end, line_of, out)
|
||||
local open_paren = duffle.skip_ws_and_cmt(source, ident_end)
|
||||
if source:sub(open_paren, open_paren) ~= "(" then return open_paren + 1 end
|
||||
local inner, after_paren = duffle.read_parens(source, open_paren)
|
||||
|
||||
local raw_name = duffle.read_ident(inner, 1)
|
||||
if not raw_name then return open_paren + 1 end
|
||||
|
||||
local brace = duffle.scan_to_char(source, "{", after_paren)
|
||||
if not brace then return open_paren + 1 end
|
||||
|
||||
local body, after_brace = duffle.read_braces(source, brace)
|
||||
|
||||
out.atoms[#out.atoms + 1] = {
|
||||
line = line_of(pos), name = strip_ac_prefix(raw_name), body = body, body_off = brace + 1,
|
||||
kind = "comp_bare", raw_name = raw_name,
|
||||
ident_pos = pos, after_paren = after_paren,
|
||||
}
|
||||
|
||||
return after_brace
|
||||
end
|
||||
|
||||
--- Parse: `MipsAtomComp_Proc_(<name>, { <body> })` — body is inside the LAST `{` in args.
|
||||
--- @param source string
|
||||
--- @param pos integer
|
||||
--- @param ident_end integer
|
||||
--- @param line_of fun(pos: integer): integer
|
||||
--- @param out SourceScan
|
||||
--- @return integer
|
||||
local function parse_mips_atom_comp_proc(source, pos, ident_end, line_of, out)
|
||||
local open_paren = duffle.skip_ws_and_cmt(source, ident_end)
|
||||
if source:sub(open_paren, open_paren) ~= "(" then return open_paren + 1 end
|
||||
local inner, after_paren = duffle.read_parens(source, open_paren)
|
||||
|
||||
-- Find the LAST `{` in inner (the body brace, not any potential embedded braces in expressions).
|
||||
local last_brace_pos = nil
|
||||
for search_pos = #inner, 1, -1 do
|
||||
if inner:sub(search_pos, search_pos) == "{" then last_brace_pos = search_pos; break end
|
||||
end
|
||||
if not last_brace_pos then return after_paren end
|
||||
|
||||
-- Use duffle.read_braces to find the matching close brace.
|
||||
-- Replaces the pre-refactor hand-rolled depth tracker (~25 LOC of `if c == 123 then depth = depth + 1 ...`).
|
||||
-- If close_pos is past the end of inner, the brace didn't match (malformed input); skip.
|
||||
local body, close_pos = duffle.read_braces(inner, last_brace_pos)
|
||||
if close_pos > #inner + 1 then return after_paren end
|
||||
|
||||
local raw_name = inner:match("^%s*([%w_]+)") or "?"
|
||||
-- Position of body[1] in source = open_paren + 1 (start of inner) + last_brace_pos + 1 (past '{').
|
||||
local body_off = open_paren + 2 + last_brace_pos
|
||||
|
||||
out.atoms[#out.atoms + 1] = {
|
||||
line = line_of(pos), name = strip_ac_prefix(raw_name), body = body, body_off = body_off,
|
||||
kind = "comp_proc", raw_name = raw_name,
|
||||
ident_pos = pos, after_paren = after_paren,
|
||||
}
|
||||
|
||||
return after_paren
|
||||
end
|
||||
|
||||
--- Parse: `MipsCode code_<name> { <body> }` (raw atom form — offsets pass only).
|
||||
--- @param source string
|
||||
--- @param pos integer
|
||||
--- @param ident_end integer
|
||||
--- @param line_of fun(pos: integer): integer
|
||||
--- @param out SourceScan
|
||||
--- @return integer
|
||||
local function parse_mips_code(source, pos, ident_end, line_of, out)
|
||||
local next_pos = duffle.skip_ws_and_cmt(source, ident_end)
|
||||
local next_ident, next_after = duffle.read_ident(source, next_pos)
|
||||
if not next_ident or #next_ident <= 5 or next_ident:sub(1, 5) ~= "code_" then
|
||||
return ident_end
|
||||
end
|
||||
|
||||
local atom_name = next_ident:sub(6)
|
||||
local brace_pos = duffle.scan_to_char(source, "{", next_after)
|
||||
if not brace_pos then return ident_end end
|
||||
|
||||
local body, after_brace = duffle.read_braces(source, brace_pos)
|
||||
out.raw_atoms[#out.raw_atoms + 1] = {
|
||||
line = line_of(pos), name = atom_name, body = body, body_off = brace_pos + 1,
|
||||
kind = "raw_atom", raw_name = atom_name,
|
||||
}
|
||||
|
||||
return after_brace
|
||||
end
|
||||
|
||||
--- Parse: `typedef Struct_(<name>) { <fields> }` — only emits when name starts with `Binds_`.
|
||||
--- @param source string
|
||||
--- @param pos integer
|
||||
--- @param ident_end integer
|
||||
--- @param line_of fun(pos: integer): integer
|
||||
--- @param out SourceScan
|
||||
--- @return integer
|
||||
local function parse_typedef_binds(source, pos, ident_end, line_of, out)
|
||||
local after_typedef = duffle.skip_ws_and_cmt(source, ident_end)
|
||||
local id2, id2_end = duffle.read_ident(source, after_typedef)
|
||||
if id2 ~= "Struct_" then return ident_end end
|
||||
|
||||
local open_paren = duffle.skip_ws_and_cmt(source, id2_end)
|
||||
if source:sub(open_paren, open_paren) ~= "(" then return id2_end end
|
||||
|
||||
local inner, after_paren = duffle.read_parens(source, open_paren)
|
||||
local name = duffle.trim(inner)
|
||||
|
||||
local brace = duffle.scan_to_char(source, "{", after_paren)
|
||||
if not brace then return open_paren + 1 end
|
||||
|
||||
local body, after_brace = duffle.read_braces(source, brace)
|
||||
if name:sub(1, 6) == "Binds_" then
|
||||
local fields, byte_off = scan_binds_fields(body)
|
||||
out.binds[#out.binds + 1] = { line = line_of(pos), name = name, fields = fields, bytes = byte_off }
|
||||
end
|
||||
|
||||
return after_brace
|
||||
end
|
||||
|
||||
--- Parse: `_Pragma("mac_X tape_atom words=N")` (operator form).
|
||||
--- @param source string
|
||||
--- @param pos integer
|
||||
--- @param ident_end integer
|
||||
--- @param line_of fun(pos: integer): integer
|
||||
--- @param out SourceScan
|
||||
--- @return integer
|
||||
local function parse_pragma_macro(source, pos, ident_end, line_of, out)
|
||||
local open_paren = duffle.skip_ws_and_cmt(source, ident_end)
|
||||
if source:sub(open_paren, open_paren) ~= "(" then return open_paren + 1 end
|
||||
|
||||
local str, str_end = duffle.read_parens(source, open_paren)
|
||||
str = duffle.trim(str)
|
||||
if str:sub(1, 1) ~= '"' or str:sub(-1) ~= '"' then return str_end end
|
||||
|
||||
local inner = str:sub(2, -2)
|
||||
local space = duffle.find_byte(inner, 32, 1)
|
||||
if not space then return str_end end
|
||||
|
||||
local name = inner:sub(1, space - 1)
|
||||
local rest = inner:sub(space + 1)
|
||||
local eq = duffle.find_byte(rest, 61, 1)
|
||||
if not eq then return str_end end
|
||||
|
||||
local key = duffle.trim(rest:sub(1, eq - 1))
|
||||
local val = duffle.trim(rest:sub(eq + 1))
|
||||
if key == "tape_atom words" or key == "words" then
|
||||
out.macros[#out.macros + 1] = { line = line_of(pos), name = name, words = tonumber(val) or 0 }
|
||||
end
|
||||
|
||||
return str_end
|
||||
end
|
||||
|
||||
--- Parse: `pragma` ident (no-op — directive form `#pragma` is handled by `skip_preprocessor_line` upstream).
|
||||
--- If we reach this parser it means the directive skip didn't fire, which can happen for non-#-prefixed pragma.
|
||||
--- Just advance past the ident.
|
||||
--- @param source string
|
||||
--- @param pos integer
|
||||
--- @param ident_end integer
|
||||
--- @param line_of fun(pos: integer): integer
|
||||
--- @param out SourceScan
|
||||
--- @return integer
|
||||
local function parse_pragma_dummy(source, pos, ident_end, line_of, out)
|
||||
return ident_end
|
||||
end
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- DECL_PARSERS — data-driven construct dispatch (the plex pattern)
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
--
|
||||
-- Each entry maps a leading ident to its parser function. The main scan_source() loop is one line of dispatch:
|
||||
-- local parser = DECL_PARSERS[ident]; if parser then pos = parser(...) end
|
||||
--
|
||||
-- Adding a new construct = 1 row here + 1 parser function above.
|
||||
|
||||
local DECL_PARSERS = {
|
||||
MipsAtom_ = parse_mips_atom,
|
||||
MipsAtomComp_ = parse_mips_atom_comp,
|
||||
MipsAtomComp_Proc_ = parse_mips_atom_comp_proc,
|
||||
MipsCode = parse_mips_code,
|
||||
typedef = parse_typedef_binds,
|
||||
_Pragma = parse_pragma_macro,
|
||||
pragma = parse_pragma_dummy,
|
||||
}
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
-- The single source walker
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
@@ -191,13 +462,16 @@ end
|
||||
--- @return table -- SourceScan { atoms, raw_atoms, binds, atom_infos, macros, line_of }
|
||||
local function scan_source(source)
|
||||
local line_of = duffle.LineIndex(source)
|
||||
local atoms = {}
|
||||
local raw_atoms = {}
|
||||
local binds = {}
|
||||
local atom_infos = {}
|
||||
local macros = {}
|
||||
local pos = 1
|
||||
local src_len = #source
|
||||
local out = {
|
||||
atoms = {},
|
||||
raw_atoms = {},
|
||||
binds = {},
|
||||
atom_infos = {},
|
||||
macros = {},
|
||||
line_of = line_of,
|
||||
}
|
||||
local pos = 1
|
||||
local src_len = #source
|
||||
|
||||
while pos <= src_len do
|
||||
pos = duffle.skip_ws_and_cmt(source, pos)
|
||||
@@ -206,250 +480,29 @@ local function scan_source(source)
|
||||
-- Skip preprocessor directives (#define / #include / #pragma / etc).
|
||||
-- _Pragma is an operator (not a directive) — it doesn't start with #.
|
||||
local pp_pos = duffle.skip_preprocessor_line(source, pos)
|
||||
if pp_pos then pos = pp_pos; goto continue end
|
||||
|
||||
-- Skip C qualifiers (static, const, etc.) that may precede a declaration.
|
||||
pos = scan_skip_qualifiers(source, pos)
|
||||
if pos > src_len then break end
|
||||
|
||||
local ident, ident_end = duffle.read_ident(source, pos)
|
||||
-- scan: <ident>
|
||||
if not ident then pos = pos + 1; goto continue end
|
||||
|
||||
-- ── MipsAtom_ / MipsAtomComp_ / MipsAtomComp_Proc_ ──
|
||||
if ident == "MipsAtom_" or ident == "MipsAtomComp_" or ident == "MipsAtomComp_Proc_" then
|
||||
local is_atom = ident == "MipsAtom_"
|
||||
local is_comp = ident == "MipsAtomComp_"
|
||||
local is_proc = ident == "MipsAtomComp_Proc_"
|
||||
local kind = is_atom and "atom" or (is_comp and "comp_bare" or "comp_proc")
|
||||
local open_paren = duffle.skip_ws_and_cmt(source, ident_end)
|
||||
if source:sub(open_paren, open_paren) ~= "(" then pos = open_paren + 1; goto continue end
|
||||
|
||||
local inner, after_paren = duffle.read_parens(source, open_paren)
|
||||
-- scan: <ident>(<args>)
|
||||
|
||||
if is_proc then
|
||||
-- MipsAtomComp_Proc_(name, { body }) — body is inside the LAST { } in args.
|
||||
local last_brace_pos
|
||||
for search_pos = #inner, 1, -1 do
|
||||
if inner:sub(search_pos, search_pos) == "{" then last_brace_pos = search_pos; break end
|
||||
end
|
||||
if last_brace_pos then
|
||||
local depth = 1
|
||||
local inner_pos = last_brace_pos + 1
|
||||
while inner_pos <= #inner and depth > 0 do
|
||||
local c = inner:byte(inner_pos)
|
||||
if c == 123 then depth = depth + 1; inner_pos = inner_pos + 1
|
||||
elseif c == 125 then depth = depth - 1; if depth == 0 then break end; inner_pos = inner_pos + 1
|
||||
elseif c == 40 then local _, a = duffle.read_parens(inner, inner_pos); inner_pos = a
|
||||
elseif c == 91 then local _, a = duffle.read_brackets(inner, inner_pos); inner_pos = a
|
||||
elseif c == 34 or c == 39 then inner_pos = duffle.skip_str_or_cmt(inner, inner_pos) + 1
|
||||
else inner_pos = inner_pos + 1 end
|
||||
end
|
||||
if depth == 0 then
|
||||
-- scan: <ident>(<name>, { <body> })
|
||||
local name_match = inner:match("^%s*([%w_]+)")
|
||||
local raw_name = name_match or "?"
|
||||
-- Strip "ac_" prefix for component names (components pass convention).
|
||||
local name = raw_name
|
||||
if #raw_name > 3 and raw_name:sub(1, 3) == "ac_" then
|
||||
name = raw_name:sub(4)
|
||||
end
|
||||
local body = inner:sub(last_brace_pos + 1, inner_pos - 1)
|
||||
local body_off = open_paren + 1 + last_brace_pos
|
||||
atoms[#atoms + 1] = {
|
||||
line = line_of(pos), name = name, body = body, body_off = body_off + 1,
|
||||
kind = kind, raw_name = raw_name,
|
||||
ident_pos = pos, after_paren = after_paren,
|
||||
args = nil, comment = nil,
|
||||
}
|
||||
end
|
||||
end
|
||||
pos = after_paren
|
||||
else
|
||||
-- MipsAtom_(name) { body } OR MipsAtomComp_(name) { body }
|
||||
local name_start = 1
|
||||
while name_start <= #inner and inner:sub(name_start, name_start):match("[%s]") do name_start = name_start + 1 end
|
||||
local name_end = name_start
|
||||
while name_end <= #inner and inner:sub(name_end, name_end):match("[%w_]") do name_end = name_end + 1 end
|
||||
local raw_name = inner:sub(name_start, name_end - 1)
|
||||
-- scan: <ident>(<name>)
|
||||
if raw_name ~= "" then
|
||||
local brace = duffle.scan_to_char(source, "{", after_paren)
|
||||
-- scan: <ident>(<name>) {
|
||||
if brace then
|
||||
local body, after_brace = duffle.read_braces(source, brace)
|
||||
-- scan: <ident>(<name>) { <body> }
|
||||
-- Strip "ac_" prefix for component names (components pass convention).
|
||||
local disp_name = raw_name
|
||||
if is_comp and #raw_name > 3 and raw_name:sub(1, 3) == "ac_" then
|
||||
disp_name = raw_name:sub(4)
|
||||
end
|
||||
atoms[#atoms + 1] = {
|
||||
line = line_of(pos), name = disp_name, body = body, body_off = brace + 1,
|
||||
kind = kind, raw_name = raw_name,
|
||||
ident_pos = pos, after_paren = after_paren,
|
||||
args = nil, comment = nil,
|
||||
}
|
||||
pos = after_brace
|
||||
if pp_pos then
|
||||
pos = pp_pos
|
||||
else
|
||||
-- Skip C qualifiers (static, const, etc.) that may precede a declaration.
|
||||
pos = scan_skip_qualifiers(source, pos)
|
||||
if pos <= src_len then
|
||||
local ident, ident_end = duffle.read_ident(source, pos)
|
||||
if ident then
|
||||
local parser = DECL_PARSERS[ident]
|
||||
if parser then
|
||||
pos = parser(source, pos, ident_end, line_of, out)
|
||||
else
|
||||
pos = open_paren + 1
|
||||
-- Unrecognized ident — advance past it.
|
||||
pos = ident_end
|
||||
end
|
||||
else
|
||||
pos = open_paren + 1
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- For MipsAtom_ entries: check if atom_info(...) follows.
|
||||
if is_atom then
|
||||
local lookahead = duffle.skip_ws_and_cmt(source, after_paren)
|
||||
local look_ident, look_end = duffle.read_ident(source, lookahead)
|
||||
-- scan: MipsAtom_(<name>) <look_ident>
|
||||
if look_ident == "atom_info" then
|
||||
local info_open = duffle.skip_ws_and_cmt(source, look_end)
|
||||
if source:sub(info_open, info_open) == "(" then
|
||||
local info_inner, info_after = duffle.read_parens(source, info_open)
|
||||
-- scan: MipsAtom_(<name>) atom_info(<binds>, <reads>, <writes>)
|
||||
-- Find the atom name from the just-parsed atom entry (last one added).
|
||||
local last_atom = atoms[#atoms]
|
||||
local atom_name = last_atom and last_atom.raw_name or "?"
|
||||
local ai_binds, ai_reads, ai_writes = scan_atom_info_subcalls(info_inner)
|
||||
atom_infos[#atom_infos + 1] = {
|
||||
atom_name = atom_name, binds = ai_binds,
|
||||
reads = ai_reads or {}, writes = ai_writes or {},
|
||||
info_line = line_of(lookahead),
|
||||
}
|
||||
-- Don't advance pos past info_after — the body { ... } still needs to be skipped
|
||||
-- by the brace scan below. But if there's no body (forward decl), advance.
|
||||
local body_brace = duffle.scan_to_char(source, "{", info_after)
|
||||
if body_brace then
|
||||
local _, after_body = duffle.read_braces(source, body_brace)
|
||||
pos = after_body
|
||||
else
|
||||
pos = info_after
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
goto continue
|
||||
end
|
||||
|
||||
-- ── MipsCode code_<name> { body } (raw atom form — offsets pass only) ──
|
||||
if ident == "MipsCode" then
|
||||
local next_pos = duffle.skip_ws_and_cmt(source, ident_end)
|
||||
local next_ident, next_after = duffle.read_ident(source, next_pos)
|
||||
-- scan: MipsCode <next_ident>
|
||||
if next_ident and #next_ident > 5 and next_ident:sub(1, 5) == "code_" then
|
||||
local atom_name = next_ident:sub(6)
|
||||
-- scan: MipsCode code_<name>
|
||||
local brace_pos = duffle.scan_to_char(source, "{", next_after)
|
||||
-- scan: MipsCode code_<name> {
|
||||
if brace_pos then
|
||||
local body, after_brace = duffle.read_braces(source, brace_pos)
|
||||
-- scan: MipsCode code_<name> { <body> }
|
||||
raw_atoms[#raw_atoms + 1] = {
|
||||
line = line_of(pos), name = atom_name, body = body, body_off = brace_pos + 1,
|
||||
kind = "raw_atom", raw_name = atom_name,
|
||||
}
|
||||
pos = after_brace
|
||||
goto continue
|
||||
end
|
||||
end
|
||||
pos = ident_end
|
||||
goto continue
|
||||
end
|
||||
|
||||
-- ── typedef Struct_(Binds_X) { fields } ──
|
||||
if ident == "typedef" then
|
||||
local after_typedef = duffle.skip_ws_and_cmt(source, ident_end)
|
||||
local id2, id2_end = duffle.read_ident(source, after_typedef)
|
||||
-- scan: typedef <id2>
|
||||
if id2 == "Struct_" then
|
||||
local open_paren = duffle.skip_ws_and_cmt(source, id2_end)
|
||||
if source:sub(open_paren, open_paren) == "(" then
|
||||
local inner, after_paren = duffle.read_parens(source, open_paren)
|
||||
-- scan: typedef Struct_(<name>)
|
||||
local name = duffle.trim(inner)
|
||||
local brace = duffle.scan_to_char(source, "{", after_paren)
|
||||
-- scan: typedef Struct_(<name>) {
|
||||
if brace then
|
||||
local body, after_brace = duffle.read_braces(source, brace)
|
||||
-- scan: typedef Struct_(<name>) { <fields> }
|
||||
if name:sub(1, 6) == "Binds_" then
|
||||
local fields, byte_off = scan_binds_fields(body)
|
||||
binds[#binds + 1] = { line = line_of(pos), name = name, fields = fields, bytes = byte_off }
|
||||
end
|
||||
pos = after_brace
|
||||
goto continue
|
||||
end
|
||||
pos = open_paren + 1
|
||||
goto continue
|
||||
end
|
||||
pos = id2_end or (after_typedef + 1)
|
||||
goto continue
|
||||
end
|
||||
pos = ident_end
|
||||
goto continue
|
||||
end
|
||||
|
||||
-- ── _Pragma("mac_X tape_atom words=N") (operator form) ──
|
||||
if ident == "_Pragma" then
|
||||
local open_paren = duffle.skip_ws_and_cmt(source, ident_end)
|
||||
if source:sub(open_paren, open_paren) == "(" then
|
||||
local str, str_end = duffle.read_parens(source, open_paren)
|
||||
-- scan: _Pragma(<string>)
|
||||
str = duffle.trim(str)
|
||||
if str:sub(1, 1) == '"' and str:sub(-1) == '"' then
|
||||
local inner = str:sub(2, -2)
|
||||
local space = duffle.find_byte(inner, 32, 1)
|
||||
if space then
|
||||
local name = inner:sub(1, space - 1)
|
||||
local rest = inner:sub(space + 1)
|
||||
local eq = duffle.find_byte(rest, 61, 1)
|
||||
if eq then
|
||||
local key = duffle.trim(rest:sub(1, eq - 1))
|
||||
local val = duffle.trim(rest:sub(eq + 1))
|
||||
if key == "tape_atom words" or key == "words" then
|
||||
macros[#macros + 1] = { line = line_of(pos), name = name, words = tonumber(val) or 0 }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
pos = str_end
|
||||
goto continue
|
||||
end
|
||||
pos = open_paren + 1
|
||||
goto continue
|
||||
end
|
||||
|
||||
-- ── #pragma mac_X tape_atom words=N (directive form) ──
|
||||
-- (preprocessor skip above handles # lines, but pragma is an ident here
|
||||
-- only if it appeared without a leading # — which happens when the
|
||||
-- preprocessor skip didn't fire because the # was on a previous line.
|
||||
-- The annotation pass handles this via its own skip_preprocessor_line,
|
||||
-- but scan_source handles it here by checking the ident.)
|
||||
if ident == "pragma" then
|
||||
-- This shouldn't normally fire — #pragma lines are skipped by
|
||||
-- skip_preprocessor_line above. If we get here, it's a _Pragma
|
||||
-- variant or a non-#-prefixed pragma. Just advance.
|
||||
pos = ident_end
|
||||
goto continue
|
||||
end
|
||||
|
||||
-- ── Unrecognized ident — advance past it ──
|
||||
pos = ident_end
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
return {
|
||||
atoms = atoms,
|
||||
raw_atoms = raw_atoms,
|
||||
binds = binds,
|
||||
atom_infos = atom_infos,
|
||||
macros = macros,
|
||||
line_of = line_of,
|
||||
}
|
||||
return out
|
||||
end
|
||||
|
||||
-- ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user