local outdir = arg[1] function string.trim(self) return self:gsub("^%s+", ""):gsub("%s+$", "") end local escapes = { ["\\"] = "\\", ["n"] = "\n", ["t"] = "\t", ["r"] = "\r" } local docs = {} local cdoc local cfunc local function parse_string(str, close, noesc) local s = "" local esc = false repeat local c = str.read() if not esc then if (c ~= "\\" or noesc) then s = s .. c else esc = true end else if (escapes[c]) then s = s .. escapes[c] else return nil, "invalid escape" end end until str.peek(#close) == close or str.peek(#close) == "" str.seek(#close) return s end local function parse_dec(str) local num = "" repeat num = num .. str.read() until not str.peek():match("%d") or str.peek() == "" return tonumber(num, 10) end local function parse_hex(str) if not str.peek():match("%x") then return nil, "malformed hex" end local num = "" repeat num = num .. str.read() until not str.peek():match("%x") or str.peek() == "" return tonumber(num, 16) end local ln = 0 local function parse_line(line) local args = {} local idx = 1 local str = { peek = function(amt) amt = amt or 1 return line:sub(idx, idx+amt-1) end, read = function(amt) amt = amt or 1 local data = line:sub(idx, idx+amt-1) idx = idx + amt return data end, seek = function(amt) amt = amt or 0 idx = idx + amt return idx end } local function lassert(a, b) return assert(a, "line #"..ln..": "..(b or "unknown error")) end while str.peek() ~= "" do if (str.peek() == "\"") then str.seek(1) args[#args+1] = lassert(parse_string(str, "\"", false)) elseif (str.peek(2) == "0x") then str.seek(2) args[#args+1] = lassert(parse_hex(str)) elseif (str.peek(1):match("%d")) then args[#args+1] = lassert(parse_dec(str)) elseif (str.peek() == "'") then str.seek(1) args[#args+1] = lassert(parse_string(str, "'", false)) elseif (str.peek() == " ") then --do nothing else args[#args+1] = lassert(parse_string(str, " ", true)) end end return args end local function is_comment(line) if (line:trim():sub(1, 2) == "--") then return true, line:sub(3) end end for line in io.stdin:lines() do ln = ln+1 local com, rline = is_comment(line) if (com) then if (rline:match("^%-@module")) then local args = parse_line(rline:sub(10)) if (cdoc) then if (cfunc) then cdoc.methods[#cdoc.methods+1] = cfunc cfunc = nil end docs[#docs+1] = cdoc end cdoc = { type = "module", methods = {}, name = args[2], mod = args[1] } elseif (rline:match("^%-@section")) then local args = parse_line(rline:sub(11)) if (cdoc) then if (cfunc) then cdoc.methods[#cdoc.methods+1] = cfunc cfunc = nil end docs[#docs+1] = cdoc end cdoc = { type = "generic", methods = {}, name = args[2], mod = args[1] } elseif (rline:match("^%-@page")) then if (cfunc) then cdoc.methods[#cdoc.methods+1] = cfunc end local args = parse_line(rline:sub(8)) cfunc = { type = "page", doc = {}, name = args[1], print_name = args[2] } elseif (rline:match("^%-@arg") and cfunc.type == "func") then local args = parse_line(rline:sub(7)) cfunc.args[#cfunc.args+1] = { name = args[1], type = args[2], doc = args[3] } elseif (rline:match("^%-@return") and cfunc.type == "func") then local args = parse_line(rline:sub(10)) cfunc.ret[#cfunc.ret+1] = { type = args[1], doc = args[2], } elseif (rline:match("^%-@doc")) then local args = parse_line(rline:sub(7)) cfunc.doc[#cfunc.doc+1] = { doc = args[1], } elseif (rline:match("^%-@see")) then local args = parse_line(rline:sub(7)) cfunc.doc[#cfunc.doc+1] = { doc = "See \27_S"..args[1].."\27_E" } elseif (rline:match("^%-@note")) then local args = parse_line(rline:sub(8)) cfunc.doc[#cfunc.doc+1] = { doc = "NOTE: "..args[1] } elseif (rline:match("^%-@desc")) then local args = parse_line(rline:sub(8)) cfunc.doc[#cfunc.doc+1] = { doc = args[1] } elseif (rline:match("^%-@vararg")) then cfunc.vararg = true elseif (rline:match("^%-@varret")) then cfunc.varret = true elseif (rline:match("^%-@func")) then if (cfunc) then cdoc.methods[#cdoc.methods+1] = cfunc end local args = parse_line(rline:sub(8)) cfunc = { type = "func", doc = {}, name = args[1], ret = {}, args = {} } end else io.stderr:write(line.."\n") end end if (cfunc) then cdoc.methods[#cdoc.methods+1] = cfunc end if (cdoc) then docs[#docs+1] = cdoc end for i=1, #docs do os.execute("mkdir -p \""..outdir.."/"..docs[i].mod:trim().."\"") for j=1, #docs[i].methods do local docc = "" local f = io.open(outdir.."/"..docs[i].mod:trim().."/"..docs[i].methods[j].name:trim()..".tdf", "wb") if (docs[i].methods[j].type == "func") then local argtext = "" local fun = docs[i].mod.."."..docs[i].methods[j].name.."(" if (#docs[i].methods[j].args > 0) then for k=1, #docs[i].methods[j].args do fun = fun .. docs[i].methods[j].args[k].name..":"..docs[i].methods[j].args[k].type .. ", " argtext = argtext .. docs[i].methods[j].args[k].name .. " - " .. docs[i].methods[j].args[k].doc .. "\n" end if (docs[i].methods[j].vararg) then fun = fun .. "..." else fun = fun:sub(1, #fun-2) end else argtext = "No arguments" end fun = fun .. ")" local rettext = "" if (#docs[i].methods[j].ret > 0) then fun = fun .. ":" for k=1, #docs[i].methods[j].ret do fun = fun .. docs[i].methods[j].ret[k].type .. ", " rettext = rettext .. docs[i].methods[j].ret[k].type .. " - " .. docs[i].methods[j].ret[k].doc .. "\n" end if (docs[i].methods[j].varret) then fun = fun .. "..." else fun = fun:sub(1, #fun-2) end else rettext = "No return." end docc = docc .. fun .. "\n\nArguments:\n"..argtext.."\n\nReturns:\n"..rettext.."\n\n" else docc = docc .. docs[i].methods[j].print_name .. "\n\n" end for k=1, #docs[i].methods[j].doc do docc = docc..docs[i].methods[j].doc[k].doc .. "\n" end f:write(docc) f:close() end end