initial refreshed docgen implementation
This adds a new template engine as well as removes doctree and a bunch of other changes, but for now does not actually utilize the templates. More work will come in very soon, cleaning up the codebase, templating it and fixing the C# support.
This commit is contained in:
parent
57ca910dbc
commit
31ae703ece
|
@ -0,0 +1,539 @@
|
|||
local ffi = require("ffi")
|
||||
local eolian = require("eolian")
|
||||
local keyref = require("docgen.keyref")
|
||||
|
||||
local M = {}
|
||||
|
||||
local writer
|
||||
|
||||
local func_type_str = {
|
||||
[eolian.function_type.PROPERTY] = "property",
|
||||
[eolian.function_type.PROP_GET] = "property",
|
||||
[eolian.function_type.PROP_SET] = "property",
|
||||
[eolian.function_type.METHOD] = "method"
|
||||
}
|
||||
|
||||
M.tok_ref_resolve = function(tok, eos, root)
|
||||
local tp, d1, d2 = tok:ref_resolve(eos)
|
||||
local ret = {}
|
||||
for tokp in d1:name_get():gmatch("[^%.]+") do
|
||||
ret[#ret + 1] = tokp:lower()
|
||||
end
|
||||
if tp == eolian.object_type.FUNCTION then
|
||||
ret[#ret + 1] = func_type_str[d2:type_get()]
|
||||
ret[#ret + 1] = d2:name_get():lower()
|
||||
elseif tp == eolian.object_type.EVENT then
|
||||
ret[#ret + 1] = "event"
|
||||
ret[#ret + 1] = d2:name_get():lower()
|
||||
end
|
||||
if root ~= nil then
|
||||
ret[#ret + 1] = not not root
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
M.obj_matches_filter = function(obj)
|
||||
local ns = M.obj_nspaces_get(obj)
|
||||
if #ns and (ns[1] == "efl" or ns[1] == "eina") then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
M.classes_get_filtered = function(eos)
|
||||
local ret = {}
|
||||
for cl in eos:classes_get() do
|
||||
if M.obj_matches_filter(cl) then
|
||||
ret[#ret + 1] = cl
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
M.obj_id_get = function(obj)
|
||||
return tonumber(ffi.cast("uintptr_t", obj))
|
||||
end
|
||||
|
||||
M.obj_nspaces_get = function(obj, root)
|
||||
local tbl = obj:namespaces_get():to_array()
|
||||
for i = 1, #tbl do
|
||||
tbl[i] = tbl[i]:lower()
|
||||
end
|
||||
tbl[#tbl + 1] = obj:short_name_get():lower()
|
||||
if root ~= nil then
|
||||
tbl[#tbl + 1] = not not root
|
||||
end
|
||||
return tbl
|
||||
end
|
||||
|
||||
M.event_nspaces_get = function(obj, cl, root)
|
||||
local tbl = M.obj_nspaces_get(cl)
|
||||
tbl[#tbl + 1] = "event"
|
||||
tbl[#tbl + 1] = obj:name_get():lower():gsub(",", "_")
|
||||
if root ~= nil then
|
||||
tbl[#tbl + 1] = not not root
|
||||
end
|
||||
return tbl
|
||||
end
|
||||
|
||||
M.func_nspaces_get = function(obj, cl, root)
|
||||
local tbl = M.obj_nspaces_get(cl)
|
||||
tbl[#tbl + 1] = func_type_str[obj:type_get()]
|
||||
tbl[#tbl + 1] = obj:name_get():lower()
|
||||
if root ~= nil then
|
||||
tbl[#tbl + 1] = not not root
|
||||
end
|
||||
return tbl
|
||||
end
|
||||
|
||||
local gen_doc_refd = function(str, eos)
|
||||
if not writer then
|
||||
writer = require("docgen.writer")
|
||||
end
|
||||
if not str then
|
||||
return nil
|
||||
end
|
||||
local pars = eolian.documentation_string_split(str)
|
||||
for i = 1, #pars do
|
||||
pars[i] = writer.Buffer():write_par(pars[i], eos):finish()
|
||||
end
|
||||
return table.concat(pars, "\n\n")
|
||||
end
|
||||
|
||||
local add_since = function(str, since)
|
||||
if not writer then
|
||||
writer = require("docgen.writer")
|
||||
end
|
||||
if not since then
|
||||
return str
|
||||
end
|
||||
local buf = writer.Buffer()
|
||||
if not str then
|
||||
buf:write_i("Since " .. since)
|
||||
return buf:finish()
|
||||
end
|
||||
buf:write_raw(str)
|
||||
buf:write_nl(2)
|
||||
buf:write_i("Since " .. since)
|
||||
return buf:finish()
|
||||
end
|
||||
|
||||
M.doc_summary_get = function(obj)
|
||||
if not obj then
|
||||
return nil
|
||||
end
|
||||
return obj:summary_get()
|
||||
end
|
||||
|
||||
M.doc_description_get = function(obj)
|
||||
if not obj then
|
||||
return nil
|
||||
end
|
||||
return obj:description_get()
|
||||
end
|
||||
|
||||
M.doc_since_get = function(obj)
|
||||
if not obj then
|
||||
return nil
|
||||
end
|
||||
return obj:since_get()
|
||||
end
|
||||
|
||||
M.doc_brief_get = function(eos, obj, obj2)
|
||||
if not obj and not obj2 then
|
||||
return "No description supplied."
|
||||
end
|
||||
if not obj then
|
||||
obj = obj2
|
||||
end
|
||||
return gen_doc_refd(obj:summary_get(), eos)
|
||||
end
|
||||
|
||||
M.doc_full_get = function(eos, obj, obj2, write_since)
|
||||
if not obj and not obj2 then
|
||||
return "No description supplied."
|
||||
end
|
||||
if not obj then
|
||||
obj, obj2 = obj2, obj
|
||||
end
|
||||
local sum1 = obj:summary_get()
|
||||
local desc1 = obj:description_get()
|
||||
local edoc = ""
|
||||
local since
|
||||
if obj2 then
|
||||
local sum2 = obj2:summary_get()
|
||||
local desc2 = obj2:descirption_get()
|
||||
if not desc2 then
|
||||
if sum2 then
|
||||
edoc = "\n\n" .. sum2
|
||||
end
|
||||
else
|
||||
edoc = "\n\n" .. sum2 .. "\n\n" .. desc2
|
||||
end
|
||||
if write_since then
|
||||
since = obj2:since_get()
|
||||
end
|
||||
end
|
||||
if not since and write_since then
|
||||
since = obj:since_get()
|
||||
end
|
||||
if not desc1 then
|
||||
return add_since(gen_doc_refd(sum1 .. edoc, eos), since)
|
||||
end
|
||||
return add_since(gen_doc_refd(sum1 .. "\n\n" .. desc1 .. edoc, eos), since)
|
||||
end
|
||||
|
||||
local class_type_str = {
|
||||
[eolian.class_type.REGULAR] = "class",
|
||||
[eolian.class_type.ABSTRACT] = "class",
|
||||
[eolian.class_type.MIXIN] = "mixin",
|
||||
[eolian.class_type.INTERFACE] = "interface"
|
||||
}
|
||||
|
||||
M.class_type_str_get = function(cl)
|
||||
return class_type_str[cl:type_get()]
|
||||
end
|
||||
|
||||
local param_dirs = {
|
||||
[eolian.parameter_dir.IN] = "in",
|
||||
[eolian.parameter_dir.OUT] = "out",
|
||||
[eolian.parameter_dir.INOUT] = "inout"
|
||||
}
|
||||
|
||||
M.param_get_dir_name = function(param)
|
||||
return assert(param_dirs[param:direction_get()], "unknown parameter direction")
|
||||
end
|
||||
|
||||
M.type_cstr_get = function(tp, suffix)
|
||||
tp = tp or "void"
|
||||
local ct = (type(tp) == "string") and tp or tp:c_type_get(eolian.c_type_type.DEFAULT)
|
||||
if not suffix then
|
||||
return ct
|
||||
end
|
||||
if ct:sub(#ct) == "*" then
|
||||
return ct .. suffix
|
||||
else
|
||||
return ct .. " " .. suffix
|
||||
end
|
||||
end
|
||||
|
||||
local serialize_type
|
||||
local serialize_tdecl
|
||||
local serialize_tdecl_c
|
||||
local serialize_var
|
||||
local serialize_var_c
|
||||
|
||||
local wrap_type_attrs = function(tp, str)
|
||||
if tp:is_const() then
|
||||
str = "const(" .. str .. ")"
|
||||
end
|
||||
-- TODO: implement new ownership system into docs
|
||||
--if tp:is_own() then
|
||||
-- str = "own(" .. str .. ")"
|
||||
--end
|
||||
local ffunc = tp:free_func_get()
|
||||
if ffunc then
|
||||
str = "free(" .. str .. ", " .. ffunc .. ")"
|
||||
end
|
||||
if tp:is_ptr() then
|
||||
str = "ptr(" .. str .. ")"
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
serialize_type = function(tp)
|
||||
local tpt = tp:type_get()
|
||||
if tpt == eolian.type_type.UNKNOWN then
|
||||
error("unknown type: " .. tp:name_get())
|
||||
elseif tpt == eolian.type_type.VOID then
|
||||
return wrap_type_attrs(tp, "void")
|
||||
elseif tpt == eolian.type_type.UNDEFINED then
|
||||
return wrap_type_attrs(tp, "__undefined_type")
|
||||
elseif tpt == eolian.type_type.REGULAR or tpt == eolian.type_type.CLASS then
|
||||
local stp = tp:base_type_get()
|
||||
if stp then
|
||||
local stypes = {}
|
||||
while stp do
|
||||
stypes[#stypes + 1] = serialize_type(stp)
|
||||
stp = stp:next_type_get()
|
||||
end
|
||||
return wrap_type_attrs(tp, tp:name_get() .. "<"
|
||||
.. table.concat(stypes, ", ") .. ">")
|
||||
end
|
||||
return wrap_type_attrs(tp, tp:name_get())
|
||||
end
|
||||
error("unhandled type type: " .. tpt)
|
||||
end
|
||||
|
||||
local add_typedecl_attrs = function(tp, buf)
|
||||
if tp:is_extern() then
|
||||
buf[#buf + 1] = "@extern "
|
||||
end
|
||||
local ffunc = tp:free_func_get()
|
||||
if ffunc then
|
||||
buf[#buf + 1] = "@free("
|
||||
buf[#buf + 1] = ffunc
|
||||
buf[#buf + 1] = ") "
|
||||
end
|
||||
end
|
||||
|
||||
serialize_tdecl = function(tp)
|
||||
local tpt = tp:type_get()
|
||||
if tpt == eolian.typedecl_type.UNKNOWN then
|
||||
error("unknown typedecl: " .. tp:name_get())
|
||||
elseif tpt == eolian.typedecl_type.STRUCT or
|
||||
tpt == eolian.typedecl_type.STRUCT_OPAQUE then
|
||||
local buf = { "struct " }
|
||||
add_typedecl_attrs(tp, buf)
|
||||
buf[#buf + 1] = tp:name_get()
|
||||
if tpt == eolian.typedecl_type.STRUCT_OPAQUE then
|
||||
buf[#buf + 1] = ";"
|
||||
return table.concat(buf)
|
||||
end
|
||||
local fields = tp:struct_fields_get():to_array()
|
||||
if #fields == 0 then
|
||||
buf[#buf + 1] = " {}"
|
||||
return table.concat(buf)
|
||||
end
|
||||
buf[#buf + 1] = " {\n"
|
||||
for i, fld in ipairs(fields) do
|
||||
buf[#buf + 1] = " "
|
||||
buf[#buf + 1] = fld:name_get()
|
||||
buf[#buf + 1] = ": "
|
||||
buf[#buf + 1] = serialize_type(fld:type_get())
|
||||
buf[#buf + 1] = ";\n"
|
||||
end
|
||||
buf[#buf + 1] = "}"
|
||||
return table.concat(buf)
|
||||
elseif tpt == eolian.typedecl_type.ENUM then
|
||||
local buf = { "enum " }
|
||||
add_typedecl_attrs(tp, buf)
|
||||
buf[#buf + 1] = tp:name_get()
|
||||
local fields = tp:enum_fields_get():to_array()
|
||||
if #fields == 0 then
|
||||
buf[#buf + 1] = " {}"
|
||||
return table.concat(buf)
|
||||
end
|
||||
buf[#buf + 1] = " {\n"
|
||||
for i, fld in ipairs(fields) do
|
||||
buf[#buf + 1] = " "
|
||||
buf[#buf + 1] = fld:name_get()
|
||||
local val = fld:value_get()
|
||||
if val then
|
||||
buf[#buf + 1] = ": "
|
||||
buf[#buf + 1] = val:serialize()
|
||||
end
|
||||
if i == #fields then
|
||||
buf[#buf + 1] = "\n"
|
||||
else
|
||||
buf[#buf + 1] = ",\n"
|
||||
end
|
||||
end
|
||||
buf[#buf + 1] = "}"
|
||||
return table.concat(buf)
|
||||
elseif tpt == eolian.typedecl_type.ALIAS then
|
||||
local buf = { "type " }
|
||||
add_typedecl_attrs(tp, buf)
|
||||
buf[#buf + 1] = tp:name_get()
|
||||
buf[#buf + 1] = ": "
|
||||
buf[#buf + 1] = serialize_type(tp:base_type_get())
|
||||
buf[#buf + 1] = ";"
|
||||
return table.concat(buf)
|
||||
elseif tpt == eolian.typedecl_type.FUNCTION_POINTER then
|
||||
return "TODO"
|
||||
end
|
||||
error("unhandled typedecl type: " .. tpt)
|
||||
end
|
||||
|
||||
serialize_tdecl_c = function(tp, ns)
|
||||
local tpt = tp:type_get()
|
||||
if tpt == eolian.typedecl_type.UNKNOWN then
|
||||
error("unknown typedecl: " .. tp:name_get())
|
||||
elseif tpt == eolian.typedecl_type.STRUCT or
|
||||
tpt == eolian.typedecl_type.STRUCT_OPAQUE then
|
||||
local buf = { "typedef struct " }
|
||||
local fulln = tp:name_get():gsub("%.", "_");
|
||||
keyref.add(fulln, ns, "c")
|
||||
buf[#buf + 1] = "_" .. fulln;
|
||||
if tpt == eolian.typedecl_type.STRUCT_OPAQUE then
|
||||
buf[#buf + 1] = " " .. fulln .. ";"
|
||||
return table.concat(buf)
|
||||
end
|
||||
local fields = tp:struct_fields_get():to_array()
|
||||
if #fields == 0 then
|
||||
buf[#buf + 1] = " {} " .. fulln .. ";"
|
||||
return table.concat(buf)
|
||||
end
|
||||
buf[#buf + 1] = " {\n"
|
||||
for i, fld in ipairs(fields) do
|
||||
buf[#buf + 1] = " "
|
||||
buf[#buf + 1] = M.type_cstr_get(fld:type_get(), fld:name_get())
|
||||
buf[#buf + 1] = ";\n"
|
||||
end
|
||||
buf[#buf + 1] = "} " .. fulln .. ";"
|
||||
return table.concat(buf)
|
||||
elseif tpt == eolian.typedecl_type.ENUM then
|
||||
local buf = { "typedef enum" }
|
||||
local fulln = tp:name_get():gsub("%.", "_");
|
||||
keyref.add(fulln, ns, "c")
|
||||
local fields = tp:enum_fields_get():to_array()
|
||||
if #fields == 0 then
|
||||
buf[#buf + 1] = " {} " .. fulln .. ";"
|
||||
return table.concat(buf)
|
||||
end
|
||||
buf[#buf + 1] = " {\n"
|
||||
for i, fld in ipairs(fields) do
|
||||
buf[#buf + 1] = " "
|
||||
local cn = fld:c_name_get()
|
||||
buf[#buf + 1] = cn
|
||||
keyref.add(cn, ns, "c")
|
||||
local val = fld:value_get()
|
||||
if val then
|
||||
buf[#buf + 1] = " = "
|
||||
local ev = val:eval(eolian.expression_mask.INT)
|
||||
local lit = ev:to_literal()
|
||||
buf[#buf + 1] = lit
|
||||
local ser = val:serialize()
|
||||
if ser and ser ~= lit then
|
||||
buf[#buf + 1] = " /* " .. ser .. " */"
|
||||
end
|
||||
end
|
||||
if i == #fields then
|
||||
buf[#buf + 1] = "\n"
|
||||
else
|
||||
buf[#buf + 1] = ",\n"
|
||||
end
|
||||
end
|
||||
buf[#buf + 1] = "} " .. fulln .. ";"
|
||||
return table.concat(buf)
|
||||
elseif tpt == eolian.typedecl_type.ALIAS then
|
||||
local fulln = tp:name_get():gsub("%.", "_");
|
||||
keyref.add(fulln, ns, "c")
|
||||
return "typedef "
|
||||
.. M.type_cstr_get(tp:base_type_get(), fulln) .. ";"
|
||||
elseif tpt == eolian.typedecl_type.FUNCTION_POINTER then
|
||||
return "TODO"
|
||||
end
|
||||
error("unhandled typedecl type: " .. tpt)
|
||||
end
|
||||
|
||||
serialize_var = function(var)
|
||||
local buf = {}
|
||||
if var:type_get() == eolian.variable_type.GLOBAL then
|
||||
buf[#buf + 1] = "var "
|
||||
else
|
||||
buf[#buf + 1] = "const "
|
||||
end
|
||||
if var:is_extern() then
|
||||
buf[#buf + 1] = "@extern "
|
||||
end
|
||||
buf[#buf + 1] = var:name_get()
|
||||
buf[#buf + 1] = ": "
|
||||
buf[#buf + 1] = serialize_type(var:base_type_get())
|
||||
local val = var:value_get()
|
||||
if val then
|
||||
buf[#buf + 1] = " = "
|
||||
buf[#buf + 1] = val:serialize()
|
||||
end
|
||||
buf[#buf + 1] = ";"
|
||||
return table.concat(buf)
|
||||
end
|
||||
|
||||
serialize_var_c = function(var, ns)
|
||||
local buf = {}
|
||||
local bt = var:base_type_get()
|
||||
local fulln = var:name_get():gsub("%.", "_"):upper()
|
||||
keyref.add(fulln, ns, "c")
|
||||
if var:type_get() == eolian.variable_type.GLOBAL then
|
||||
local ts = bt:c_type_get(eolian.c_type_type.DEFAULT)
|
||||
buf[#buf + 1] = ts
|
||||
if ts:sub(#ts) ~= "*" then
|
||||
buf[#buf + 1] = " "
|
||||
end
|
||||
buf[#buf + 1] = fulln
|
||||
local val = var:value_get()
|
||||
if val then
|
||||
buf[#buf + 1] = " = "
|
||||
local vt = val:eval_type(bt)
|
||||
local lv = vt:to_literal()
|
||||
local sv = val:serialize()
|
||||
buf[#buf + 1] = lv
|
||||
if lv ~= sv then
|
||||
buf[#buf + 1] = "/* " .. sv .. " */"
|
||||
end
|
||||
end
|
||||
buf[#buf + 1] = ";"
|
||||
else
|
||||
buf[#buf + 1] = "#define "
|
||||
buf[#buf + 1] = fulln
|
||||
buf[#buf + 1] = " "
|
||||
local val = var:value_get()
|
||||
local vt = val:eval_type(bt)
|
||||
local lv = vt:to_literal()
|
||||
local sv = val:serialize()
|
||||
buf[#buf + 1] = lv
|
||||
if lv ~= sv then
|
||||
buf[#buf + 1] = "/* " .. sv .. " */"
|
||||
end
|
||||
end
|
||||
return table.concat(buf)
|
||||
end
|
||||
|
||||
local sert = {
|
||||
[eolian.object_type.TYPE] = serialize_type,
|
||||
[eolian.object_type.TYPEDECL] = serialize_tdecl,
|
||||
[eolian.object_type.VARIABLE] = serialize_var
|
||||
}
|
||||
|
||||
M.obj_serialize = function(obj)
|
||||
local f = sert[obj:object_get():type_get()]
|
||||
assert(f, "cannot serialize object")
|
||||
return f(obj)
|
||||
end
|
||||
|
||||
local sertc = {
|
||||
[eolian.object_type.TYPEDECL] = serialize_tdecl_c,
|
||||
[eolian.object_type.VARIABLE] = serialize_var_c
|
||||
}
|
||||
|
||||
M.obj_serialize_c = function(obj, ns)
|
||||
local f = sertc[obj:object_get():type_get()]
|
||||
assert(f, "cannot serialize object")
|
||||
return f(obj, ns)
|
||||
end
|
||||
|
||||
M.impl_is_overridden = function(obj, cl)
|
||||
return obj:class_get() ~= cl
|
||||
end
|
||||
|
||||
M.impl_fallback_doc_get = function(obj)
|
||||
local ig, is = obj:is_prop_get(), obj:is_prop_set()
|
||||
if ig and not is then
|
||||
return obj:documentation_get(eolian.function_type.PROP_GET)
|
||||
elseif is and not ig then
|
||||
return obj:documentation_get(eolian.function_type.PROP_SET)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local revh = {}
|
||||
|
||||
M.class_children_get = function(cl)
|
||||
return revh[cl:name_get()] or {}
|
||||
end
|
||||
|
||||
M.build_class_children = function(eos)
|
||||
for cl in eos:classes_get() do
|
||||
for icl in cl:inherits_get() do
|
||||
local icln = icl:name_get()
|
||||
local t = revh[icln]
|
||||
if not t then
|
||||
t = {}
|
||||
revh[icln] = t
|
||||
end
|
||||
t[#t + 1] = cl
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
|
@ -0,0 +1,41 @@
|
|||
local M = {}
|
||||
|
||||
local key_refs = {}
|
||||
|
||||
M.add = function(key, link, lang)
|
||||
local rfs = key_refs[lang]
|
||||
if not rfs then
|
||||
key_refs[lang] = {}
|
||||
rfs = key_refs[lang]
|
||||
end
|
||||
rfs[key] = link
|
||||
end
|
||||
|
||||
M.build = function()
|
||||
local writer = require("docgen.writer")
|
||||
local dutil = require("docgen.util")
|
||||
for lang, rfs in pairs(key_refs) do
|
||||
local f = writer.Writer({ "ref", lang, "keyword-list" })
|
||||
local arr = {}
|
||||
for refn, v in pairs(rfs) do
|
||||
arr[#arr + 1] = refn
|
||||
local rf = writer.Writer({ "ref", lang, "key", refn })
|
||||
v[#v + 1] = true
|
||||
rf:write_include(rf.INCLUDE_PAGE, v)
|
||||
rf:finish()
|
||||
end
|
||||
table.sort(arr)
|
||||
f:write_raw(table.concat(arr, "\n"))
|
||||
f:write_nl()
|
||||
f:finish()
|
||||
local lf = writer.Writer({ "ref", lang, "keyword-link" })
|
||||
lf:write_raw("/", dutil.path_join(
|
||||
dutil.nspace_to_path(dutil.get_root_ns()),
|
||||
"ref", lang, "key", "{FNAME}"
|
||||
))
|
||||
lf:write_nl()
|
||||
lf:finish()
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
|
@ -0,0 +1,621 @@
|
|||
|
||||
local writer = require("docgen.writer")
|
||||
|
||||
local eolian = require("eolian")
|
||||
local eoutils = require("docgen.eolian_utils")
|
||||
|
||||
local eos
|
||||
|
||||
local M = {}
|
||||
|
||||
local propt_to_type = {
|
||||
[eolian.function_type.PROPERTY] = "(get, set)",
|
||||
[eolian.function_type.PROP_GET] = "(get)",
|
||||
[eolian.function_type.PROP_SET] = "(set)",
|
||||
}
|
||||
|
||||
local verbs = {
|
||||
"add",
|
||||
"get",
|
||||
"is",
|
||||
"del",
|
||||
"thaw",
|
||||
"freeze",
|
||||
"save",
|
||||
"wait",
|
||||
"eject",
|
||||
"raise",
|
||||
"lower",
|
||||
"load",
|
||||
"dup",
|
||||
"reset",
|
||||
"unload",
|
||||
"close",
|
||||
"set",
|
||||
"interpolate",
|
||||
"has",
|
||||
"grab",
|
||||
"check",
|
||||
"find",
|
||||
"ungrab",
|
||||
"unset",
|
||||
"clear",
|
||||
"pop",
|
||||
"new",
|
||||
"peek",
|
||||
"push",
|
||||
"update",
|
||||
"show",
|
||||
"move",
|
||||
"hide",
|
||||
"calculate",
|
||||
"resize",
|
||||
"attach",
|
||||
"pack",
|
||||
"unpack",
|
||||
"emit",
|
||||
"call",
|
||||
"append"
|
||||
}
|
||||
|
||||
local not_verbs = {
|
||||
"below",
|
||||
"above",
|
||||
"name",
|
||||
"unfreezable",
|
||||
"value",
|
||||
"r",
|
||||
"g",
|
||||
"b",
|
||||
"a",
|
||||
"finalize",
|
||||
"destructor",
|
||||
"to",
|
||||
"circle",
|
||||
"rect",
|
||||
"path",
|
||||
"commands",
|
||||
"type",
|
||||
"colorspace",
|
||||
"op",
|
||||
"type",
|
||||
"properties",
|
||||
"status",
|
||||
"status",
|
||||
"relative",
|
||||
"ptr",
|
||||
"pair",
|
||||
"pos",
|
||||
"end"
|
||||
}
|
||||
|
||||
local get_class_name = function(cls)
|
||||
local words = {}
|
||||
local klass = cls:name_get()
|
||||
for word in string.gmatch(klass, "%a+") do
|
||||
words[#words+1] = word
|
||||
end
|
||||
for i = 1, #words -1 do
|
||||
words[i] = string.lower(words[i])
|
||||
end
|
||||
return table.concat(words, '.')
|
||||
end
|
||||
|
||||
local get_mono_type
|
||||
get_mono_type = function(tp)
|
||||
if not tp then
|
||||
return "void "
|
||||
end
|
||||
|
||||
tpt = tp:type_get()
|
||||
tpdecl = tp:typedecl_get()
|
||||
|
||||
if tpt == eolian.type_type.REGULAR then
|
||||
if tp:name_get() == "string" then
|
||||
return "System.String"
|
||||
elseif tp:name_get() == "list" then
|
||||
ntp = tp:base_type_get()
|
||||
--assert(btp ~= nil)
|
||||
--ntp = btp:next_type_get()
|
||||
return "eina.List<" .. get_mono_type(ntp) .. ">"
|
||||
elseif tpdecl then
|
||||
--print("typedecl type is ", tp:name_get())
|
||||
tpt = tpdecl:type_get()
|
||||
return get_class_name(tp) --tp:name_get()
|
||||
else
|
||||
--print("regular type is ", tp:name_get())
|
||||
return tp:name_get()
|
||||
end
|
||||
elseif tpt == eolian.type_type.CLASS then
|
||||
return get_class_name(tp)
|
||||
else
|
||||
return "unknown"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local is_verb = function(word)
|
||||
for i = 1, #verbs do
|
||||
if verbs[i] == word then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local mono_method_name_get = function(f, ftype)
|
||||
local cn = f:name_get(ftype)
|
||||
|
||||
local words = {}
|
||||
|
||||
for word in string.gmatch(cn, "%a+") do
|
||||
words[#words+1] = word
|
||||
end
|
||||
|
||||
if #words > 1 and is_verb(words[#words]) then
|
||||
local tmp = words[#words]
|
||||
words[#words] = words[1]
|
||||
words[1] = tmp
|
||||
end
|
||||
|
||||
for i = 1, #words do
|
||||
words[i] = words[i]:gsub("^%l", string.upper)
|
||||
end
|
||||
|
||||
if ftype == eolian.function_type.PROP_GET then
|
||||
table.insert(words, 1, "Get")
|
||||
elseif ftype == eolian.function_type.PROP_SET then
|
||||
table.insert(words, 1, "Set")
|
||||
end
|
||||
|
||||
return table.concat(words)
|
||||
end
|
||||
|
||||
local gen_mono_param = function(par, out)
|
||||
local part = par:type_get()
|
||||
out = out or (par:direction_get() == eolian.parameter_dir.OUT)
|
||||
if out then
|
||||
out = "out "
|
||||
else
|
||||
out = ""
|
||||
end
|
||||
|
||||
return out .. get_mono_type(par:type_get()) .. ' ' .. par:name_get()
|
||||
--local tstr = part:c_type_get()
|
||||
--return out .. eoutils.type_cstr_get(tstr, par:name_get())
|
||||
end
|
||||
|
||||
local get_func_mono_sig_part = function(cn, tp)
|
||||
return get_mono_type(tp) .. " " .. cn
|
||||
end
|
||||
|
||||
local find_parent_impl
|
||||
find_parent_impl = function(fulln, cl)
|
||||
for pcl in cl:inherits_get() do
|
||||
for impl in pcl:implements_get() do
|
||||
if impl:name_get() == fulln then
|
||||
--if get_class_name(impl) == fulln then
|
||||
return impl, pcl
|
||||
end
|
||||
end
|
||||
local pimpl, pcl = find_parent_impl(fulln, pcl)
|
||||
if pimpl then
|
||||
return pimpl, pcl
|
||||
end
|
||||
end
|
||||
return nil, cl
|
||||
end
|
||||
|
||||
local find_parent_briefdoc
|
||||
find_parent_briefdoc = function(fulln, cl)
|
||||
local pimpl, pcl = find_parent_impl(fulln, cl)
|
||||
if not pimpl then
|
||||
return eoutils.doc_brief_get(eos)
|
||||
end
|
||||
local pdoc = pimpl:documentation_get(eolian.function_type.METHOD)
|
||||
local pdocf = eoutils.impl_fallback_doc_get(pimpl)
|
||||
if not pdoc and not pdocf then
|
||||
return find_parent_briefdoc(fulln, pcl)
|
||||
end
|
||||
return eoutils.doc_brief_get(eos, pdocf)
|
||||
end
|
||||
|
||||
|
||||
local write_description = function(f, impl, func, cl)
|
||||
local over = eoutils.impl_is_overridden(impl, cl)
|
||||
local bdoc
|
||||
|
||||
local doc = impl:documentation_get(eolian.function_type.METHOD)
|
||||
local docf = eoutils.impl_fallback_doc_get(impl)
|
||||
if over and not doc and not docf then
|
||||
bdoc = find_parent_briefdoc(impl:name_get(), cl)
|
||||
else
|
||||
bdoc = eoutils.doc_brief_get(eos, docf)
|
||||
end
|
||||
if bdoc ~= "No description supplied." then
|
||||
f:write_raw(bdoc)
|
||||
end
|
||||
end
|
||||
|
||||
local write_scope = function(f, func)
|
||||
local ftt = {
|
||||
[eolian.object_scope.PROTECTED] = "protected",
|
||||
[eolian.object_scope.PRIVATE] = "private"
|
||||
}
|
||||
if func:is_class() then
|
||||
f:write_raw(" ")
|
||||
f:write_m("class")
|
||||
end
|
||||
if func:type_get() == eolian.function_type.PROPERTY then
|
||||
local ft1, ft2 = ftt[func:scope_get(eolian.function_type.PROP_GET)],
|
||||
ftt[func:scope_get(eolian.function_type.PROP_SET)]
|
||||
if ft1 and ft1 == ft2 then
|
||||
f:write_raw(" ")
|
||||
f:write_m(ft1)
|
||||
elseif ft1 or ft2 then
|
||||
local s = ""
|
||||
if ft1 then
|
||||
s = s .. ft1 .. " get" .. (ft2 and ", " or "")
|
||||
end
|
||||
if ft2 then
|
||||
s = s .. ft2 .. " set"
|
||||
end
|
||||
f:write_raw(" ")
|
||||
f:write_m(s)
|
||||
end
|
||||
else
|
||||
local ft = ftt[func:scope_get(func:type_get())]
|
||||
if ft then
|
||||
f:write_raw(" ")
|
||||
f:write_m(ft)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local write_function = function(f, func, cl)
|
||||
local llbuf = writer.Buffer()
|
||||
llbuf:write_link(eoutils.func_nspaces_get(func, cl, true), func:name_get())
|
||||
f:write_b(llbuf:finish())
|
||||
|
||||
local pt = propt_to_type[func:type_get()]
|
||||
if pt then
|
||||
f:write_raw(" ")
|
||||
local llbuf = writer.Buffer()
|
||||
llbuf:write_b(pt)
|
||||
f:write_i(llbuf:finish())
|
||||
end
|
||||
end
|
||||
|
||||
local gen_func_mono_sig = function(f, ftype)
|
||||
ftype = ftype or eolian.function_type.METHOD
|
||||
assert(ftype ~= eolian.function_type.PROPERTY)
|
||||
|
||||
local cn = mono_method_name_get(f, ftype)
|
||||
local rtype = f:return_type_get(ftype)
|
||||
local prefix = ""
|
||||
local suffix = ""
|
||||
|
||||
if f:is_class() then
|
||||
prefix = "static "
|
||||
elseif f:is_const() or ftype == eolian.function_type.PROP_GET then
|
||||
suffix = " const"
|
||||
end
|
||||
|
||||
if f:type_get() == eolian.function_type.METHOD then
|
||||
local pars = f:parameters_get():to_array()
|
||||
local cnrt = get_func_mono_sig_part(cn, rtype)
|
||||
for i = 1, #pars do
|
||||
pars[i] = gen_mono_param(pars[i])
|
||||
end
|
||||
return prefix .. cnrt .. "(" .. table.concat(pars, ", ") .. ")" .. suffix .. ";"
|
||||
end
|
||||
|
||||
local keys = f:property_keys_get(ftype):to_array()
|
||||
local vals = f:property_values_get(ftype):to_array()
|
||||
|
||||
if ftype == eolian.function_type.PROP_SET then
|
||||
local cnrt = get_func_mono_sig_part(cn, rtype)
|
||||
local pars = {}
|
||||
for i, par in ipairs(keys) do
|
||||
pars[#pars + 1] = gen_mono_param(par)
|
||||
end
|
||||
for i, par in ipairs(vals) do
|
||||
pars[#pars + 1] = gen_mono_param(par)
|
||||
end
|
||||
return cnrt .. "(" .. table.concat(pars, ", ") .. ");"
|
||||
end
|
||||
|
||||
-- getters
|
||||
local cnrt
|
||||
if not rtype then
|
||||
if #vals == 1 then
|
||||
cnrt = get_func_mono_sig_part(cn, vals[1]:type_get())
|
||||
table.remove(vals, 1)
|
||||
else
|
||||
cnrt = get_func_mono_sig_part(cn)
|
||||
end
|
||||
else
|
||||
cnrt = get_func_mono_sig_part(cn, rtype)
|
||||
end
|
||||
local pars = {}
|
||||
for i, par in ipairs(keys) do
|
||||
pars[#pars + 1] = gen_mono_param(par)
|
||||
end
|
||||
for i, par in ipairs(vals) do
|
||||
--print('parameter is value for get, so out')
|
||||
pars[#pars + 1] = gen_mono_param(par, true)
|
||||
end
|
||||
|
||||
return cnrt .. "(" .. table.concat(pars, ", ") .. ");"
|
||||
end
|
||||
|
||||
local build_functable = function(f, tcl, tbl)
|
||||
if #tbl == 0 then
|
||||
return
|
||||
end
|
||||
local nt = {}
|
||||
for i, implt in ipairs(tbl) do
|
||||
local lbuf = writer.Buffer()
|
||||
|
||||
local cl, impl = unpack(implt)
|
||||
local func = impl:function_get()
|
||||
|
||||
local wt = {}
|
||||
wt[0] = cl
|
||||
wt[1] = func
|
||||
wt[2] = impl
|
||||
|
||||
nt[#nt + 1] = wt
|
||||
end
|
||||
|
||||
local get_best_scope = function(f)
|
||||
local ft = f:type_get()
|
||||
if ft == eolian.function_type.PROPERTY then
|
||||
local fs1, fs2 = f:scope_get(eolian.function_type.PROP_GET), f:scope_get(eolian.function_type.PROP_SET)
|
||||
if fs1 == eolian.object_scope.PUBLIC or fs2 == eolian.object_scope.PUBLIC then
|
||||
return eolian.object_scope.PUBLIC
|
||||
elseif fs1 == eolian.object_scope.PROTECTED or fs2 == eolian.object_scope.PROTECTED then
|
||||
return eolian.object_scope.PROTECTED
|
||||
else
|
||||
return eolian.object_scope.PRIVATE
|
||||
end
|
||||
else
|
||||
return f:scope_get(ft)
|
||||
end
|
||||
end
|
||||
table.sort(nt, function(v1, v2)
|
||||
local cl1, cl2 = v1[0], v2[0]
|
||||
if cl1 ~= cl2 then
|
||||
return cl1:name_get() < cl2:name_get()
|
||||
end
|
||||
|
||||
local f1, f2 = v1[1], v2[1]
|
||||
local f1s, f2s = get_best_scope(f1), get_best_scope(f2)
|
||||
if f1s ~= f2s then
|
||||
if f1s ~= eolian.object_scope.PROTECED then
|
||||
-- public funcs go first, private funcs go last
|
||||
return f1s == eolian.object_scope.PUBLIC
|
||||
else
|
||||
-- protected funcs go second
|
||||
return f2s == eolian.object_scope.PRIVATE
|
||||
end
|
||||
end
|
||||
return f1:name_get() < f2:name_get()
|
||||
end)
|
||||
|
||||
return nt
|
||||
end
|
||||
|
||||
local find_callables
|
||||
find_callables = function(cl, omeths, events, written)
|
||||
for pcl in cl:inherits_get() do
|
||||
for impl in pcl:implements_get() do
|
||||
local func = impl:function_get()
|
||||
local fid = eoutils.obj_id_get(func)
|
||||
if not written[fid] then
|
||||
omeths[#omeths + 1] = { pcl, impl }
|
||||
written[fid] = true
|
||||
end
|
||||
end
|
||||
for ev in pcl:events_get() do
|
||||
local evid = ev:name_get()
|
||||
if not written[evid] then
|
||||
events[#events + 1] = { pcl, ev }
|
||||
written[evid] = true
|
||||
end
|
||||
end
|
||||
find_callables(pcl, omeths, events, written)
|
||||
end
|
||||
end
|
||||
|
||||
M.build_inherits = function(cl, t, lvl)
|
||||
t = t or {}
|
||||
lvl = lvl or 0
|
||||
local lbuf = writer.Buffer()
|
||||
if lvl > 0 then
|
||||
local cln = eoutils.obj_nspaces_get(cl, true)
|
||||
cln[#cln] = nil
|
||||
cln[#cln] = cln[#cln] .. "_mono"
|
||||
cln = ":" .. 'develop:api' .. ":"
|
||||
.. table.concat(cln, ":")
|
||||
lbuf:write_raw("[[", cln, "|", get_class_name(cl), "]]")
|
||||
--lbuf:write_link(eoutils.obj_nspaces_get(cl, true), cl:name_get())
|
||||
lbuf:write_raw(" ")
|
||||
lbuf:write_i("(" .. eoutils.class_type_str_get(cl) .. ")")
|
||||
|
||||
t[#t + 1] = { lvl - 1, lbuf:finish() }
|
||||
end
|
||||
|
||||
for acl in cl:inherits_get() do
|
||||
M.build_inherits(acl, t, lvl + 1)
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
M.build_inherit_summary = function(cl, buf)
|
||||
buf = buf or writer.Buffer()
|
||||
buf:write_raw(" => ")
|
||||
|
||||
local cln = eoutils.obj_nspaces_get(cl, true)
|
||||
cln[#cln] = nil
|
||||
cln[#cln] = cln[#cln] .. "_mono"
|
||||
cln = ":" .. 'develop:api' .. ":"
|
||||
.. table.concat(cln, ":")
|
||||
buf:write_raw("[[", cln, "|", get_class_name(cl), "]]")
|
||||
buf:write_raw(" ")
|
||||
buf:write_i("(" .. eoutils.class_type_str_get(cl) .. ")")
|
||||
|
||||
local inherits = cl:inherits_get():to_array()
|
||||
if #inherits ~= 0 then
|
||||
M.build_inherit_summary(inherits[1], buf)
|
||||
end
|
||||
return buf
|
||||
end
|
||||
|
||||
M.write_inherit_functable = function(f, tcl, tbl)
|
||||
if #tbl == 0 then
|
||||
return
|
||||
end
|
||||
local nt = build_functable(t, tcl, tbl)
|
||||
|
||||
local prevcl = tcl
|
||||
for i, wt in ipairs(nt) do
|
||||
local cl = wt[0]
|
||||
local func = wt[1]
|
||||
local impl = wt[2]
|
||||
|
||||
local func = impl:function_get()
|
||||
|
||||
-- class grouping for inheritance
|
||||
if cl ~= prevcl then
|
||||
prevcl = cl
|
||||
f:write_raw("^ ")
|
||||
f:write_link(eoutils.obj_nspaces_get(cl, true), cl:name_get())
|
||||
f:write_raw(" ^^^")
|
||||
f:write_nl()
|
||||
end
|
||||
|
||||
-- scope
|
||||
f:write_raw("| ")
|
||||
write_scope(f, func)
|
||||
f:write_raw(" | ")
|
||||
-- function
|
||||
write_function(f, func, cl)
|
||||
f:write_raw(" | ")
|
||||
-- description
|
||||
write_description(f, impl, func, cl)
|
||||
f:write_raw(" |")
|
||||
f:write_nl()
|
||||
end
|
||||
f:write_nl()
|
||||
end
|
||||
|
||||
M.write_functable = function(f, tcl, tbl)
|
||||
if #tbl == 0 then
|
||||
return
|
||||
end
|
||||
local nt = build_functable(t, tcl, tbl)
|
||||
|
||||
local wrote = false
|
||||
for i, wt in ipairs(nt) do
|
||||
local cl = wt[0]
|
||||
local func = wt[1]
|
||||
local impl = wt[2]
|
||||
|
||||
local ocl = impl:class_get()
|
||||
local func = impl:function_get()
|
||||
local over = eoutils.impl_is_overridden(impl, cl)
|
||||
|
||||
-- function
|
||||
write_function(f, func, cl)
|
||||
-- scope
|
||||
write_scope(f, func)
|
||||
|
||||
-- overrides
|
||||
if over then
|
||||
-- TODO: possibly also mention which part of a property was
|
||||
-- overridden and where, get/set override point might differ!
|
||||
-- but we get latest doc every time so it's ok for now
|
||||
local llbuf = writer.Buffer()
|
||||
llbuf:write_raw(" [Overridden from ")
|
||||
llbuf:write_link(eoutils.obj_nspaces_get(ocl, true), ocl:name_get())
|
||||
llbuf:write_raw("]")
|
||||
f:write_i(llbuf:finish())
|
||||
end
|
||||
|
||||
-- description
|
||||
f:write_br(true)
|
||||
f:write_raw("> ")
|
||||
write_description(f, impl, func, cl)
|
||||
|
||||
-- code snippets
|
||||
f:write_nl()
|
||||
local codes = {}
|
||||
if func:type_get() ~= eolian.function_type.PROPERTY then
|
||||
codes[#codes + 1] = gen_func_mono_sig(func, func:type_get())
|
||||
else
|
||||
codes[#codes + 1] = gen_func_mono_sig(func, eolian.function_type.PROP_GET)
|
||||
codes[#codes + 1] = gen_func_mono_sig(func, eolian.function_type.PROP_SET)
|
||||
end
|
||||
f:write_code(table.concat(codes, "\n"), "c")
|
||||
f:write_br(true)
|
||||
end
|
||||
f:write_nl()
|
||||
end
|
||||
|
||||
M.build_class = function(cl)
|
||||
local cln = eoutils.obj_nspaces_get(cl)
|
||||
local fulln = cl:name_get()
|
||||
--table.insert(cln, "mono")
|
||||
cln[#cln] = cln[#cln] .. "_mono"
|
||||
--printgen("Generating (MONO) class: " .. fulln .. " in ns ", unpack(cln))
|
||||
local f = writer.Writer(cln, fulln .. " (mono)")
|
||||
f:write_h(cl:name_get() .. " (" .. eoutils.class_type_str_get(cl) .. ")", 1)
|
||||
|
||||
f:write_h("Description", 2)
|
||||
f:write_raw(eoutils.doc_full_get(eos, cl:documentation_get(), nil, true))
|
||||
f:write_nl(2)
|
||||
|
||||
f:write_editable(cln, "description")
|
||||
f:write_nl()
|
||||
|
||||
local inherits = cl:inherits_get():to_array()
|
||||
if #inherits ~= 0 then
|
||||
f:write_h("Inheritance", 2)
|
||||
|
||||
f:write_raw(M.build_inherit_summary(inherits[1]):finish())
|
||||
f:write_nl()
|
||||
|
||||
f:write_folded("Full hierarchy", function()
|
||||
f:write_list(M.build_inherits(cl))
|
||||
end)
|
||||
f:write_nl()
|
||||
end
|
||||
|
||||
local written = {}
|
||||
local ievs = {}
|
||||
local meths, omeths = {}, {}
|
||||
for impl in cl:implements_get() do
|
||||
local func = impl:function_get()
|
||||
written[eoutils.obj_id_get(func)] = true
|
||||
meths[#meths + 1] = { cl, impl }
|
||||
end
|
||||
find_callables(cl, omeths, ievs, written)
|
||||
|
||||
f:write_h("Members", 2)
|
||||
M.write_functable(f, cl, meths, true)
|
||||
if #omeths ~= 0 then
|
||||
f:write_h("Inherited", 3)
|
||||
end
|
||||
M.write_inherit_functable(f, cl, omeths, false)
|
||||
|
||||
f:finish()
|
||||
end
|
||||
|
||||
M.init = function(eosh)
|
||||
eos = eosh
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
|
|
@ -0,0 +1,278 @@
|
|||
local eolian = require("eolian")
|
||||
local eoutils = require("docgen.eolian_utils")
|
||||
|
||||
local is_verbose = false
|
||||
|
||||
local M = {}
|
||||
|
||||
local stats = {}
|
||||
|
||||
local stats_pd = function(n)
|
||||
local ret = 0
|
||||
if n == 0 then
|
||||
return 1
|
||||
end
|
||||
while (n ~= 0) do
|
||||
n = math.floor(n / 10)
|
||||
ret = ret + 1
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
local fcol = 30
|
||||
local ncol = 0
|
||||
|
||||
local get_percent = function(sv, svu)
|
||||
return (sv == 0) and 100 or math.floor(((sv - svu) / sv) * 100 + 0.5)
|
||||
end
|
||||
|
||||
local print_stat = function(printname, statname)
|
||||
local sv = stats[statname] or 0
|
||||
local svu = stats[statname .. "_undoc"] or 0
|
||||
local percent = get_percent(sv, svu)
|
||||
local tb = (" "):rep(math.max(0, fcol - #printname - 1) + ncol - stats_pd(sv))
|
||||
local dtb = (" "):rep(ncol - stats_pd(sv - svu))
|
||||
local ptb = (" "):rep(3 - stats_pd(percent))
|
||||
print(("%s:%s%d (documented: %s%d or %s%d%%)")
|
||||
:format(printname, tb, sv, dtb, sv - svu, ptb, percent))
|
||||
end
|
||||
|
||||
local get_secstats = function(...)
|
||||
local sv, svu = 0, 0
|
||||
for i, v in ipairs({ ... }) do
|
||||
sv = sv + (stats[v] or 0)
|
||||
svu = svu + (stats[v .. "_undoc"] or 0)
|
||||
end
|
||||
return sv - svu, sv, get_percent(sv, svu)
|
||||
end
|
||||
|
||||
M.print = function()
|
||||
for k, v in pairs(stats) do
|
||||
ncol = math.max(ncol, stats_pd(v))
|
||||
end
|
||||
|
||||
print(("=== CLASS SECTION: %d out of %d (%d%%) ===\n")
|
||||
:format(get_secstats("class", "interface", "mixin", "event")))
|
||||
print_stat("Classes", "class")
|
||||
print_stat("Interfaces", "interface")
|
||||
print_stat("Mixins", "mixin")
|
||||
print_stat("Events", "event")
|
||||
|
||||
print(("\n=== FUNCTION SECTION: %d out of %d (%d%%) ===\n")
|
||||
:format(get_secstats("method", "param", "mret",
|
||||
"getter", "gret", "gkey", "gvalue",
|
||||
"setter", "sret", "skey", "svalue")))
|
||||
print_stat("Methods", "method")
|
||||
print_stat(" Method params", "param")
|
||||
print_stat(" Method returns", "mret")
|
||||
print_stat("Getters", "getter")
|
||||
print_stat(" Getter returns", "gret")
|
||||
print_stat(" Getter keys", "gkey")
|
||||
print_stat(" Getter values", "gvalue")
|
||||
print_stat("Setters", "setter")
|
||||
print_stat(" Setter returns", "sret")
|
||||
print_stat(" Setter keys", "skey")
|
||||
print_stat(" Setter values", "svalue")
|
||||
|
||||
print(("\n=== TYPE SECTION: %d out of %d (%d%%) ===\n")
|
||||
:format(get_secstats("alias", "struct", "sfield", "enum", "efield")))
|
||||
print_stat("Aliases", "alias")
|
||||
print_stat("Structs", "struct")
|
||||
print_stat("Struct fields", "sfield")
|
||||
print_stat("Enums", "enum")
|
||||
print_stat("Enum fields", "efield")
|
||||
|
||||
print(("\n=== VARIABLE SECTION: %d out of %d (%d%%) ===\n")
|
||||
:format(get_secstats("constant", "global")))
|
||||
print_stat("Constants", "constant")
|
||||
print_stat("Globals", "global")
|
||||
|
||||
local sv, svu = 0, 0
|
||||
for k, v in pairs(stats) do
|
||||
if k:match(".*_undoc$") then
|
||||
svu = svu + v
|
||||
else
|
||||
sv = sv + v
|
||||
end
|
||||
end
|
||||
print(("\n=== TOTAL: %d out of %d (%d%%) ===")
|
||||
:format(sv - svu, sv, get_percent(sv, svu)))
|
||||
end
|
||||
|
||||
local stat_incr = function(name, missing)
|
||||
if not stats[name] then
|
||||
stats[name], stats[name .. "_undoc"] = 0, 0
|
||||
end
|
||||
stats[name] = stats[name] + 1
|
||||
if missing then
|
||||
stats[name .. "_undoc"] = stats[name .. "_undoc"] + 1
|
||||
end
|
||||
end
|
||||
|
||||
local print_missing = function(name, tp)
|
||||
if not is_verbose then
|
||||
return
|
||||
end
|
||||
print(tp .. " '" .. name .. "'" .. " missing documentation")
|
||||
end
|
||||
|
||||
M.check_class = function(cl)
|
||||
local ct = eoutils.class_type_str_get(cl)
|
||||
if not ct then
|
||||
return
|
||||
end
|
||||
if not cl:documentation_get() then
|
||||
print_missing(cl:name_get(), ct)
|
||||
stat_incr(ct, true)
|
||||
else
|
||||
stat_incr(ct, false)
|
||||
end
|
||||
|
||||
for ev in cl:events_get() do
|
||||
if not ev:documentation_get() then
|
||||
print_missing(cl:name_get() .. "." .. ev:name_get(), "event")
|
||||
stat_incr("event", true)
|
||||
else
|
||||
stat_incr("event", false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.check_method = function(fn, cl)
|
||||
local fulln = cl:name_get() .. "." .. fn:name_get()
|
||||
if fn:return_type_get(eolian.function_type.METHOD) then
|
||||
if not fn:return_documentation_get(eolian.function_type.METHOD) then
|
||||
print_missing(fulln, "method return")
|
||||
stat_incr("mret", true)
|
||||
else
|
||||
stat_incr("mret", false)
|
||||
end
|
||||
end
|
||||
if not fn:implement_get():documentation_get(eolian.function_type.METHOD) then
|
||||
print_missing(fulln, "method")
|
||||
stat_incr("method", true)
|
||||
else
|
||||
stat_incr("method", false)
|
||||
end
|
||||
for p in fn:parameters_get() do
|
||||
if not p:documentation_get() then
|
||||
print_missing(fulln .. "." .. p:name_get(), "method parameter")
|
||||
stat_incr("param", true)
|
||||
else
|
||||
stat_incr("param", false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.check_property = function(fn, cl, ft)
|
||||
local pfxs = {
|
||||
[eolian.function_type.PROP_GET] = "g",
|
||||
[eolian.function_type.PROP_SET] = "s",
|
||||
}
|
||||
local pfx = pfxs[ft]
|
||||
|
||||
local fulln = cl:name_get() .. "." .. fn:name_get()
|
||||
if fn:return_type_get(ft) then
|
||||
if not fn:return_documentation_get(ft) then
|
||||
print_missing(fulln, pfx .. "etter return")
|
||||
stat_incr(pfx .. "ret", true)
|
||||
else
|
||||
stat_incr(pfx .. "ret", false)
|
||||
end
|
||||
end
|
||||
|
||||
local pimp = fn:implement_get()
|
||||
|
||||
if not pimp:documentation_get(eolian.function_type.PROPERTY) and
|
||||
not pimp:documentation_get(ft) then
|
||||
print_missing(fulln, pfx .. "etter")
|
||||
stat_incr(pfx .. "etter", true)
|
||||
else
|
||||
stat_incr(pfx .. "etter", false)
|
||||
end
|
||||
|
||||
for p in fn:property_keys_get(ft) do
|
||||
if not p:documentation_get() then
|
||||
print_missing(fulln .. "." .. p:name_get(), pfx .. "etter key")
|
||||
stat_incr(pfx .. "key", true)
|
||||
else
|
||||
stat_incr(pfx .. "key", false)
|
||||
end
|
||||
end
|
||||
|
||||
for p in fn:property_values_get(ft) do
|
||||
if not p:documentation_get() then
|
||||
print_missing(fulln .. "." .. p:name_get(), pfx .. "etter value")
|
||||
stat_incr(pfx .. "value", true)
|
||||
else
|
||||
stat_incr(pfx .. "value", false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.check_alias = function(v)
|
||||
if not v:documentation_get() then
|
||||
print_missing(v:name_get(), "alias")
|
||||
stat_incr("alias", true)
|
||||
else
|
||||
stat_incr("alias", false)
|
||||
end
|
||||
end
|
||||
|
||||
M.check_struct = function(v)
|
||||
if not v:documentation_get() then
|
||||
print_missing(v:name_get(), "struct")
|
||||
stat_incr("struct", true)
|
||||
else
|
||||
stat_incr("struct", false)
|
||||
end
|
||||
for fl in v:struct_fields_get() do
|
||||
if not fl:documentation_get() then
|
||||
print_missing(v:name_get() .. "." .. fl:name_get(), "struct field")
|
||||
stat_incr("sfield", true)
|
||||
else
|
||||
stat_incr("sfield", false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.check_enum = function(v)
|
||||
if not v:documentation_get() then
|
||||
print_missing(v:name_get(), "enum")
|
||||
stat_incr("enum", true)
|
||||
else
|
||||
stat_incr("enum", false)
|
||||
end
|
||||
for fl in v:enum_fields_get() do
|
||||
if not fl:documentation_get() then
|
||||
print_missing(v:name_get() .. "." .. fl:name_get(), "enum field")
|
||||
stat_incr("efield", true)
|
||||
else
|
||||
stat_incr("efield", false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.check_constant = function(v)
|
||||
if not v:documentation_get() then
|
||||
print_missing(v:name_get(), "constant")
|
||||
stat_incr("constant", true)
|
||||
else
|
||||
stat_incr("constant", false)
|
||||
end
|
||||
end
|
||||
|
||||
M.check_global = function(v)
|
||||
if not v:documentation_get() then
|
||||
print_missing(v:name_get(), "global")
|
||||
stat_incr("global", true)
|
||||
else
|
||||
stat_incr("global", false)
|
||||
end
|
||||
end
|
||||
|
||||
M.init = function(verbose)
|
||||
is_verbose = verbose
|
||||
end
|
||||
|
||||
return M
|
|
@ -0,0 +1,50 @@
|
|||
local cutil = require("cutil")
|
||||
local ffi = require("ffi")
|
||||
|
||||
local M = {}
|
||||
|
||||
local doc_root, root_ns
|
||||
|
||||
local path_sep, rep_sep = "/", "\\"
|
||||
if ffi.os == "Windows" then
|
||||
path_sep, rep_sep = rep_sep, path_sep
|
||||
end
|
||||
|
||||
M.path_join = function(...)
|
||||
return (table.concat({ ... }, path_sep):gsub(rep_sep, path_sep))
|
||||
end
|
||||
|
||||
M.path_to_nspace = function(p)
|
||||
return p:gsub(rep_sep, ":"):gsub(path_sep, ":"):lower()
|
||||
end
|
||||
|
||||
M.nspace_to_path = function(ns)
|
||||
return ns:gsub(":", path_sep):gsub(rep_sep, path_sep):lower()
|
||||
end
|
||||
|
||||
M.make_page = function(path, ext)
|
||||
return M.path_join(doc_root, path .. "." .. ext)
|
||||
end
|
||||
|
||||
M.get_root_ns = function()
|
||||
return root_ns
|
||||
end
|
||||
|
||||
M.mkdir_r = function(dirn)
|
||||
assert(cutil.file_mkpath(M.path_join(doc_root, dirn)))
|
||||
end
|
||||
|
||||
M.mkdir_p = function(path)
|
||||
M.mkdir_r(path:match("(.+)" .. path_sep .. "([^" .. path_sep .. "]+)"))
|
||||
end
|
||||
|
||||
M.rm_root = function()
|
||||
cutil.file_rmrf(doc_root)
|
||||
end
|
||||
|
||||
M.init = function(root, rns)
|
||||
doc_root = root:gsub(rep_sep, path_sep)
|
||||
root_ns = rns
|
||||
end
|
||||
|
||||
return M
|
|
@ -0,0 +1,403 @@
|
|||
local util = require("util")
|
||||
|
||||
local eolian = require("eolian")
|
||||
local eoutils = require("docgen.eolian_utils")
|
||||
|
||||
local dutil = require("docgen.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
local root_nspace, features
|
||||
|
||||
M.has_feature = function(fname)
|
||||
if not features then
|
||||
return false
|
||||
end
|
||||
return not not features[fname]
|
||||
end
|
||||
|
||||
local allowed_incflags = {
|
||||
noheader = { "noheader", "showheader" },
|
||||
firstseconly = { "firstseconly", "fullpage" },
|
||||
readmore = { "readmore", "noreadmore" },
|
||||
footer = { "footer", "nofooter" },
|
||||
link = { "link", "nolink" },
|
||||
permalink = { "permalink", "nopermalink" },
|
||||
date = { "date", "nodate" },
|
||||
mdate = { "mdate", "nomdate" },
|
||||
user = { "user", "nouser" },
|
||||
comments = { "comments", "nocomments" },
|
||||
linkbacks = { "linkbacks", "nolinkbacks" },
|
||||
tags = { "tags", "notags" },
|
||||
editbutton = { "editbtn", "noeditbtn" },
|
||||
redirect = { "redirect", "noredirect" },
|
||||
indent = { "indent", "noindent" },
|
||||
linkonly = { "linkonly", "nolinkonly" },
|
||||
title = { "title", "notitle" },
|
||||
pageexists = { "pageexists", "nopageexists" },
|
||||
parlink = { "parlink", "noparlink" },
|
||||
order = { { "id", "title", "created", "modified", "indexmenu", "custom" } },
|
||||
rsort = { "rsort", "sort" },
|
||||
depth = 0,
|
||||
inline = true,
|
||||
beforeeach = "",
|
||||
aftereach = ""
|
||||
}
|
||||
|
||||
local writers = {}
|
||||
|
||||
local Buffer = {
|
||||
__ctor = function(self)
|
||||
self.buf = {}
|
||||
end,
|
||||
|
||||
write_raw = function(self, ...)
|
||||
for i, v in ipairs({ ... }) do
|
||||
self.buf[#self.buf + 1] = v
|
||||
end
|
||||
return self
|
||||
end,
|
||||
|
||||
finish = function(self)
|
||||
self.result = table.concat(self.buf)
|
||||
self.buf = {}
|
||||
return self.result
|
||||
end
|
||||
}
|
||||
|
||||
local write_include = function(self, tp, name, flags)
|
||||
local it_to_tp = {
|
||||
[self.INCLUDE_PAGE] = "page",
|
||||
[self.INCLUDE_SECTION] = "section",
|
||||
[self.INCLUDE_NAMESPACE] = "namespace",
|
||||
[self.INCLUDE_TAG] = "tagtopic"
|
||||
}
|
||||
if type(name) == "table" then
|
||||
if name[#name] == true then
|
||||
name[#name] = nil
|
||||
name = ":" .. root_nspace .. ":"
|
||||
.. table.concat(name, ":")
|
||||
elseif name[#name] == false then
|
||||
name[#name] = nil
|
||||
name = ":" .. root_nspace .. "-include:"
|
||||
.. table.concat(name, ":")
|
||||
else
|
||||
name = table.concat(name, ":")
|
||||
end
|
||||
end
|
||||
self:write_raw("{{", it_to_tp[tp], ">", name);
|
||||
if flags then
|
||||
if tp == self.INCLUDE_SECTION and flags.section then
|
||||
self:write_raw("#", flags.section)
|
||||
end
|
||||
flags.section = nil
|
||||
local flstr = {}
|
||||
for k, v in pairs(flags) do
|
||||
local allow = allowed_incflags[k]
|
||||
if allow ~= nil then
|
||||
if type(allow) == "boolean" then
|
||||
flstr[#flstr + 1] = k
|
||||
elseif type(allow) == "number" or type(allow) == "string" then
|
||||
if type(v) ~= type(allow) then
|
||||
error("invalid value type for flag " .. k)
|
||||
end
|
||||
flstr[#flstr + 1] = k .. "=" .. v
|
||||
elseif type(allow) == "table" then
|
||||
if type(allow[1]) == "table" then
|
||||
local valid = false
|
||||
for i, vv in ipairs(allow[1]) do
|
||||
if v == vv then
|
||||
flstr[#flstr + 1] = k .. "=" .. v
|
||||
valid = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not valid then
|
||||
error("invalid value " .. v .. " for flag " .. k)
|
||||
end
|
||||
elseif type(allow[1]) == "string" and
|
||||
type(allow[2]) == "string" then
|
||||
if v then
|
||||
flstr[#flstr + 1] = allow[1]
|
||||
else
|
||||
flstr[#flstr + 1] = allow[2]
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
error("invalid include flag: " .. tostring(k))
|
||||
end
|
||||
end
|
||||
flstr = table.concat(flstr, "&")
|
||||
if #flstr > 0 then
|
||||
self:write_raw("&", flstr)
|
||||
end
|
||||
end
|
||||
self:write_raw("}}")
|
||||
self:write_nl()
|
||||
return self
|
||||
end
|
||||
|
||||
M.set_backend = function(bend)
|
||||
M.Writer = assert(writers[bend], "invalid generation backend")
|
||||
M.Buffer = M.Writer:clone(Buffer)
|
||||
end
|
||||
|
||||
writers["dokuwiki"] = util.Object:clone {
|
||||
INCLUDE_PAGE = 0,
|
||||
INCLUDE_SECTION = 1,
|
||||
INCLUDE_NAMESPACE = 2,
|
||||
INCLUDE_TAG = 3,
|
||||
|
||||
__ctor = function(self, path, title)
|
||||
local subs
|
||||
if type(path) == "table" then
|
||||
subs = dutil.path_join(unpack(path))
|
||||
else
|
||||
subs = dutil.nspace_to_path(path)
|
||||
end
|
||||
dutil.mkdir_p(subs)
|
||||
self.file = assert(io.open(dutil.make_page(subs, "txt"), "w"))
|
||||
if title then
|
||||
if M.has_feature("title") then
|
||||
self:write_raw("~~Title: ", title, "~~")
|
||||
self:write_nl()
|
||||
else
|
||||
self:write_h(title, 1)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
write_raw = function(self, ...)
|
||||
self.file:write(...)
|
||||
return self
|
||||
end,
|
||||
|
||||
write_nl = function(self, n)
|
||||
self:write_raw(("\n"):rep(n or 1))
|
||||
return self
|
||||
end,
|
||||
|
||||
write_h = function(self, heading, level, nonl)
|
||||
local s = ("="):rep(7 - level)
|
||||
self:write_raw(s, " ", heading, " ", s, "\n")
|
||||
if not nonl then
|
||||
self:write_nl()
|
||||
end
|
||||
return self
|
||||
end,
|
||||
|
||||
write_include = function(self, tp, name, flags)
|
||||
return write_include(self, tp, name, flags)
|
||||
end,
|
||||
|
||||
write_editable = function(self, ns, name)
|
||||
ns[#ns + 1] = name
|
||||
ns[#ns + 1] = false
|
||||
self:write_include(self.INCLUDE_PAGE, ns, {
|
||||
date = false, user = false, link = false
|
||||
})
|
||||
-- restore the table for later reuse
|
||||
-- the false gets deleted by write_include
|
||||
ns[#ns] = nil
|
||||
end,
|
||||
|
||||
write_inherited = function(self, ns)
|
||||
ns[#ns + 1] = true
|
||||
self:write_include(self.INCLUDE_PAGE, ns, {
|
||||
editbutton = false, date = false, user = false, link = false
|
||||
})
|
||||
end,
|
||||
|
||||
write_fmt = function(self, fmt1, fmt2, ...)
|
||||
self:write_raw(fmt1, ...)
|
||||
self:write_raw(fmt2)
|
||||
return self
|
||||
end,
|
||||
|
||||
write_b = function(self, ...)
|
||||
self:write_fmt("**", "**", ...)
|
||||
return self
|
||||
end,
|
||||
|
||||
write_i = function(self, ...)
|
||||
self:write_fmt("//", "//", ...)
|
||||
return self
|
||||
end,
|
||||
|
||||
write_u = function(self, ...)
|
||||
self:write_fmt("__", "__", ...)
|
||||
return self
|
||||
end,
|
||||
|
||||
write_s = function(self, ...)
|
||||
self:write_fmt("<del>", "</del>", ...)
|
||||
return self
|
||||
end,
|
||||
|
||||
write_m = function(self, ...)
|
||||
self:write_fmt("''", "''", ...)
|
||||
return self
|
||||
end,
|
||||
|
||||
write_sub = function(self, ...)
|
||||
self:write_fmt("<sub>", "</sub>", ...)
|
||||
return self
|
||||
end,
|
||||
|
||||
write_sup = function(self, ...)
|
||||
self:write_fmt("<sup>", "</sup>", ...)
|
||||
return self
|
||||
end,
|
||||
|
||||
write_br = function(self, nl)
|
||||
self:write_raw("\\\\", nl and "\n" or " ")
|
||||
return self
|
||||
end,
|
||||
|
||||
write_pre_inline = function(self, ...)
|
||||
self:write_fmt("%%", "%%", ...)
|
||||
return self
|
||||
end,
|
||||
|
||||
write_pre = function(self, ...)
|
||||
self:write_fmt("<nowiki>\n", "\n</nowiki>", ...)
|
||||
return self
|
||||
end,
|
||||
|
||||
write_code = function(self, str, lang)
|
||||
lang = lang and (" " .. lang) or ""
|
||||
self:write_raw("<code" .. lang .. ">\n", str, "\n</code>\n")
|
||||
end,
|
||||
|
||||
write_link = function(self, target, title)
|
||||
if type(target) == "table" then
|
||||
if target[#target] == false then
|
||||
target[#target] = nil
|
||||
target = ":" .. root_nspace .. "-include:"
|
||||
.. table.concat(target, ":")
|
||||
else
|
||||
target[#target] = nil
|
||||
target = ":" .. root_nspace .. ":"
|
||||
.. table.concat(target, ":")
|
||||
end
|
||||
end
|
||||
if not title then
|
||||
self:write_raw("[[", target:lower(), "|", target, "]]")
|
||||
return
|
||||
end
|
||||
target = target:lower()
|
||||
if type(title) == "string" then
|
||||
self:write_raw("[[", target, "|", title, "]]")
|
||||
return self
|
||||
end
|
||||
self:write_raw("[[", target, "|")
|
||||
title(self)
|
||||
self:write_raw("]]")
|
||||
return self
|
||||
end,
|
||||
|
||||
write_table = function(self, titles, tbl)
|
||||
if titles then
|
||||
self:write_raw("^ ", table.concat(titles, " ^ "), " ^\n")
|
||||
end
|
||||
for i, v in ipairs(tbl) do
|
||||
self:write_raw("| ", table.concat(v, " | "), " |\n")
|
||||
end
|
||||
return self
|
||||
end,
|
||||
|
||||
write_list = function(self, tbl, ord)
|
||||
local prec = ord and "-" or "*"
|
||||
for i, v in ipairs(tbl) do
|
||||
local lvl, str = 1, v
|
||||
if type(v) == "table" then
|
||||
lvl, str = v[1] + 1, v[2]
|
||||
end
|
||||
local pbeg, pend = str:match("([^\n]+)\n(.+)")
|
||||
if not pbeg then
|
||||
pbeg = str
|
||||
end
|
||||
self:write_raw((" "):rep(lvl), prec, " ", str, "\n")
|
||||
if pend then
|
||||
self:write_raw(pend, "\n")
|
||||
end
|
||||
end
|
||||
return self
|
||||
end,
|
||||
|
||||
write_par = function(self, str, eos)
|
||||
local tokp = eolian.doc_token_init()
|
||||
local notetypes = M.has_feature("notes") and {
|
||||
[eolian.doc_token_type.MARK_NOTE] = "<note>\n",
|
||||
[eolian.doc_token_type.MARK_WARNING] = "<note warning>\n",
|
||||
[eolian.doc_token_type.MARK_REMARK] = "<note tip>\n",
|
||||
[eolian.doc_token_type.MARK_TODO] = "<note>\n**TODO:** "
|
||||
} or {}
|
||||
local hasraw, hasnote = false, false
|
||||
local tokf = function()
|
||||
str = eolian.documentation_tokenize(str, tokp)
|
||||
return not not str
|
||||
end
|
||||
while tokf() do
|
||||
local tp = tokp:type_get()
|
||||
local tag = notetypes[tp]
|
||||
if tag then
|
||||
self:write_raw(tag)
|
||||
hasnote = true
|
||||
else
|
||||
if not hasraw then
|
||||
self:write_raw("%%")
|
||||
hasraw = true
|
||||
end
|
||||
if tp == eolian.doc_token_type.REF then
|
||||
local reft = eoutils.tok_ref_resolve(tokp, eos, true)
|
||||
local str = tokp:text_get()
|
||||
if str:sub(1, 1) == "[" then
|
||||
str = str:sub(2, #str - 1)
|
||||
end
|
||||
self:write_raw("%%")
|
||||
self:write_link(reft, str)
|
||||
self:write_raw("%%")
|
||||
else
|
||||
local str = tokp:text_get()
|
||||
assert(str, "internal tokenizer error")
|
||||
-- replace possible %% chars
|
||||
str = str:gsub("%%%%", "%%%%<nowiki>%%%%</nowiki>%%%%")
|
||||
if tp == eolian.doc_token_type.MARKUP_MONOSPACE then
|
||||
self:write_raw("%%''" .. str .. "''%%")
|
||||
else
|
||||
self:write_raw(str)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self:write_raw("%%")
|
||||
if hasnote then
|
||||
self:write_raw("\n</note>")
|
||||
end
|
||||
return self
|
||||
end,
|
||||
|
||||
write_folded = function(self, title, func)
|
||||
if M.has_feature("folds") then
|
||||
self:write_raw("++++ ", title, " |\n\n")
|
||||
end
|
||||
func(self)
|
||||
if M.has_feature("folds") then
|
||||
self:write_raw("\n\n++++")
|
||||
end
|
||||
return self
|
||||
end,
|
||||
|
||||
finish = function(self)
|
||||
self.file:close()
|
||||
end
|
||||
}
|
||||
|
||||
M.init = function(root_ns, ftrs)
|
||||
root_nspace = root_ns
|
||||
features = ftrs
|
||||
end
|
||||
|
||||
return M
|
2668
gendoc.lua
2668
gendoc.lua
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,254 @@
|
|||
local M = {}
|
||||
|
||||
local is_nl = function(c)
|
||||
return (c == "\n") or (c == "\r")
|
||||
end
|
||||
|
||||
local wses = {
|
||||
[" "] = true, ["\t"] = true, ["\v"] = true, ["\f"] = true
|
||||
}
|
||||
|
||||
local is_ws = function(c)
|
||||
return not not wses[c]
|
||||
end
|
||||
|
||||
local next_char = function(ls)
|
||||
local oc = ls.current
|
||||
ls.current = ls.stream()
|
||||
return oc
|
||||
end
|
||||
|
||||
local next_line = function(ls)
|
||||
local pc = next_char(ls)
|
||||
assert(is_nl(pc))
|
||||
if is_nl(ls.current) and (ls.current ~= pc) then
|
||||
local nc = next_char(ls)
|
||||
return "\n"
|
||||
end
|
||||
ls.lnum = ls.lnum + 1
|
||||
return "\n"
|
||||
end
|
||||
|
||||
local lex_new = function(s)
|
||||
local ret = {
|
||||
stream = s,
|
||||
lnum = 1
|
||||
}
|
||||
next_char(ret)
|
||||
return ret
|
||||
end
|
||||
|
||||
local lex_error = function(ls, msg)
|
||||
error(("input:%d: %s near '%s'"):format(ls.lnum, msg, ls.current or "EOF"))
|
||||
end
|
||||
|
||||
local pair_chars = {
|
||||
["*"] = true, ["%"] = true, ["-"] = true
|
||||
}
|
||||
|
||||
local lex_token
|
||||
|
||||
lex_token = function(ls)
|
||||
-- keep track of line numbers but consider newlines
|
||||
-- tokens in order to preserve format exactly as it was
|
||||
-- other whitespace needs no tracking
|
||||
if is_nl(ls.current) then
|
||||
return next_line(ls)
|
||||
end
|
||||
if ls.current == "\\" then
|
||||
next_char(ls)
|
||||
local c = ls.current
|
||||
if not c then
|
||||
lex_error(ls, "unfinished escape")
|
||||
end
|
||||
next_char(ls)
|
||||
return c
|
||||
end
|
||||
if ls.current == "{" then
|
||||
next_char(ls)
|
||||
-- comment
|
||||
if ls.current == "#" then
|
||||
next_char(ls)
|
||||
while true do
|
||||
if not ls.current then
|
||||
lex_error(ls, "unfinished comment")
|
||||
end
|
||||
if ls.current == "\\" then
|
||||
next_char(ls)
|
||||
next_char(ls)
|
||||
end
|
||||
local c = ls.current
|
||||
next_char(ls)
|
||||
if c == "#" and ls.current == "}" then
|
||||
next_char(ls)
|
||||
-- eliminated tail call
|
||||
return lex_token(ls)
|
||||
end
|
||||
end
|
||||
end
|
||||
local c = ls.current
|
||||
if c == "{" or c == "[" or pair_chars[c] then
|
||||
next_char(ls)
|
||||
return "{" .. c
|
||||
end
|
||||
return c
|
||||
end
|
||||
local c = ls.current
|
||||
next_char(ls)
|
||||
if ls.current == "}" then
|
||||
if c == "}" or c == "]" or pair_chars[c] then
|
||||
local nc = ls.current
|
||||
next_char(ls)
|
||||
return c .. nc
|
||||
end
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
local lex = function(ls)
|
||||
local ret = ls.lookahead
|
||||
if ret then
|
||||
ls.lookahead = nil
|
||||
return ret
|
||||
end
|
||||
return lex_token(ls)
|
||||
end
|
||||
|
||||
local lookahead = function(ls)
|
||||
local ret = lex_token(ls)
|
||||
ls.lookahead = ret
|
||||
return ret
|
||||
end
|
||||
|
||||
local get_until = function(ls, etok)
|
||||
local tok = lex(ls)
|
||||
local buf = {}
|
||||
while tok ~= etok do
|
||||
buf[#buf + 1] = tok
|
||||
tok = lex(ls)
|
||||
if not tok then
|
||||
lex_error(ls, ("'%s' expected"):format(etok))
|
||||
end
|
||||
end
|
||||
return table.concat(buf)
|
||||
end
|
||||
|
||||
local save_exp = function(cbuf, exp)
|
||||
cbuf[#cbuf + 1] = " write(" .. exp .. ") "
|
||||
end
|
||||
|
||||
local save_code = function(cbuf, code)
|
||||
cbuf[#cbuf + 1] = " " .. code .. " "
|
||||
end
|
||||
|
||||
local save_acc = function(cbuf, acc)
|
||||
save_exp(cbuf, ("%q"):format(table.concat(acc)))
|
||||
return {}
|
||||
end
|
||||
|
||||
|
||||
local parse = function(ls)
|
||||
local acc, cbuf = {}, {"local context, escape = ... "}
|
||||
local tok = lex(ls)
|
||||
local was_nl = false
|
||||
while tok ~= nil do
|
||||
local cnl, skip_tok = false, false
|
||||
if tok == "{{" then
|
||||
acc = save_acc(cbuf, acc)
|
||||
save_exp(cbuf, "escape(" .. get_until(ls, "}}") .. ")")
|
||||
elseif tok == "{*" then
|
||||
acc = save_acc(cbuf, acc)
|
||||
save_exp(cbuf, get_until(ls, "*}"))
|
||||
elseif tok == "{%" then
|
||||
local code = get_until(ls, "%}")
|
||||
local lah = lookahead(ls)
|
||||
skip_tok = true
|
||||
if not was_nl or (lah ~= "\n") then
|
||||
acc = save_acc(cbuf, acc)
|
||||
else
|
||||
acc[#acc] = nil
|
||||
end
|
||||
save_code(cbuf, code)
|
||||
elseif was_nl and is_ws(tok) then
|
||||
acc[#acc] = acc[#acc] .. tok
|
||||
cnl = true
|
||||
else
|
||||
cnl = (tok == "\n")
|
||||
if cnl then
|
||||
acc = save_acc(cbuf, acc)
|
||||
end
|
||||
acc[#acc + 1] = tok
|
||||
end
|
||||
was_nl = cnl
|
||||
tok = lex(ls)
|
||||
end
|
||||
save_acc(cbuf, acc)
|
||||
return table.concat(cbuf)
|
||||
end
|
||||
|
||||
-- turn input into a character stream
|
||||
local make_stream = function(str)
|
||||
local f
|
||||
-- if a string, test if it's a file path, otherwise consider a stream
|
||||
if type(str) == "string" then
|
||||
f = io.open(str)
|
||||
else
|
||||
f = str
|
||||
end
|
||||
-- a string and cannot be opened, treat as it is
|
||||
if not f then
|
||||
return str:gmatch(".")
|
||||
end
|
||||
-- otherwise turn into a special stream
|
||||
-- but if we can't read nything, return empty
|
||||
local chunksize = 64
|
||||
local ip = f:read(chunksize)
|
||||
if not ip or #ip == 0 then
|
||||
return function() end
|
||||
end
|
||||
local ss = ip:gmatch(".")
|
||||
return function()
|
||||
local c = ss()
|
||||
if not c then
|
||||
ip = f:read(chunksize)
|
||||
if not ip or #ip == 0 then
|
||||
return nil
|
||||
end
|
||||
c = ss()
|
||||
end
|
||||
return c
|
||||
end
|
||||
end
|
||||
|
||||
local default_esc = function(str)
|
||||
return str
|
||||
end
|
||||
|
||||
local default_mt = {
|
||||
-- not invoked for actual fields of the environment, but when invoked,
|
||||
-- look up in context first (that way we can access context variables
|
||||
-- without prefixing every time) and if that fails, global environment
|
||||
__index = function(self, n)
|
||||
local cf = self.context[n]
|
||||
if cf == nil then
|
||||
return getfenv(0)[n]
|
||||
end
|
||||
return cf
|
||||
end
|
||||
}
|
||||
|
||||
M.compile = function(str)
|
||||
-- environment is nicely encapsulated...
|
||||
local denv = setmetatable({ template = M }, default_mt)
|
||||
local f = setfenv(
|
||||
assert(loadstring(parse(lex_new(make_stream(str))))),
|
||||
denv
|
||||
)
|
||||
return function(ctx, escape, write)
|
||||
denv.context = ctx
|
||||
denv.write = write or io.write
|
||||
return f(ctx, escape or default_esc)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
Loading…
Reference in New Issue