mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-07-12 20:31:25 -07:00
e9f26f89b8
script running is slow need to fix.
241 lines
7.3 KiB
Lua
241 lines
7.3 KiB
Lua
--- audit_lua_nesting.lua — Walk Lua source files and flag any block
|
|
--- nesting deeper than 5 levels.
|
|
---
|
|
--- Usage:
|
|
--- luajit scripts/audit_lua_nesting.lua scripts/duffle.lua scripts/ps1_meta.lua
|
|
--- luajit scripts/audit_lua_nesting.lua scripts/passes/
|
|
---
|
|
--- Output: for each file, a list of {line, depth} entries where depth > 5.
|
|
--- Returns exit code 1 if any violations found, 0 if clean.
|
|
---
|
|
--- **Implementation**: a hand-rolled depth tracker that counts:
|
|
--- - `do`, `function`, `if`, `for`, `while`, `repeat` -> depth +1
|
|
--- - `end`, `until` -> depth -1
|
|
--- - `else`, `elseif` -> depth unchanged
|
|
---
|
|
--- **Caveats**: doesn't handle string/comment state (will miscount
|
|
--- braces inside strings). For our metaprogram files (no embedded
|
|
--- code generation), this is acceptable.
|
|
|
|
local M = {}
|
|
|
|
local BLOCK_OPEN = {
|
|
["do"] = true,
|
|
["function"] = true,
|
|
["if"] = true,
|
|
["for"] = true,
|
|
["while"] = true,
|
|
["repeat"] = true,
|
|
}
|
|
|
|
local function is_block_close(token)
|
|
return token == "end" or token == "until"
|
|
end
|
|
|
|
-- (internal) Walk one source file and return a list of
|
|
-- {line, depth, token} entries where depth > MAX_NESTING.
|
|
local function audit_file(path, max_nesting)
|
|
local f = io.open(path, "r")
|
|
if not f then
|
|
error("Cannot open " .. path)
|
|
end
|
|
local content = f:read("*a")
|
|
f:close()
|
|
|
|
local violations = {}
|
|
local depth = 0
|
|
local line = 1
|
|
local pos = 1
|
|
local len = #content
|
|
local token_start = 0
|
|
|
|
local function read_ident_at(p)
|
|
-- Lua ident: [a-zA-Z_][a-zA-Z0-9_]*
|
|
local start = p
|
|
if start > len then return nil end
|
|
local ch = content:sub(start, start)
|
|
if not (ch:match("[%a_]")) then return nil end
|
|
p = p + 1
|
|
while p <= len do
|
|
local c = content:sub(p, p)
|
|
if not (c:match("[%w_]")) then break end
|
|
p = p + 1
|
|
end
|
|
return content:sub(start, p - 1), p
|
|
end
|
|
|
|
local function skip_string_or_comment(p)
|
|
local ch = content:sub(p, p)
|
|
if ch == '"' or ch == "'" then
|
|
-- String literal: skip to matching end-quote.
|
|
p = p + 1
|
|
while p <= len do
|
|
local c = content:sub(p, p)
|
|
if c == "\\" then
|
|
p = p + 2
|
|
elseif c == ch then
|
|
p = p + 1
|
|
break
|
|
else
|
|
p = p + 1
|
|
end
|
|
end
|
|
return p
|
|
elseif ch == "-" and content:sub(p + 1, p + 1) == "-" then
|
|
-- Lua comment: -- to end of line.
|
|
p = p + 2
|
|
if content:sub(p, p + 1) == "[[" and content:sub(p + 2, p + 3) == "[" then
|
|
-- Long bracket comment [==[ ... ]==]
|
|
p = p + 2
|
|
local eq = ""
|
|
while content:sub(p, p) == "=" do
|
|
eq = eq .. "="
|
|
p = p + 1
|
|
end
|
|
local close_marker = "]" .. eq .. "]"
|
|
local close_pos = content:find(close_marker, p, true)
|
|
if close_pos then
|
|
p = close_pos + #close_marker
|
|
else
|
|
p = len + 1
|
|
end
|
|
else
|
|
while p <= len and content:sub(p, p) ~= "\n" do p = p + 1 end
|
|
end
|
|
return p
|
|
elseif ch == "[" and content:sub(p + 1, p + 1) == "[" then
|
|
-- Long bracket string: [==[ ... ]==]
|
|
p = p + 2
|
|
local eq = ""
|
|
while content:sub(p, p) == "=" do
|
|
eq = eq .. "="
|
|
p = p + 1
|
|
end
|
|
local close_marker = "]" .. eq .. "]"
|
|
local close_pos = content:find(close_marker, p, true)
|
|
if close_pos then
|
|
p = close_pos + #close_marker
|
|
else
|
|
p = len + 1
|
|
end
|
|
return p
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local token_count = 0
|
|
while pos <= len do
|
|
local ch = content:sub(pos, pos)
|
|
if ch == "\n" then line = line + 1 end
|
|
|
|
local skip_to = skip_string_or_comment(pos)
|
|
if skip_to then
|
|
for i = pos, skip_to - 1 do
|
|
if content:sub(i, i) == "\n" then line = line + 1 end
|
|
end
|
|
pos = skip_to
|
|
elseif ch:match("[%a_]") then
|
|
local tok, next_pos = read_ident_at(pos)
|
|
token_count = token_count + 1
|
|
if BLOCK_OPEN[tok] then
|
|
depth = depth + 1
|
|
if depth > max_nesting then
|
|
violations[#violations + 1] = {
|
|
line = line,
|
|
depth = depth,
|
|
token = tok,
|
|
}
|
|
end
|
|
elseif is_block_close(tok) then
|
|
depth = depth - 1
|
|
end
|
|
pos = next_pos
|
|
else
|
|
pos = pos + 1
|
|
end
|
|
end
|
|
|
|
return violations
|
|
end
|
|
|
|
--- Audit one file. Returns nil if clean, else a list of violations.
|
|
--- @param path string
|
|
--- @param max_nesting integer -- default 5
|
|
--- @return table|nil
|
|
function M.audit(path, max_nesting)
|
|
local violations = audit_file(path, max_nesting or 5)
|
|
if #violations == 0 then return nil end
|
|
return violations
|
|
end
|
|
|
|
-- Module CLI.
|
|
if arg and arg[1] then
|
|
local max_nesting = 5
|
|
local files = {}
|
|
for i = 1, #arg do
|
|
if arg[i] == "--max" and arg[i + 1] then
|
|
max_nesting = tonumber(arg[i + 1]) or 5
|
|
else
|
|
files[#files + 1] = arg[i]
|
|
end
|
|
end
|
|
|
|
-- Accept either a directory or a file path. Directory args are
|
|
-- expanded via `dir /b *.lua` (Windows) or `ls *.lua` (Unix).
|
|
local function is_dir(p)
|
|
-- Try opening it as a file; if that succeeds, it's not a dir.
|
|
local f = io.open(p, "r")
|
|
if f then f:close() return false end
|
|
return true
|
|
end
|
|
local function list_lua(dir)
|
|
local out = {}
|
|
local cmd
|
|
if package.config:sub(1, 1) == "\\" then
|
|
cmd = 'dir /b "' .. dir .. '\\*.lua" 2>nul'
|
|
else
|
|
cmd = 'ls -1 "' .. dir .. '"/*.lua 2>/dev/null'
|
|
end
|
|
local p = io.popen(cmd)
|
|
if p then
|
|
for line in p:lines() do
|
|
if line:match("%.lua$") then
|
|
out[#out + 1] = dir .. "/" .. line
|
|
end
|
|
end
|
|
p:close()
|
|
end
|
|
return out
|
|
end
|
|
|
|
local to_check = {}
|
|
for _, f in ipairs(files) do
|
|
if is_dir(f) then
|
|
for _, sub in ipairs(list_lua(f)) do to_check[#to_check + 1] = sub end
|
|
else
|
|
to_check[#to_check + 1] = f
|
|
end
|
|
end
|
|
|
|
local total_violations = 0
|
|
for _, f in ipairs(to_check) do
|
|
local v = M.audit(f, max_nesting)
|
|
if v then
|
|
io.write(string.format("\n%s\n", f))
|
|
for _, x in ipairs(v) do
|
|
io.write(string.format(" line %d: depth %d (after '%s')\n", x.line, x.depth, x.token))
|
|
end
|
|
total_violations = total_violations + #v
|
|
end
|
|
end
|
|
|
|
if total_violations == 0 then
|
|
io.write("OK: no files exceed max nesting of " .. max_nesting .. "\n")
|
|
os.exit(0)
|
|
else
|
|
io.write(string.format("\n%d nesting violation(s) found.\n", total_violations))
|
|
os.exit(1)
|
|
end
|
|
end
|
|
|
|
return M |