mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-08-05 23:28:47 +00:00
lua metaprogram: more cruft removal.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
#ifdef INTELLISENSE_DIRECTIVES
|
#ifdef INTELLISENSE_DIRECTIVES
|
||||||
#pragma once
|
#pragma once
|
||||||
#endif
|
#endif
|
||||||
// Auto-generated by tape_atom_annotation_pass.lua — DO NOT EDIT
|
// Auto-generated by ps1_meta.lua — DO NOT EDIT
|
||||||
// Source: C:\projects\Pikuma\ps1\code\duffle\lottes_tape.h
|
// Source: C:\projects\Pikuma\ps1\code\duffle\lottes_tape.h
|
||||||
// Component atoms (MipsAtomComp_(ac_*)) -> macro variants (mac_*)
|
// Component atoms (MipsAtomComp_(ac_*)) -> macro variants (mac_*)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Auto-generated by ps1_meta.lua (passes/offsets.lua) — DO NOT EDIT
|
// Auto-generated by ps1_meta.lua (passes/offsets.lua) — DO NOT EDIT
|
||||||
// Source: C:\projects\Pikuma\ps1\code\duffle\lottes_tape.h
|
// Source: code\duffle\lottes_tape.h
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#pragma region lottes_tape
|
#pragma region lottes_tape
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
// Auto-generated by ps1_meta.lua (passes/offsets.lua) — DO NOT EDIT
|
|
||||||
// Source: C:\projects\Pikuma\ps1\code\gte_hello\hello_gte_tape.c
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#pragma region hello_gte_tape
|
|
||||||
|
|
||||||
|
|
||||||
// --- atom: cube_g4_face (87 words) ---
|
|
||||||
|
|
||||||
#define _atom_offset_cull_cube_g4_face_exit 48
|
|
||||||
#define _atom_offset_bounds_chk_cube_g4_face_exit 12
|
|
||||||
|
|
||||||
enum {
|
|
||||||
atom_offset_cull_cube_g4_face_exit = _atom_offset_cull_cube_g4_face_exit,
|
|
||||||
atom_offset_bounds_chk_cube_g4_face_exit = _atom_offset_bounds_chk_cube_g4_face_exit,
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- atom: floor_f3_face (66 words) ---
|
|
||||||
|
|
||||||
#define _atom_offset_culling_floor_f3_face_exit 29
|
|
||||||
#define _atom_offset_bounds_chk_floor_f3_face_exit 13
|
|
||||||
|
|
||||||
enum {
|
|
||||||
atom_offset_culling_floor_f3_face_exit = _atom_offset_culling_floor_f3_face_exit,
|
|
||||||
atom_offset_bounds_chk_floor_f3_face_exit = _atom_offset_bounds_chk_floor_f3_face_exit,
|
|
||||||
};
|
|
||||||
|
|
||||||
#pragma endregion hello_gte_tape
|
|
||||||
|
|
||||||
@@ -123,7 +123,6 @@ MipsAtom_(floor_f3_face) atom_info(
|
|||||||
nop,
|
nop,
|
||||||
branch_le_zero(R_T0, atom_offset(culling, floor_f3_face_exit)), nop,
|
branch_le_zero(R_T0, atom_offset(culling, floor_f3_face_exit)), nop,
|
||||||
/* Format Primitive */
|
/* Format Primitive */
|
||||||
// mac_format_f3_color(0x20FF, 0xFFFF), // works
|
|
||||||
mac_format_f3_color(0xFF, 0xFF, 0xFF), // RGB-form (R=FF, G=FF, B=FF = white)
|
mac_format_f3_color(0xFF, 0xFF, 0xFF), // RGB-form (R=FF, G=FF, B=FF = white)
|
||||||
mac_gte_store_f3_post_rtpt(),
|
mac_gte_store_f3_post_rtpt(),
|
||||||
|
|
||||||
|
|||||||
+176
-179
@@ -18,12 +18,12 @@
|
|||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local BLOCK_OPEN = {
|
local BLOCK_OPEN = {
|
||||||
["do"] = true,
|
["do"] = true,
|
||||||
["function"] = true,
|
["function"] = true,
|
||||||
["if"] = true,
|
["if"] = true,
|
||||||
["for"] = true,
|
["for"] = true,
|
||||||
["while"] = true,
|
["while"] = true,
|
||||||
["repeat"] = true,
|
["repeat"] = true,
|
||||||
}
|
}
|
||||||
|
|
||||||
local function is_block_close(token) return token == "end" or token == "until" end
|
local function is_block_close(token) return token == "end" or token == "until" end
|
||||||
@@ -31,121 +31,118 @@ local function is_block_close(token) return token == "end" or token == "until" e
|
|||||||
-- (internal) Walk one source file and return a list of
|
-- (internal) Walk one source file and return a list of
|
||||||
-- {line, depth, token} entries where depth > max_nesting.
|
-- {line, depth, token} entries where depth > max_nesting.
|
||||||
local function audit_file(path, max_nesting)
|
local function audit_file(path, max_nesting)
|
||||||
local f = io.open(path, "r")
|
local f = io.open(path, "r")
|
||||||
if not f then error("Cannot open " .. path) end
|
if not f then error("Cannot open " .. path) end
|
||||||
local content = f:read("*a")
|
local content = f:read("*a")
|
||||||
f:close()
|
f:close()
|
||||||
|
|
||||||
local violations = {}
|
local violations = {}
|
||||||
local depth = 0
|
local depth = 0
|
||||||
local line = 1
|
local line = 1
|
||||||
local pos = 1
|
local pos = 1
|
||||||
local src_len = #content
|
local src_len = #content
|
||||||
local token_idx = 0
|
local token_idx = 0
|
||||||
|
|
||||||
local function read_ident_at(start_pos)
|
local function read_ident_at(start_pos)
|
||||||
local ident_start = start_pos
|
local ident_start = start_pos
|
||||||
if ident_start > src_len then return nil end
|
if ident_start > src_len then return nil end
|
||||||
local first_ch = content:sub(ident_start, ident_start)
|
local first_ch = content:sub(ident_start, ident_start)
|
||||||
if not (first_ch:match("[%a_]")) then return nil end
|
if not (first_ch:match("[%a_]")) then return nil end
|
||||||
local scan = start_pos + 1
|
local scan = start_pos + 1
|
||||||
while scan <= src_len do
|
while scan <= src_len do
|
||||||
local ch = content:sub(scan, scan)
|
local ch = content:sub(scan, scan)
|
||||||
if not (ch:match("[%w_]")) then break end
|
if not (ch:match("[%w_]")) then break end
|
||||||
scan = scan + 1
|
scan = scan + 1
|
||||||
end
|
end
|
||||||
return content:sub(ident_start, scan - 1), scan
|
return content:sub(ident_start, scan - 1), scan
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Skip past a string literal or comment starting at `start_pos`.
|
-- Skip past a string literal or comment starting at `start_pos`.
|
||||||
-- Returns the position just past the construct, or nil if `start_pos`
|
-- Returns the position just past the construct, or nil if `start_pos`
|
||||||
-- is not the start of a string/comment.
|
-- is not the start of a string/comment.
|
||||||
local function skip_string_or_comment(start_pos)
|
local function skip_string_or_comment(start_pos)
|
||||||
local ch = content:sub(start_pos, start_pos)
|
local ch = content:sub(start_pos, start_pos)
|
||||||
if ch == '"' or ch == "'" then
|
if ch == '"' or ch == "'" then
|
||||||
local scan = start_pos + 1
|
local scan = start_pos + 1
|
||||||
while scan <= src_len do
|
while scan <= src_len do
|
||||||
local c = content:sub(scan, scan)
|
local c = content:sub(scan, scan)
|
||||||
if c == "\\" then
|
if c == "\\" then scan = scan + 2
|
||||||
scan = scan + 2
|
elseif c == ch then return scan + 1
|
||||||
elseif c == ch then
|
else scan = scan + 1
|
||||||
return scan + 1
|
end
|
||||||
else
|
end
|
||||||
scan = scan + 1
|
return src_len + 1
|
||||||
end
|
elseif ch == "-" and content:sub(start_pos + 1, start_pos + 1) == "-" then
|
||||||
end
|
local scan = start_pos + 2
|
||||||
return src_len + 1
|
if content:sub(scan, scan + 1) == "[[" and content:sub(scan + 2, scan + 3) == "[" then
|
||||||
elseif ch == "-" and content:sub(start_pos + 1, start_pos + 1) == "-" then
|
-- Long bracket comment [==[ ... ]==]
|
||||||
local scan = start_pos + 2
|
scan = scan + 2
|
||||||
if content:sub(scan, scan + 1) == "[[" and content:sub(scan + 2, scan + 3) == "[" then
|
local eq = ""
|
||||||
-- Long bracket comment [==[ ... ]==]
|
while content:sub(scan, scan) == "=" do
|
||||||
scan = scan + 2
|
eq = eq .. "="
|
||||||
local eq = ""
|
scan = scan + 1
|
||||||
while content:sub(scan, scan) == "=" do
|
end
|
||||||
eq = eq .. "="
|
local close_marker = "]" .. eq .. "]"
|
||||||
scan = scan + 1
|
local close_pos = content:find(close_marker, scan, true)
|
||||||
end
|
if close_pos then
|
||||||
local close_marker = "]" .. eq .. "]"
|
return close_pos + #close_marker
|
||||||
local close_pos = content:find(close_marker, scan, true)
|
else
|
||||||
if close_pos then
|
return src_len + 1
|
||||||
return close_pos + #close_marker
|
end
|
||||||
else
|
else
|
||||||
return src_len + 1
|
while scan <= src_len and content:sub(scan, scan) ~= "\n" do scan = scan + 1 end
|
||||||
end
|
return scan + 1
|
||||||
else
|
end
|
||||||
while scan <= src_len and content:sub(scan, scan) ~= "\n" do scan = scan + 1 end
|
elseif ch == "[" and content:sub(start_pos + 1, start_pos + 1) == "[" then
|
||||||
return scan + 1
|
local scan = start_pos + 2
|
||||||
end
|
local eq = ""
|
||||||
elseif ch == "[" and content:sub(start_pos + 1, start_pos + 1) == "[" then
|
while content:sub(scan, scan) == "=" do
|
||||||
local scan = start_pos + 2
|
eq = eq .. "="
|
||||||
local eq = ""
|
scan = scan + 1
|
||||||
while content:sub(scan, scan) == "=" do
|
end
|
||||||
eq = eq .. "="
|
local close_marker = "]" .. eq .. "]"
|
||||||
scan = scan + 1
|
local close_pos = content:find(close_marker, scan, true)
|
||||||
end
|
if close_pos then
|
||||||
local close_marker = "]" .. eq .. "]"
|
return close_pos + #close_marker
|
||||||
local close_pos = content:find(close_marker, scan, true)
|
else
|
||||||
if close_pos then
|
return src_len + 1
|
||||||
return close_pos + #close_marker
|
end
|
||||||
else
|
end
|
||||||
return src_len + 1
|
return nil
|
||||||
end
|
end
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
while pos <= src_len do
|
while pos <= src_len do
|
||||||
local ch = content:sub(pos, pos)
|
local ch = content:sub(pos, pos)
|
||||||
if ch == "\n" then line = line + 1 end
|
if ch == "\n" then line = line + 1 end
|
||||||
|
|
||||||
local skip_to = skip_string_or_comment(pos)
|
local skip_to = skip_string_or_comment(pos)
|
||||||
if skip_to then
|
if skip_to then
|
||||||
for scan = pos, skip_to - 1 do
|
for scan = pos, skip_to - 1 do
|
||||||
if content:sub(scan, scan) == "\n" then line = line + 1 end
|
if content:sub(scan, scan) == "\n" then line = line + 1 end
|
||||||
end
|
end
|
||||||
pos = skip_to
|
pos = skip_to
|
||||||
elseif ch:match("[%a_]") then
|
elseif ch:match("[%a_]") then
|
||||||
local tok, next_pos = read_ident_at(pos)
|
local tok, next_pos = read_ident_at(pos)
|
||||||
token_idx = token_idx + 1
|
token_idx = token_idx + 1
|
||||||
if BLOCK_OPEN[tok] then
|
if BLOCK_OPEN[tok] then
|
||||||
depth = depth + 1
|
depth = depth + 1
|
||||||
if depth > max_nesting then
|
if depth > max_nesting then
|
||||||
violations[#violations + 1] = {
|
violations[#violations + 1] = {
|
||||||
line = line,
|
line = line,
|
||||||
depth = depth,
|
depth = depth,
|
||||||
token = tok,
|
token = tok,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
elseif is_block_close(tok) then
|
elseif is_block_close(tok) then
|
||||||
depth = depth - 1
|
depth = depth - 1
|
||||||
end
|
end
|
||||||
pos = next_pos
|
pos = next_pos
|
||||||
else
|
else
|
||||||
pos = pos + 1
|
pos = pos + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return violations
|
return violations
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Audit one file. Returns nil if clean, else a list of violations.
|
--- Audit one file. Returns nil if clean, else a list of violations.
|
||||||
@@ -153,78 +150,78 @@ end
|
|||||||
--- @param max_nesting integer -- default 5
|
--- @param max_nesting integer -- default 5
|
||||||
--- @return table|nil
|
--- @return table|nil
|
||||||
function M.audit(path, max_nesting)
|
function M.audit(path, max_nesting)
|
||||||
local violations = audit_file(path, max_nesting or 5)
|
local violations = audit_file(path, max_nesting or 5)
|
||||||
if #violations == 0 then return nil end
|
if #violations == 0 then return nil end
|
||||||
return violations
|
return violations
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Module CLI.
|
-- Module CLI.
|
||||||
if arg and arg[1] then
|
if arg and arg[1] then
|
||||||
local max_nesting = 5
|
local max_nesting = 5
|
||||||
local files = {}
|
local files = {}
|
||||||
for arg_idx = 1, #arg do
|
for arg_idx = 1, #arg do
|
||||||
if arg[arg_idx] == "--max" and arg[arg_idx + 1] then
|
if arg[arg_idx] == "--max" and arg[arg_idx + 1] then
|
||||||
max_nesting = tonumber(arg[arg_idx + 1]) or 5
|
max_nesting = tonumber(arg[arg_idx + 1]) or 5
|
||||||
else
|
else
|
||||||
files[#files + 1] = arg[arg_idx]
|
files[#files + 1] = arg[arg_idx]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Accept either a directory or a file path. Directory args are
|
-- Accept either a directory or a file path. Directory args are
|
||||||
-- expanded via `dir /b *.lua` (Windows) or `ls *.lua` (Unix).
|
-- expanded via `dir /b *.lua` (Windows) or `ls *.lua` (Unix).
|
||||||
local function is_dir(p)
|
local function is_dir(p)
|
||||||
local f = io.open(p, "r")
|
local f = io.open(p, "r")
|
||||||
if f then f:close() return false end
|
if f then f:close() return false end
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
local function list_lua(dir)
|
local function list_lua(dir)
|
||||||
local out = {}
|
local out = {}
|
||||||
local cmd
|
local cmd
|
||||||
if package.config:sub(1, 1) == "\\" then
|
if package.config:sub(1, 1) == "\\" then
|
||||||
cmd = 'dir /b "' .. dir .. '\\*.lua" 2>nul'
|
cmd = 'dir /b "' .. dir .. '\\*.lua" 2>nul'
|
||||||
else
|
else
|
||||||
cmd = 'ls -1 "' .. dir .. '"/*.lua 2>/dev/null'
|
cmd = 'ls -1 "' .. dir .. '"/*.lua 2>/dev/null'
|
||||||
end
|
end
|
||||||
local p = io.popen(cmd)
|
local p = io.popen(cmd)
|
||||||
if p then
|
if p then
|
||||||
for line in p:lines() do
|
for line in p:lines() do
|
||||||
if line:match("%.lua$") then
|
if line:match("%.lua$") then
|
||||||
out[#out + 1] = dir .. "/" .. line
|
out[#out + 1] = dir .. "/" .. line
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
p:close()
|
p:close()
|
||||||
end
|
end
|
||||||
return out
|
return out
|
||||||
end
|
end
|
||||||
|
|
||||||
local to_check = {}
|
local to_check = {}
|
||||||
for _, f in ipairs(files) do
|
for _, f in ipairs(files) do
|
||||||
if is_dir(f) then
|
if is_dir(f) then
|
||||||
for _, sub in ipairs(list_lua(f)) do to_check[#to_check + 1] = sub end
|
for _, sub in ipairs(list_lua(f)) do to_check[#to_check + 1] = sub end
|
||||||
else
|
else
|
||||||
to_check[#to_check + 1] = f
|
to_check[#to_check + 1] = f
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local total_violations = 0
|
local total_violations = 0
|
||||||
for _, f in ipairs(to_check) do
|
for _, f in ipairs(to_check) do
|
||||||
local v = M.audit(f, max_nesting)
|
local v = M.audit(f, max_nesting)
|
||||||
if v then
|
if v then
|
||||||
io.write(string.format("\n%s\n", f))
|
io.write(string.format("\n%s\n", f))
|
||||||
for _, x in ipairs(v) do
|
for _, x in ipairs(v) do
|
||||||
io.write(string.format(" line %d: depth %d (after '%s')\n", x.line, x.depth, x.token))
|
io.write(string.format(" line %d: depth %d (after '%s')\n", x.line, x.depth, x.token))
|
||||||
end
|
end
|
||||||
total_violations = total_violations + #v
|
total_violations = total_violations + #v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if total_violations == 0 then
|
if total_violations == 0 then
|
||||||
io.write("OK: no files exceed max nesting of " .. max_nesting .. "\n")
|
io.write("OK: no files exceed max nesting of " .. max_nesting .. "\n")
|
||||||
os.exit(0)
|
os.exit(0)
|
||||||
else
|
else
|
||||||
io.write(string.format("\n%d nesting violation(s) found.\n", total_violations))
|
io.write(string.format("\n%d nesting violation(s) found.\n", total_violations))
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
+37
-28
@@ -15,10 +15,8 @@
|
|||||||
--- Lua 5.3 compatible; no `<close>`/`<toclose>`, no `continue`, no
|
--- Lua 5.3 compatible; no `<close>`/`<toclose>`, no `continue`, no
|
||||||
--- 5.4 string.dump improvements. LuaJIT 5.1+extensions model is the primary target.
|
--- 5.4 string.dump improvements. LuaJIT 5.1+extensions model is the primary target.
|
||||||
---
|
---
|
||||||
--- **No `:match` / `:gmatch` regex use anywhere**; all delimiter-
|
--- **No `:match` / `:gmatch` regex use anywhere**;
|
||||||
--- splitting is hand-rolled or via LPeg (the regex-free PEG library).
|
--- all delimiter-splitting is hand-rolled or via LPeg (the regex-free PEG library).
|
||||||
--- The hot lexer primitives are LPeg-backed where it pays off;
|
|
||||||
--- hand-rolled variants remain for callers that need a fallback.
|
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
@@ -320,21 +318,17 @@ function M._reset_ensured_dirs() _ensured_dirs = {} end
|
|||||||
|
|
||||||
-- Skip a string or C-style comment starting at position `pos`.
|
-- Skip a string or C-style comment starting at position `pos`.
|
||||||
-- Returns the position just past the construct, or `pos` unchanged if no string/comment starts there. LPeg-backed.
|
-- Returns the position just past the construct, or `pos` unchanged if no string/comment starts there. LPeg-backed.
|
||||||
function M.skip_str_or_cmt(s, pos)
|
function M.skip_str_or_cmt(s, pos) return lpeg.match(lpeg_str_or_cmt_pat, s, pos) or pos end
|
||||||
return lpeg.match(lpeg_str_or_cmt_pat, s, pos) or pos
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Skip whitespace AND C-style comments starting at position `pos`.
|
-- Skip whitespace AND C-style comments starting at position `pos`.
|
||||||
-- LPeg-backed; ~5-10x faster than a hand-rolled byte-by-byte walker.
|
-- LPeg-backed; ~5-10x faster than a hand-rolled byte-by-byte walker.
|
||||||
function M.skip_ws_and_cmt(s, pos)
|
function M.skip_ws_and_cmt(s, pos) return lpeg.match(lpeg_ws_and_cmt_pat, s, pos) or pos end
|
||||||
return lpeg.match(lpeg_ws_and_cmt_pat, s, pos) or pos
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Read a C-style identifier (alpha followed by zero+ alnum) starting at position `pos`.
|
-- Read a C-style identifier (alpha followed by zero+ alnum) starting at position `pos`.
|
||||||
-- Returns the identifier string + the position just past it, or nil + pos if no identifier starts here. LPeg-backed.
|
-- Returns the identifier string + the position just past it, or nil + pos if no identifier starts here. LPeg-backed.
|
||||||
function M.read_ident(s, pos)
|
function M.read_ident(s, pos)
|
||||||
local result = lpeg.match(lpeg_ident_pat, s, pos)
|
local result = lpeg.match(lpeg_ident_pat, s, pos)
|
||||||
if result then return result, pos + #result end
|
if result then return result, pos + #result end
|
||||||
return nil, pos
|
return nil, pos
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -344,7 +338,9 @@ end
|
|||||||
function M.read_balanced(s, open_char, close_char, pos)
|
function M.read_balanced(s, open_char, close_char, pos)
|
||||||
local open_byte = open_char:byte()
|
local open_byte = open_char:byte()
|
||||||
if s:byte(pos) ~= open_byte then return nil, pos end
|
if s:byte(pos) ~= open_byte then return nil, pos end
|
||||||
|
-- scan: <open_char>
|
||||||
pos = pos + 1
|
pos = pos + 1
|
||||||
|
-- scan: <open_char> <inner...>
|
||||||
local len = #s
|
local len = #s
|
||||||
local depth = 1
|
local depth = 1
|
||||||
local a = pos
|
local a = pos
|
||||||
@@ -353,15 +349,23 @@ function M.read_balanced(s, open_char, close_char, pos)
|
|||||||
if c == open_byte then
|
if c == open_byte then
|
||||||
depth = depth + 1
|
depth = depth + 1
|
||||||
pos = pos + 1
|
pos = pos + 1
|
||||||
|
-- scan: <open_char> <inner...> <open_char> (depth=depth)
|
||||||
elseif c == close_char:byte() then
|
elseif c == close_char:byte() then
|
||||||
depth = depth - 1
|
depth = depth - 1
|
||||||
if depth == 0 then break end
|
if depth == 0 then break end
|
||||||
pos = pos + 1
|
pos = pos + 1
|
||||||
|
-- scan: <open_char> <inner...> <close_char> (depth=depth)
|
||||||
else
|
else
|
||||||
local nx = M.skip_str_or_cmt(s, pos)
|
local nx = M.skip_str_or_cmt(s, pos)
|
||||||
if nx > pos then pos = nx else pos = pos + 1 end
|
if nx > pos then
|
||||||
|
-- scan: <open_char> <inner...> <str|cmt>
|
||||||
|
pos = nx
|
||||||
|
else
|
||||||
|
pos = pos + 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
-- scan: <open_char> <inner> <close_char>
|
||||||
return s:sub(a, pos - 1), pos + 1
|
return s:sub(a, pos - 1), pos + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -379,12 +383,17 @@ function M.scan_to_char(s, target, start)
|
|||||||
while pos <= #s do
|
while pos <= #s do
|
||||||
local c = s:byte(pos)
|
local c = s:byte(pos)
|
||||||
if c == target_byte then return pos end
|
if c == target_byte then return pos end
|
||||||
|
-- scan: ... <target found> | <skipping to target>
|
||||||
if c == BYTE_OPEN_PAREN then local _, a = M.read_balanced(s, "(", ")", pos); pos = a
|
if c == BYTE_OPEN_PAREN then local _, a = M.read_balanced(s, "(", ")", pos); pos = a
|
||||||
|
-- scan: ... ( <balanced> ) ...
|
||||||
elseif c == BYTE_OPEN_BRACE then local _, a = M.read_balanced(s, "{", "}", pos); pos = a
|
elseif c == BYTE_OPEN_BRACE then local _, a = M.read_balanced(s, "{", "}", pos); pos = a
|
||||||
|
-- scan: ... { <balanced> } ...
|
||||||
elseif c == BYTE_OPEN_BRACK then local _, a = M.read_balanced(s, "[", "]", pos); pos = a
|
elseif c == BYTE_OPEN_BRACK then local _, a = M.read_balanced(s, "[", "]", pos); pos = a
|
||||||
|
-- scan: ... [ <balanced> ] ...
|
||||||
else
|
else
|
||||||
local nx = M.skip_str_or_cmt(s, pos)
|
local nx = M.skip_str_or_cmt(s, pos)
|
||||||
pos = (nx > pos) and nx or (pos + 1)
|
pos = (nx > pos) and nx or (pos + 1)
|
||||||
|
-- scan: ... <str|cmt skipped> ...
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return nil
|
return nil
|
||||||
@@ -447,28 +456,29 @@ function M.split_top_level_commas(body)
|
|||||||
end
|
end
|
||||||
|
|
||||||
while pos <= body_len do
|
while pos <= body_len do
|
||||||
local c = body:byte(pos)
|
local c = body:byte(pos)
|
||||||
if c == BYTE_OPEN_PAREN then -- '('
|
if c == BYTE_OPEN_PAREN then local _, a = M.read_parens(body, pos); pos = a -- scan: ... ( <balanced> ...
|
||||||
local _, a = M.read_parens(body, pos); pos = a
|
elseif c == BYTE_OPEN_BRACE then local _, a = M.read_braces(body, pos); pos = a -- scan: ... { <balanced> ...
|
||||||
elseif c == BYTE_OPEN_BRACE then -- '{'
|
elseif c == BYTE_OPEN_BRACK then local _, a = M.read_brackets(body, pos); pos = a -- scan: ... ( <balanced> ...
|
||||||
local _, a = M.read_braces(body, pos); pos = a
|
elseif c == BYTE_COMMA then
|
||||||
elseif c == BYTE_OPEN_BRACK then -- '['
|
-- scan: ... <token> , <next> ...
|
||||||
local _, a = M.read_brackets(body, pos); pos = a
|
|
||||||
elseif c == BYTE_COMMA then -- ','
|
|
||||||
emit(pos - 1)
|
emit(pos - 1)
|
||||||
pos = pos + 1
|
pos = pos + 1
|
||||||
token_start = pos
|
token_start = pos
|
||||||
elseif c == BYTE_SEMI then -- ';'
|
elseif c == BYTE_SEMI then
|
||||||
|
-- scan: ... <token> ; <next> ...
|
||||||
emit(pos - 1)
|
emit(pos - 1)
|
||||||
pos = pos + 1
|
pos = pos + 1
|
||||||
token_start = pos
|
token_start = pos
|
||||||
elseif c == BYTE_NEWLINE then -- '\n'
|
elseif c == BYTE_NEWLINE then
|
||||||
|
-- scan: ... <token> \n <next> ...
|
||||||
emit(pos - 1)
|
emit(pos - 1)
|
||||||
pos = pos + 1
|
pos = pos + 1
|
||||||
token_start = pos
|
token_start = pos
|
||||||
else
|
else
|
||||||
local nx = M.skip_str_or_cmt(body, pos)
|
local nx = M.skip_str_or_cmt(body, pos)
|
||||||
if nx > pos then
|
if nx > pos then
|
||||||
|
-- scan: ... <str|cmt> ...
|
||||||
-- Skipped a comment or string at top level: emit token break.
|
-- Skipped a comment or string at top level: emit token break.
|
||||||
pos = nx
|
pos = nx
|
||||||
emit(pos - 1)
|
emit(pos - 1)
|
||||||
@@ -477,6 +487,7 @@ function M.split_top_level_commas(body)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
-- scan: <token> , <token> , ... <token>
|
||||||
emit(body_len)
|
emit(body_len)
|
||||||
return tokens
|
return tokens
|
||||||
end
|
end
|
||||||
@@ -495,6 +506,7 @@ function M.load_word_counts(metadata_path)
|
|||||||
local nl = M.find_byte(content, BYTE_NEWLINE, pos)
|
local nl = M.find_byte(content, BYTE_NEWLINE, pos)
|
||||||
local line_end = nl or (len + 1)
|
local line_end = nl or (len + 1)
|
||||||
local line = content:sub(pos, line_end - 1)
|
local line = content:sub(pos, line_end - 1)
|
||||||
|
-- scan: WORD_COUNT(<name>, <N>)
|
||||||
local trimmed = M.trim(line)
|
local trimmed = M.trim(line)
|
||||||
if trimmed:sub(1, #prefix) == prefix and trimmed:sub(-1) == ")" then
|
if trimmed:sub(1, #prefix) == prefix and trimmed:sub(-1) == ")" then
|
||||||
local inner = trimmed:sub(#prefix + 1, #trimmed - 1)
|
local inner = trimmed:sub(#prefix + 1, #trimmed - 1)
|
||||||
@@ -527,11 +539,8 @@ function M.LineIndex(source)
|
|||||||
local lo, hi = 1, n
|
local lo, hi = 1, n
|
||||||
while lo <= hi do
|
while lo <= hi do
|
||||||
local mid = math.floor((lo + hi) / 2)
|
local mid = math.floor((lo + hi) / 2)
|
||||||
if positions[mid] <= query_pos then
|
if positions[mid] <= query_pos then lo = mid + 1
|
||||||
lo = mid + 1
|
else hi = mid - 1 end
|
||||||
else
|
|
||||||
hi = mid - 1
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
return hi + 1
|
return hi + 1
|
||||||
end
|
end
|
||||||
@@ -666,7 +675,7 @@ M.GP0_MACRO_CONTRIB = {
|
|||||||
["mac_insert_ot_tag_g4"] = 1,
|
["mac_insert_ot_tag_g4"] = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Per-macro cycle cost (best-case, no stalls). Used by the static-analysis `count_atom_cycles` pass (Phase 3) to emit per-atom cycle budgets.
|
-- Per-macro cycle cost (best-case, no stalls). Used by the static-analysis pass to emit per-atom cycle budgets.
|
||||||
-- The counts cover the EXPANDED instruction sequence the macro emits (NOT just the token it appears as in source).
|
-- The counts cover the EXPANDED instruction sequence the macro emits (NOT just the token it appears as in source).
|
||||||
-- For example:
|
-- For example:
|
||||||
-- mac_pack_color_word(off, cmd, r, g, b) emits:
|
-- mac_pack_color_word(off, cmd, r, g, b) emits:
|
||||||
|
|||||||
@@ -149,7 +149,6 @@ local BYTE_COMMA = 44
|
|||||||
--- @field errors Finding[]
|
--- @field errors Finding[]
|
||||||
--- @field warnings Finding[]
|
--- @field warnings Finding[]
|
||||||
--- @field info Finding[]
|
--- @field info Finding[]
|
||||||
--- @field pragmas table -- reserved (currently always nil; legacy compat)
|
|
||||||
|
|
||||||
--- ════════════════════════════════════════════════════════════════════════════
|
--- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- split helpers
|
-- split helpers
|
||||||
@@ -239,18 +238,14 @@ local function parse_regs_call(s)
|
|||||||
return spec.kind, inner
|
return spec.kind, inner
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Resolve any phase_* / R_* alias macros in a register list.
|
-- Parse a comma-separated inner content (e.g. inside atom_reads(...)) into a list of trimmed identifiers.
|
||||||
-- (Phase / region / cadence aliases have been dropped. Kept as an identity function so callers can stay uniform.)
|
|
||||||
local function resolve_reg_aliases(regs) 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 function parse_regs_list(inner)
|
||||||
local out = {}
|
local out = {}
|
||||||
for _, r in ipairs(split_csv_top(inner)) do
|
for _, r in ipairs(split_csv_top(inner)) do
|
||||||
local trimmed = trim(r)
|
local trimmed = trim(r)
|
||||||
if trimmed ~= "" then out[#out + 1] = trimmed end
|
if trimmed ~= "" then out[#out + 1] = trimmed end
|
||||||
end
|
end
|
||||||
return resolve_reg_aliases(out)
|
return out
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Parse a single token (from split_csv_top) into an arg entry.
|
-- Parse a single token (from split_csv_top) into an arg entry.
|
||||||
@@ -272,11 +267,11 @@ end
|
|||||||
|
|
||||||
--- Extract identifier args from a parenthesized group.
|
--- Extract identifier args from a parenthesized group.
|
||||||
--- Returns a list of {kind, value} pairs where kind is one of:
|
--- Returns a list of {kind, value} pairs where kind is one of:
|
||||||
--- "ident" -- a bare identifier (e.g. phase_work)
|
--- "ident" -- a bare identifier (e.g. a reserved token)
|
||||||
--- "atom_reads" -- an atom_reads(...) call: value is the register list
|
--- "atom_reads" -- an atom_reads(...) call: value is the register list
|
||||||
--- "atom_writes" -- an atom_writes(...) call: value is the register list
|
--- "atom_writes" -- an atom_writes(...) call: value is the register list
|
||||||
--- "other" -- something we can't classify (preserved as text)
|
--- "other" -- something we can't classify (preserved as text)
|
||||||
local function parse_atom_annot_args(inner)
|
local function parse_atom_info_args(inner)
|
||||||
local args = {}
|
local args = {}
|
||||||
for _, tok in ipairs(split_csv_top(inner)) do
|
for _, tok in ipairs(split_csv_top(inner)) do
|
||||||
local s = trim(tok)
|
local s = trim(tok)
|
||||||
@@ -593,8 +588,7 @@ local function find_atom_names(source)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Find atom annotations (atom_annot / atom_init / atom_setup / atom_commit
|
-- Find atom annotations (atom_info + atom_bind / atom_reads / atom_writes)
|
||||||
-- / atom_bind / atom_terminate)
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
--- True iff the parsed arg is a register-list call (any recognized form).
|
--- True iff the parsed arg is a register-list call (any recognized form).
|
||||||
@@ -612,8 +606,7 @@ function ANNOT_ARG_HANDLERS.info(entry, args)
|
|||||||
elseif arg.kind == "atom_reads" then entry.reads = arg.value
|
elseif arg.kind == "atom_reads" then entry.reads = arg.value
|
||||||
elseif arg.kind == "atom_writes" then entry.writes = arg.value
|
elseif arg.kind == "atom_writes" then entry.writes = arg.value
|
||||||
elseif arg.kind == "ident" then
|
elseif arg.kind == "ident" then
|
||||||
-- Reserved for future phase tokens. Currently ignored.
|
-- Reserved for future use. Currently ignored.
|
||||||
-- (Could be reintroduced as `phase_*` sub-calls of atom_info.)
|
|
||||||
else
|
else
|
||||||
entry.errors[#entry.errors + 1] = string.format("unexpected atom_info arg kind=%s value=%s", arg.kind, tostring(arg.value))
|
entry.errors[#entry.errors + 1] = string.format("unexpected atom_info arg kind=%s value=%s", arg.kind, tostring(arg.value))
|
||||||
end
|
end
|
||||||
@@ -653,7 +646,7 @@ local function parse_atom_info_call(source, atom_name, after_mipsatom_paren, lin
|
|||||||
|
|
||||||
local info_inner, info_after = read_parens(source, info_open)
|
local info_inner, info_after = read_parens(source, info_open)
|
||||||
-- scan: MipsAtom_(<name>) atom_info(<binds>, <reads>, <writes>)
|
-- scan: MipsAtom_(<name>) atom_info(<binds>, <reads>, <writes>)
|
||||||
local args = parse_atom_annot_args(info_inner)
|
local args = parse_atom_info_args(info_inner)
|
||||||
local entry = new_annot_entry(line_of(lookahead), ATOM_INFO, atom_name, "info")
|
local entry = new_annot_entry(line_of(lookahead), ATOM_INFO, atom_name, "info")
|
||||||
ANNOT_ARG_HANDLERS.info(entry, args)
|
ANNOT_ARG_HANDLERS.info(entry, args)
|
||||||
return entry, info_after
|
return entry, info_after
|
||||||
@@ -713,7 +706,7 @@ local function find_atom_annotations(source)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Validation (ported from tape_atom_annotation_pass.lua:1193-1405)
|
-- Validation
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
local function validate(ctx, src)
|
local function validate(ctx, src)
|
||||||
@@ -865,7 +858,6 @@ local function validate(ctx, src)
|
|||||||
atoms = atoms,
|
atoms = atoms,
|
||||||
annots = annots,
|
annots = annots,
|
||||||
macros = macros,
|
macros = macros,
|
||||||
pragmas = pragmas,
|
|
||||||
binds = binds,
|
binds = binds,
|
||||||
errors = errors,
|
errors = errors,
|
||||||
warnings = warnings,
|
warnings = warnings,
|
||||||
|
|||||||
@@ -727,7 +727,7 @@ local function header_boilerplate(src)
|
|||||||
"#ifdef INTELLISENSE_DIRECTIVES",
|
"#ifdef INTELLISENSE_DIRECTIVES",
|
||||||
"#pragma once",
|
"#pragma once",
|
||||||
"#endif",
|
"#endif",
|
||||||
"// Auto-generated by tape_atom_annotation_pass.lua — DO NOT EDIT",
|
"// Auto-generated by ps1_meta.lua — DO NOT EDIT",
|
||||||
"// Source: " .. to_absolute_path(src.path),
|
"// Source: " .. to_absolute_path(src.path),
|
||||||
"// Component atoms (MipsAtomComp_(ac_*)) -> macro variants (mac_*)",
|
"// Component atoms (MipsAtomComp_(ac_*)) -> macro variants (mac_*)",
|
||||||
"",
|
"",
|
||||||
|
|||||||
@@ -1039,9 +1039,6 @@ end
|
|||||||
--- has_loops - true iff a path re-entered a token it had visited
|
--- has_loops - true iff a path re-entered a token it had visited
|
||||||
--- (warning; loop bodies aren't supported)
|
--- (warning; loop bodies aren't supported)
|
||||||
--- unknown_macros - list of unique macro names not in duffle.INSTRUCTION_LATENCY
|
--- unknown_macros - list of unique macro names not in duffle.INSTRUCTION_LATENCY
|
||||||
--- cycles_full - sum of ALL token costs (the previous "best case"
|
|
||||||
--- value; included for backward-compat; double-counts
|
|
||||||
--- the BD-slot nop relative to cycles_min/max)
|
|
||||||
local function analyze_atom_paths(atom)
|
local function analyze_atom_paths(atom)
|
||||||
local tokens = tokenize_body(atom.body)
|
local tokens = tokenize_body(atom.body)
|
||||||
local labels = find_atom_labels(tokens)
|
local labels = find_atom_labels(tokens)
|
||||||
@@ -1173,13 +1170,8 @@ local function analyze_atom_paths(atom)
|
|||||||
end
|
end
|
||||||
if n >= 1 then dfs(1, 0, {}) end
|
if n >= 1 then dfs(1, 0, {}) end
|
||||||
|
|
||||||
-- cycles_full: sum of every token's cost (the legacy sum-of-all-tokens
|
|
||||||
-- value; over-counts BD-slot nops relative to the path-aware min/max).
|
|
||||||
local cycles_full = 0
|
|
||||||
for tok_idx = 1, n do cycles_full = cycles_full + costs[tok_idx] end
|
|
||||||
|
|
||||||
-- If no paths were recorded (e.g. atom body is empty), cycles_min/max
|
-- If no paths were recorded (e.g. atom body is empty), cycles_min/max
|
||||||
-- default to 0 (atom costs nothing). cycles_full is 0 too in that case.
|
-- default to 0 (atom costs nothing).
|
||||||
if cycles_min == math.huge then cycles_min = 0 end
|
if cycles_min == math.huge then cycles_min = 0 end
|
||||||
if cycles_max == -1 then cycles_max = 0 end
|
if cycles_max == -1 then cycles_max = 0 end
|
||||||
|
|
||||||
@@ -1187,16 +1179,13 @@ local function analyze_atom_paths(atom)
|
|||||||
for macro_name in pairs(unknown_set) do unknown_list[#unknown_list + 1] = macro_name end
|
for macro_name in pairs(unknown_set) do unknown_list[#unknown_list + 1] = macro_name end
|
||||||
table.sort(unknown_list)
|
table.sort(unknown_list)
|
||||||
|
|
||||||
-- branch_count: number of `branch_*(...)` tokens. (More useful than
|
-- branch_count: number of `branch_*(...)` tokens.
|
||||||
-- `#branches` which is the size of the targets map and would be equal
|
|
||||||
-- to #branches anyway, but the rename is clearer.)
|
|
||||||
local branch_count = 0
|
local branch_count = 0
|
||||||
for _ in pairs(branches) do branch_count = branch_count + 1 end
|
for _ in pairs(branches) do branch_count = branch_count + 1 end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cycles_min = cycles_min,
|
cycles_min = cycles_min,
|
||||||
cycles_max = cycles_max,
|
cycles_max = cycles_max,
|
||||||
cycles_full = cycles_full,
|
|
||||||
branches = branch_count,
|
branches = branch_count,
|
||||||
paths = path_count,
|
paths = path_count,
|
||||||
has_loops = has_loops,
|
has_loops = has_loops,
|
||||||
@@ -1204,32 +1193,22 @@ local function analyze_atom_paths(atom)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns total cycle count (the sum-of-all-tokens value, which over-counts
|
|
||||||
--- BD-slot nops) + unknown macro list. New code should call
|
|
||||||
--- `analyze_atom_paths(atom)` instead.
|
|
||||||
local function count_atom_cycles(atom)
|
|
||||||
local tokens = tokenize_body(atom.body)
|
|
||||||
local total = 0
|
|
||||||
local unknown_set = {}
|
|
||||||
for _, t in ipairs(tokens) do
|
|
||||||
local c, _, unknown = token_cycles(t.tok)
|
|
||||||
total = total + c
|
|
||||||
if unknown then
|
|
||||||
unknown_set[t.tok:match("^([%w_]+)") or "?"] = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local unknown_list = {}
|
|
||||||
for macro_name in pairs(unknown_set) do unknown_list[#unknown_list + 1] = macro_name end
|
|
||||||
return total, unknown_list
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Per-source check that emits one finding per unknown macro seen
|
--- Per-source check that emits one finding per unknown macro seen
|
||||||
--- (deduplicated across atoms so the warning section doesn't get
|
--- (deduplicated across atoms so the warning section doesn't get
|
||||||
--- spammed with N copies of "macro X not in duffle.INSTRUCTION_LATENCY").
|
--- spammed with N copies of "macro X not in duffle.INSTRUCTION_LATENCY").
|
||||||
local function check_per_atom_cycle_budget(atoms, findings)
|
local function check_per_atom_cycle_budget(atoms, findings)
|
||||||
local unknown_seen = {}
|
local unknown_seen = {}
|
||||||
for _, a in ipairs(atoms) do
|
for _, a in ipairs(atoms) do
|
||||||
local _, unknown_macros = count_atom_cycles(a)
|
local tokens = tokenize_body(a.body)
|
||||||
|
local unknown_set = {}
|
||||||
|
for _, t in ipairs(tokens) do
|
||||||
|
local _, _, unknown = token_cycles(t.tok)
|
||||||
|
if unknown then
|
||||||
|
unknown_set[t.tok:match("^([%w_]+)") or "?"] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local unknown_macros = {}
|
||||||
|
for macro_name in pairs(unknown_set) do unknown_macros[#unknown_macros + 1] = macro_name end
|
||||||
for _, name in ipairs(unknown_macros) do
|
for _, name in ipairs(unknown_macros) do
|
||||||
if not unknown_seen[name] then
|
if not unknown_seen[name] then
|
||||||
unknown_seen[name] = a.line
|
unknown_seen[name] = a.line
|
||||||
@@ -1269,22 +1248,15 @@ local function validate(ctx, src)
|
|||||||
check_gpu_portstore_shape(atoms, findings)
|
check_gpu_portstore_shape(atoms, findings)
|
||||||
check_per_atom_cycle_budget(atoms, findings)
|
check_per_atom_cycle_budget(atoms, findings)
|
||||||
|
|
||||||
-- Path-aware cycle-budget output: attach per-path cycle data to each
|
-- Path-aware cycle-budget output: attach per-path cycle data to each atom.
|
||||||
-- atom. Best-case (no-stall) cycle count with BD-slot absorbed; the
|
|
||||||
-- `cycles_full` field is the legacy sum-of-all-tokens value (kept
|
|
||||||
-- for backward compat; over-counts BD-slot nops).
|
|
||||||
local cycles_by_atom = {}
|
|
||||||
for _, a in ipairs(atoms) do
|
for _, a in ipairs(atoms) do
|
||||||
local p = analyze_atom_paths(a)
|
local p = analyze_atom_paths(a)
|
||||||
a.paths = p
|
a.paths = p
|
||||||
a.cycles = p.cycles_max -- default for any code that reads .cycles
|
|
||||||
a.cycles_min = p.cycles_min
|
a.cycles_min = p.cycles_min
|
||||||
a.cycles_max = p.cycles_max
|
a.cycles_max = p.cycles_max
|
||||||
a.cycles_full = p.cycles_full
|
|
||||||
a.branch_count = p.branches
|
a.branch_count = p.branches
|
||||||
a.unknown_macros = p.unknown_macros
|
a.unknown_macros = p.unknown_macros
|
||||||
a.has_loops = p.has_loops
|
a.has_loops = p.has_loops
|
||||||
cycles_by_atom[a.name] = p
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local errors = {}
|
local errors = {}
|
||||||
@@ -1347,87 +1319,9 @@ local function validate(ctx, src)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
-- Per-source output: build/gen/<basename>.static_analysis.txt
|
-- Per-directory output: build/gen/<dir_basename>.static_analysis.txt
|
||||||
-- ════════════════════════════════════════════════════════════════════════════
|
-- ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
local function emit_static_analysis_txt(ctx, src, result)
|
|
||||||
local out_path = ctx.out_root .. "/" .. src.basename .. ".static_analysis.txt"
|
|
||||||
if ctx.dry_run then return out_path end
|
|
||||||
duffle.ensure_dir(ctx.out_root)
|
|
||||||
|
|
||||||
local lines = {}
|
|
||||||
local function add(s) lines[#lines + 1] = s end
|
|
||||||
|
|
||||||
add("========================================================")
|
|
||||||
add("STATIC ANALYSIS PASS -- " .. src.path)
|
|
||||||
add("========================================================")
|
|
||||||
add("")
|
|
||||||
-- Tally atoms by kind for the header summary
|
|
||||||
local n_atoms, n_bare, n_proc = 0, 0, 0
|
|
||||||
for _, a in ipairs(result.atoms) do
|
|
||||||
n_atoms = n_atoms + 1
|
|
||||||
if a.kind == "comp_bare" then n_bare = n_bare + 1
|
|
||||||
elseif a.kind == "comp_proc" then n_proc = n_proc + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local header_atoms = string.format("Atoms: %d", n_atoms)
|
|
||||||
if n_bare > 0 or n_proc > 0 then
|
|
||||||
header_atoms = header_atoms .. string.format(" (atoms: %d, comp_bare: %d, comp_proc: %d)",
|
|
||||||
n_atoms - n_bare - n_proc, n_bare, n_proc)
|
|
||||||
end
|
|
||||||
add(string.format("%s Findings: %d Errors: %d Warnings: %d",
|
|
||||||
header_atoms, #result.findings, #result.errors, #result.warnings))
|
|
||||||
add("")
|
|
||||||
|
|
||||||
-- Group findings by atom for readability
|
|
||||||
local by_atom = {}
|
|
||||||
for _, f in ipairs(result.findings) do
|
|
||||||
by_atom[f.atom] = by_atom[f.atom] or {}
|
|
||||||
by_atom[f.atom][#by_atom[f.atom] + 1] = f
|
|
||||||
end
|
|
||||||
|
|
||||||
if next(by_atom) == nil then
|
|
||||||
add(" (no findings -- every atom passed all checks)")
|
|
||||||
else
|
|
||||||
add("── Findings by atom ─────────────────────────────────────")
|
|
||||||
for _, a in ipairs(result.atoms) do
|
|
||||||
local fs = by_atom[a.name]
|
|
||||||
if fs then
|
|
||||||
add(string.format(" %s line %d", a.name, a.line))
|
|
||||||
for _, f in ipairs(fs) do
|
|
||||||
add(string.format(" [%s] %s", f.check, f.msg))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
add("")
|
|
||||||
add("── Errors ──────────────────────────────────────────────")
|
|
||||||
if #result.errors == 0 then add(" (none)") end
|
|
||||||
for _, e in ipairs(result.errors) do
|
|
||||||
add(string.format(" X line %d %s", e.line, e.msg))
|
|
||||||
end
|
|
||||||
|
|
||||||
add("")
|
|
||||||
add("── Warnings ────────────────────────────────────────────")
|
|
||||||
if #result.warnings == 0 then add(" (none)") end
|
|
||||||
for _, w in ipairs(result.warnings) do
|
|
||||||
add(string.format(" ! line %d %s", w.line, w.msg))
|
|
||||||
end
|
|
||||||
|
|
||||||
add("")
|
|
||||||
add("── Info ────────────────────────────────────────────────")
|
|
||||||
for _, i_ in ipairs(result.info) do
|
|
||||||
add(string.format(" %s", i_.msg))
|
|
||||||
end
|
|
||||||
|
|
||||||
duffle.write_file(out_path, table.concat(lines, "\n") .. "\n")
|
|
||||||
return out_path
|
|
||||||
end
|
|
||||||
|
|
||||||
-- (Old per-source emit function above; kept for backward compat but no longer called from M.run.
|
|
||||||
-- Replaced by `emit_module_static_analysis_txt` which aggregates by directory.)
|
|
||||||
|
|
||||||
--- Per-directory emit. Aggregates atoms + findings across every source
|
--- Per-directory emit. Aggregates atoms + findings across every source
|
||||||
--- in `dir_sources` and writes a single report to
|
--- in `dir_sources` and writes a single report to
|
||||||
--- `<out_root>/<dir_basename>.static_analysis.txt`. Called only when
|
--- `<out_root>/<dir_basename>.static_analysis.txt`. Called only when
|
||||||
|
|||||||
@@ -573,7 +573,7 @@ end
|
|||||||
-- @param result PassResult
|
-- @param result PassResult
|
||||||
-- @return boolean
|
-- @return boolean
|
||||||
local function report_validation_errors(pass_name, pass, result)
|
local function report_validation_errors(pass_name, pass, result)
|
||||||
local has_errors = result.errors and #result.errors > 0
|
local has_errors = result.errors and #result.errors > 0
|
||||||
if not (has_errors and PASS_KIND_STOP_ON_ERROR[pass.kind]) then
|
if not (has_errors and PASS_KIND_STOP_ON_ERROR[pass.kind]) then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user