mirror of
https://github.com/pound-emu/ballistic.git
synced 2026-10-24 23:45:05 -07:00
Introduces parse_file_level_docs() which extracts file level documentation prefixed with //!.
144 lines
3.6 KiB
Lua
144 lines
3.6 KiB
Lua
local log = require("log")
|
|
|
|
local M = {}
|
|
|
|
local SECTIONS = {
|
|
"Safety", "Errors", "Examples", "Returns", "Warning", "Ownership", "Platform Support",
|
|
}
|
|
|
|
local section_set = {}
|
|
|
|
for _, section in ipairs(SECTIONS) do
|
|
section_set[section] = true
|
|
end
|
|
|
|
--- Strip comment prefixes (///, //!, /**, *, */) from raw comment text.
|
|
--- Returns clean lines as a table.
|
|
local function strip_comment_markers(raw)
|
|
if not raw or raw == "" then
|
|
return {}
|
|
end
|
|
|
|
local lines = {}
|
|
|
|
for line in raw:gmatch("[^\r\n]+") do
|
|
local cleaned = line
|
|
|
|
-- Strip leading whitespace
|
|
cleaned = cleaned:match("^%s*(.*)$") or ""
|
|
|
|
-- Strip /// or //!
|
|
if cleaned:sub(1, 3) == "///" or cleaned:sub(1, 3) == "//!" then
|
|
cleaned = cleaned:sub(4)
|
|
|
|
-- Strip /** or /*!
|
|
elseif cleaned:sub(1, 3) == "/**" or cleaned:sub(1, 3) == "/*!" then
|
|
cleaned = cleaned:sub(4)
|
|
|
|
-- Strip */
|
|
elseif cleaned:sub(1, 2) == "*/" then
|
|
goto continue
|
|
|
|
-- Strip leading *
|
|
elseif cleaned:sub(1, 1) == "*" then
|
|
cleaned = cleaned:sub(2)
|
|
end
|
|
|
|
-- Strip one leading space after marker
|
|
if cleaned:sub(1, 1) == " " then
|
|
cleaned = cleaned:sub(2)
|
|
end
|
|
|
|
lines[#lines + 1] = cleaned
|
|
|
|
::continue::
|
|
end
|
|
|
|
return lines
|
|
end
|
|
|
|
--- Extract [`SYMBOL`] links from text. Returns list of symbol names.
|
|
local function extract_links(text)
|
|
local links = {}
|
|
|
|
for symbol in text:gmatch("%[`([^`]+)`%]") do
|
|
table.insert(links, symbol)
|
|
end
|
|
|
|
return links
|
|
end
|
|
|
|
--- Parse a raw doc comment string into a structured doc table.
|
|
--- Returns:
|
|
--- {
|
|
--- summary = string, -- text before first # Section
|
|
--- sections = { -- ordered list of { name, content }
|
|
--- { name = "Safety", content = "..." },
|
|
--- },
|
|
--- links = { "SYM1", ... }, -- all [`SYM`] references found
|
|
--- raw = string, -- original text
|
|
--- }
|
|
function M.parse(raw)
|
|
if not raw or raw == "" then
|
|
return nil
|
|
end
|
|
|
|
log.trace("parsing comment (%d chars)", #raw)
|
|
|
|
local lines = strip_comment_markers(raw)
|
|
|
|
if #lines == 0 then
|
|
return nil
|
|
end
|
|
|
|
local doc = {
|
|
summary = "",
|
|
sections = {},
|
|
links = {},
|
|
raw = raw,
|
|
}
|
|
|
|
local current_section = nil
|
|
local summary_lines = {}
|
|
local section_lines = {}
|
|
|
|
local function flush_section()
|
|
if current_section then
|
|
local content = table.concat(section_lines, "\n")
|
|
doc.sections[#doc.sections + 1] = {
|
|
name = current_section,
|
|
content = content,
|
|
}
|
|
log.debug("doc: parsed section '# %s' (%d chars)", current_section, #content)
|
|
section_lines = {}
|
|
end
|
|
end
|
|
|
|
for _, line in ipairs(lines) do
|
|
-- Check for section header: "# Word" at start of line
|
|
local header = line:match("^#%s+(%w+)%s*$")
|
|
|
|
if header and section_set[header] then
|
|
flush_section()
|
|
current_section = header
|
|
elseif current_section then
|
|
section_lines[#section_lines + 1] = line
|
|
else
|
|
summary_lines[#summary_lines + 1] = line
|
|
end
|
|
|
|
-- Extract links from every line
|
|
local line_links = extract_links(line)
|
|
|
|
for _, sym in ipairs(line_links) do
|
|
doc.links[#doc.links + 1] = sym
|
|
end
|
|
end
|
|
|
|
flush_section()
|
|
doc.summary = table.concat(summary_lines, "\n"):match("^%s*(.-)%s*$") or ""
|
|
log.debug("doc: summary=%d chars, sections=%d, links=%d", #doc.summary, #doc.sections, #doc.links)
|
|
return doc
|
|
end
|
|
|
|
return M |