diff --git a/doc.lua b/doc.lua index 4ea12e6..f7319b5 100644 --- a/doc.lua +++ b/doc.lua @@ -1,329 +1,329 @@ ------- --- Defining the ldoc document model. - - -require 'pl' - -local doc = {} - -local tools = require 'tools' -local split_dotted_name = tools.split_dotted_name - --- these are the basic tags known to ldoc. They come in several varieties: --- - tags with multiple values like 'param' (TAG_MULTI) --- - tags which are identifiers, like 'name' (TAG_ID) --- - tags with a single value, like 'release' (TAG_SINGLE) --- - tags which represent a type, like 'function' (TAG_TYPE) -local known_tags = { - param = 'M', see = 'M', usage = 'M', ['return'] = 'M', field = 'M', author='M'; - class = 'id', name = 'id', pragma = 'id'; - copyright = 'S', description = 'S', release = 'S'; - module = 'T', script = 'T',['function'] = 'T', table = 'T' -} -known_tags._alias = {} -known_tags._project_level = { - module = true, - script = true -} - -local TAG_MULTI,TAG_ID,TAG_SINGLE,TAG_TYPE = 'M','id','S','T' -doc.TAG_MULTI,doc.TAG_ID,doc.TAG_SINGLE,doc.TAG_TYPE = TAG_MULTI,TAG_ID,TAG_SINGLE,TAG_TYPE - --- add a new tag. -function doc.add_tag(tag,type,project_level) - if not known_tags[tag] then - known_tags[tag] = type - known_tags._project_level[tag] = project_level - end -end - --- add an alias to an existing tag (exposed through ldoc API) -function doc.add_alias (a,tag) - known_tags._alias[a] = tag -end - --- get the tag alias value, if it exists. -function doc.get_alias(tag) - return known_tags._alias[tag] -end - --- is it a'project level' tag, such as 'module' or 'script'? -function doc.project_level(tag) - return known_tags._project_level[tag] -end - --- we process each file, resulting in a File object, which has a list of Item objects. --- Items can be modules, scripts ('project level') or functions, tables, etc. --- (In the code 'module' refers to any project level tag.) --- When the File object is finalized, we specialize some items as modules which --- are 'container' types containing functions and tables, etc. - -local File = class() -local Item = class() -local Module = class(Item) -- a specialized kind of Item - -doc.File = File -doc.Item = Item -doc.Module = Module - -function File:_init(filename) - self.filename = filename - self.items = List() - self.modules = List() -end - -function File:new_item(tags,line) - local item = Item(tags) - self.items:append(item) - item.file = self - item.lineno = line - return item -end - -function File:finish() - local this_mod - local items = self.items - for item in items:iter() do - item:finish() - if doc.project_level(item.type) then - this_mod = item - -- if name is 'package.mod', then mod_name is 'mod' - local package,mname = split_dotted_name(this_mod.name) - if not package then - mname = this_mod.name - package = '' - else - package = package .. '.' - end - self.modules:append(this_mod) - this_mod.package = package - this_mod.mod_name = mname - this_mod.kinds = ModuleMap() -- the iterator over the module contents - else -- add the item to the module's item list - if this_mod then - -- new-style modules will have qualified names like 'mod.foo' - local mod,fname = split_dotted_name(item.name) - -- warning for inferred unqualified names in new style modules - -- (retired until we handle methods like Set:unset() properly) - if not mod and not this_mod.old_style and item.inferred then - --item:warning(item.name .. ' is declared in global scope') - end - -- if that's the mod_name, then we want to only use 'foo' - if mod == this_mod.mod_name and this_mod.tags.pragma ~= 'nostrip' then - item.name = fname - end - item.module = this_mod - local these_items = this_mod.items - these_items.by_name[item.name] = item - these_items:append(item) - - -- register this item with the iterator - this_mod.kinds:add(item,these_items) - - else - -- must be a free-standing function (sometimes a problem...) - end - end - end -end - -function Item:_init(tags) - self.summary = tags.summary - self.description = tags.description - tags.summary = nil - tags.description = nil - self.tags = {} - self.formal_args = tags.formal_args - tags.formal_args = nil - for tag,value in pairs(tags) do - local ttype = known_tags[tag] - if ttype == TAG_MULTI then - if type(value) == 'string' then - value = List{value} - end - self.tags[tag] = value - elseif ttype == TAG_ID then - if type(value) ~= 'string' then - -- such tags are _not_ multiple, e.g. name - self:error(tag..' cannot have multiple values') - else - self.tags[tag] = tools.extract_identifier(value) - end - elseif ttype == TAG_SINGLE then - self.tags[tag] = value - else - self:warning ('unknown tag: '..tag) - end - end -end - --- preliminary processing of tags. We check for any aliases, and for tags --- which represent types. This implements the shortcut notation. -function Item.check_tag(tags,tag) - tag = doc.get_alias(tag) or tag - local ttype = known_tags[tag] - if ttype == TAG_TYPE then - tags.class = tag - tag = 'name' - end - return tag -end - - -function Item:finish() - local tags = self.tags - self.name = tags.name - self.type = tags.class - self.usage = tags.usage - tags.name = nil - tags.class = nil - tags.usage = nil - -- see tags are multiple, but they may also be comma-separated - if tags.see then - tags.see = tools.expand_comma_list(tags.see) - end - if doc.project_level(self.type) then - -- we are a module, so become one! - self.items = List() - self.items.by_name = {} - setmetatable(self,Module) - else - -- params are either a function's arguments, or a table's fields, etc. - local params - if self.type == 'function' then - params = tags.param or List() - if tags['return'] then - self.ret = tags['return'] - end - else - params = tags.field or List() - end - tags.param = nil - local names,comments = List(),List() - for p in params:iter() do - local name,comment = p:match('%s*([%w_%.:]+)(.*)') - names:append(name) - comments:append(comment) - end - -- not all arguments may be commented -- - if self.formal_args then - -- however, ldoc allows comments in the arg list to be used - local fargs = self.formal_args - for a in fargs:iter() do - if not names:index(a) then - names:append(a) - comments:append (fargs.comments[a] or '') - end - end - end - self.params = names - for i,name in ipairs(self.params) do - self.params[name] = comments[i] - end - self.args = '('..self.params:join(', ')..')' - end -end - -function Item:warning(msg) - local name = self.file and self.file.filename - if type(name) == 'table' then pretty.dump(name); name = '?' end - name = name or '?' - io.stderr:write(name,':',self.lineno or '?',' ',msg,'\n') -end - --- resolving @see references. A word may be either a function in this module, --- or a module in this package. A MOD.NAME reference is within this package. --- Otherwise, the full qualified name must be used. --- First, check whether it is already a fully qualified module name. --- Then split it and see if the module part is a qualified module --- and try look up the name part in that module. --- If this isn't successful then try prepending the current package to the reference, --- and try to to resolve this. -function Module:resolve_references(modules) - local found = List() - - local function process_see_reference (item,see,s) - local mod_ref,fun_ref,name,packmod - -- is this a fully qualified module name? - local mod_ref = modules.by_name[s] - if mod_ref then return mod_ref,nil end - local packmod,name = split_dotted_name(s) -- e.g. 'pl.utils','split' - if packmod then -- qualified name - mod_ref = modules.by_name[packmod] -- fully qualified mod name? - if not mod_ref then - mod_ref = modules.by_name[self.package..packmod] - end - if not mod_ref then - item:warning("module not found: "..packmod) - return nil - end - fun_ref = mod_ref.items.by_name[name] - if fun_ref then return mod_ref,fun_ref - else - item:warning("function not found: "..s.." in "..mod_ref.name) - end - else -- plain jane name; module in this package, function in this module - mod_ref = modules.by_name[self.package..s] - if mod_ref then return mod_ref,nil end - fun_ref = self.items.by_name[s] - if fun_ref then return self,fun_ref - else - item:warning("function not found: "..s.." in this module") - end - end - end - - for item in self.items:iter() do - local see = item.tags.see - if see then -- this guy has @see references - item.see = List() - for s in see:iter() do - local mod_ref, item_ref = process_see_reference(item,see,s) - if mod_ref then - local name = item_ref and item_ref.name or '' - item.see:append {mod=mod_ref.name,name=name,label=s} - found:append{item,s} - end - end - end - end - -- mark as found, so we don't waste time re-searching - for f in found:iter() do - f[1].tags.see:remove_value(f[2]) - end -end - --- make a text dump of the contents of this File object. --- The level of detail is controlled by the 'verbose' parameter. --- Primarily intended as a debugging tool. -function File:dump(verbose) - for mod in self.modules:iter() do - print('Module:',mod.name,mod.summary,mod.description) - for item in mod.items:iter() do - item:dump(verbose) - end - end -end - -function Item:dump(verbose) - local tags = self.tags - local name = self.name - if self.type == 'function' then - name = name .. self.args - end - if verbose then - print(self.type,name,self.summary) - print(self.description) - for p in self.params:iter() do - print(p,self.params[p]) - end - for tag, value in pairs(self.tags) do - print(tag,value) - end - else - print('* '..name..' - '..self.summary) - end -end - -return doc - +------ +-- Defining the ldoc document model. + + +require 'pl' + +local doc = {} + +local tools = require 'tools' +local split_dotted_name = tools.split_dotted_name + +-- these are the basic tags known to ldoc. They come in several varieties: +-- - tags with multiple values like 'param' (TAG_MULTI) +-- - tags which are identifiers, like 'name' (TAG_ID) +-- - tags with a single value, like 'release' (TAG_SINGLE) +-- - tags which represent a type, like 'function' (TAG_TYPE) +local known_tags = { + param = 'M', see = 'M', usage = 'M', ['return'] = 'M', field = 'M', author='M'; + class = 'id', name = 'id', pragma = 'id'; + copyright = 'S', description = 'S', release = 'S'; + module = 'T', script = 'T',['function'] = 'T', table = 'T' +} +known_tags._alias = {} +known_tags._project_level = { + module = true, + script = true +} + +local TAG_MULTI,TAG_ID,TAG_SINGLE,TAG_TYPE = 'M','id','S','T' +doc.TAG_MULTI,doc.TAG_ID,doc.TAG_SINGLE,doc.TAG_TYPE = TAG_MULTI,TAG_ID,TAG_SINGLE,TAG_TYPE + +-- add a new tag. +function doc.add_tag(tag,type,project_level) + if not known_tags[tag] then + known_tags[tag] = type + known_tags._project_level[tag] = project_level + end +end + +-- add an alias to an existing tag (exposed through ldoc API) +function doc.add_alias (a,tag) + known_tags._alias[a] = tag +end + +-- get the tag alias value, if it exists. +function doc.get_alias(tag) + return known_tags._alias[tag] +end + +-- is it a'project level' tag, such as 'module' or 'script'? +function doc.project_level(tag) + return known_tags._project_level[tag] +end + +-- we process each file, resulting in a File object, which has a list of Item objects. +-- Items can be modules, scripts ('project level') or functions, tables, etc. +-- (In the code 'module' refers to any project level tag.) +-- When the File object is finalized, we specialize some items as modules which +-- are 'container' types containing functions and tables, etc. + +local File = class() +local Item = class() +local Module = class(Item) -- a specialized kind of Item + +doc.File = File +doc.Item = Item +doc.Module = Module + +function File:_init(filename) + self.filename = filename + self.items = List() + self.modules = List() +end + +function File:new_item(tags,line) + local item = Item(tags) + self.items:append(item) + item.file = self + item.lineno = line + return item +end + +function File:finish() + local this_mod + local items = self.items + for item in items:iter() do + item:finish() + if doc.project_level(item.type) then + this_mod = item + -- if name is 'package.mod', then mod_name is 'mod' + local package,mname = split_dotted_name(this_mod.name) + if not package then + mname = this_mod.name + package = '' + else + package = package .. '.' + end + self.modules:append(this_mod) + this_mod.package = package + this_mod.mod_name = mname + this_mod.kinds = ModuleMap() -- the iterator over the module contents + else -- add the item to the module's item list + if this_mod then + -- new-style modules will have qualified names like 'mod.foo' + local mod,fname = split_dotted_name(item.name) + -- warning for inferred unqualified names in new style modules + -- (retired until we handle methods like Set:unset() properly) + if not mod and not this_mod.old_style and item.inferred then + --item:warning(item.name .. ' is declared in global scope') + end + -- if that's the mod_name, then we want to only use 'foo' + if mod == this_mod.mod_name and this_mod.tags.pragma ~= 'nostrip' then + item.name = fname + end + item.module = this_mod + local these_items = this_mod.items + these_items.by_name[item.name] = item + these_items:append(item) + + -- register this item with the iterator + this_mod.kinds:add(item,these_items) + + else + -- must be a free-standing function (sometimes a problem...) + end + end + end +end + +function Item:_init(tags) + self.summary = tags.summary + self.description = tags.description + tags.summary = nil + tags.description = nil + self.tags = {} + self.formal_args = tags.formal_args + tags.formal_args = nil + for tag,value in pairs(tags) do + local ttype = known_tags[tag] + if ttype == TAG_MULTI then + if type(value) == 'string' then + value = List{value} + end + self.tags[tag] = value + elseif ttype == TAG_ID then + if type(value) ~= 'string' then + -- such tags are _not_ multiple, e.g. name + self:error(tag..' cannot have multiple values') + else + self.tags[tag] = tools.extract_identifier(value) + end + elseif ttype == TAG_SINGLE then + self.tags[tag] = value + else + self:warning ('unknown tag: '..tag) + end + end +end + +-- preliminary processing of tags. We check for any aliases, and for tags +-- which represent types. This implements the shortcut notation. +function Item.check_tag(tags,tag) + tag = doc.get_alias(tag) or tag + local ttype = known_tags[tag] + if ttype == TAG_TYPE then + tags.class = tag + tag = 'name' + end + return tag +end + + +function Item:finish() + local tags = self.tags + self.name = tags.name + self.type = tags.class + self.usage = tags.usage + tags.name = nil + tags.class = nil + tags.usage = nil + -- see tags are multiple, but they may also be comma-separated + if tags.see then + tags.see = tools.expand_comma_list(tags.see) + end + if doc.project_level(self.type) then + -- we are a module, so become one! + self.items = List() + self.items.by_name = {} + setmetatable(self,Module) + else + -- params are either a function's arguments, or a table's fields, etc. + local params + if self.type == 'function' then + params = tags.param or List() + if tags['return'] then + self.ret = tags['return'] + end + else + params = tags.field or List() + end + tags.param = nil + local names,comments = List(),List() + for p in params:iter() do + local name,comment = p:match('%s*([%w_%.:]+)(.*)') + names:append(name) + comments:append(comment) + end + -- not all arguments may be commented -- + if self.formal_args then + -- however, ldoc allows comments in the arg list to be used + local fargs = self.formal_args + for a in fargs:iter() do + if not names:index(a) then + names:append(a) + comments:append (fargs.comments[a] or '') + end + end + end + self.params = names + for i,name in ipairs(self.params) do + self.params[name] = comments[i] + end + self.args = '('..self.params:join(', ')..')' + end +end + +function Item:warning(msg) + local name = self.file and self.file.filename + if type(name) == 'table' then pretty.dump(name); name = '?' end + name = name or '?' + io.stderr:write(name,':',self.lineno or '?',' ',msg,'\n') +end + +-- resolving @see references. A word may be either a function in this module, +-- or a module in this package. A MOD.NAME reference is within this package. +-- Otherwise, the full qualified name must be used. +-- First, check whether it is already a fully qualified module name. +-- Then split it and see if the module part is a qualified module +-- and try look up the name part in that module. +-- If this isn't successful then try prepending the current package to the reference, +-- and try to to resolve this. +function Module:resolve_references(modules) + local found = List() + + local function process_see_reference (item,see,s) + local mod_ref,fun_ref,name,packmod + -- is this a fully qualified module name? + local mod_ref = modules.by_name[s] + if mod_ref then return mod_ref,nil end + local packmod,name = split_dotted_name(s) -- e.g. 'pl.utils','split' + if packmod then -- qualified name + mod_ref = modules.by_name[packmod] -- fully qualified mod name? + if not mod_ref then + mod_ref = modules.by_name[self.package..packmod] + end + if not mod_ref then + item:warning("module not found: "..packmod) + return nil + end + fun_ref = mod_ref.items.by_name[name] + if fun_ref then return mod_ref,fun_ref + else + item:warning("function not found: "..s.." in "..mod_ref.name) + end + else -- plain jane name; module in this package, function in this module + mod_ref = modules.by_name[self.package..s] + if mod_ref then return mod_ref,nil end + fun_ref = self.items.by_name[s] + if fun_ref then return self,fun_ref + else + item:warning("function not found: "..s.." in this module") + end + end + end + + for item in self.items:iter() do + local see = item.tags.see + if see then -- this guy has @see references + item.see = List() + for s in see:iter() do + local mod_ref, item_ref = process_see_reference(item,see,s) + if mod_ref then + local name = item_ref and item_ref.name or '' + item.see:append {mod=mod_ref.name,name=name,label=s} + found:append{item,s} + end + end + end + end + -- mark as found, so we don't waste time re-searching + for f in found:iter() do + f[1].tags.see:remove_value(f[2]) + end +end + +-- make a text dump of the contents of this File object. +-- The level of detail is controlled by the 'verbose' parameter. +-- Primarily intended as a debugging tool. +function File:dump(verbose) + for mod in self.modules:iter() do + print('Module:',mod.name,mod.summary,mod.description) + for item in mod.items:iter() do + item:dump(verbose) + end + end +end + +function Item:dump(verbose) + local tags = self.tags + local name = self.name + if self.type == 'function' then + name = name .. self.args + end + if verbose then + print(self.type,name,self.summary) + print(self.description) + for p in self.params:iter() do + print(p,self.params[p]) + end + for tag, value in pairs(self.tags) do + print(tag,value) + end + else + print('* '..name..' - '..self.summary) + end +end + +return doc + diff --git a/tests/example/config.ld b/tests/example/config.ld index 2bf75bb..333198d 100644 --- a/tests/example/config.ld +++ b/tests/example/config.ld @@ -1,12 +1,12 @@ --- ldoc configuration file -title = "testmod docs" -project = "testmod" - -description = [[ -This description applies to the project as a whole. -]] - -alias("p","param") - -new_type("macro","Macros") - +-- ldoc configuration file +title = "testmod docs" +project = "testmod" + +description = [[ +This description applies to the project as a whole. +]] + +alias("p","param") + +new_type("macro","Macros") + diff --git a/tests/example/mod1.lua b/tests/example/mod1.lua index dddc125..9ba0b21 100644 --- a/tests/example/mod1.lua +++ b/tests/example/mod1.lua @@ -1,60 +1,60 @@ ---------------------------- --- Test module providing bonzo.dog. --- Rest is a longer description --- @class module --- @name mod1 - ---- zero function. Two new ldoc features here; item types --- can be used directly as tags, and aliases for tags --- can be defined in config.lp. --- @function zero_fun --- @p k1 first --- @p k2 second - ---- first function. Some description --- @param p1 first parameter --- @param p2 second parameter -function mod1.first_fun(p1,p2) -end - -------------------------- --- second function. --- @param ... var args! -function mod1.second_function(...) -end - ------------- --- third function. Can also provide parameter comments inline, --- provided they follow this pattern. -function mod1.third_function( - alpha, -- correction A - beta, -- correction B - gamma -- factor C - ) -end - ------ --- A useful macro. This is an example of a custom 'kind'. --- @macro first_macro --- @see second_function - ----- general configuration table --- @table config --- @field A alpha --- @field B beta --- @field C gamma -mod1.config = { - A = 1, - B = 2, - C = 3 -} - ---[[-- -Another function. Using a Lua block comment -@param p a parameter -]] -function mod1.zero_function(p) -end - - - +--------------------------- +-- Test module providing bonzo.dog. +-- Rest is a longer description +-- @class module +-- @name mod1 + +--- zero function. Two new ldoc features here; item types +-- can be used directly as tags, and aliases for tags +-- can be defined in config.lp. +-- @function zero_fun +-- @p k1 first +-- @p k2 second + +--- first function. Some description +-- @param p1 first parameter +-- @param p2 second parameter +function mod1.first_fun(p1,p2) +end + +------------------------- +-- second function. +-- @param ... var args! +function mod1.second_function(...) +end + +------------ +-- third function. Can also provide parameter comments inline, +-- provided they follow this pattern. +function mod1.third_function( + alpha, -- correction A + beta, -- correction B + gamma -- factor C + ) +end + +----- +-- A useful macro. This is an example of a custom 'kind'. +-- @macro first_macro +-- @see second_function + +---- general configuration table +-- @table config +-- @field A alpha +-- @field B beta +-- @field C gamma +mod1.config = { + A = 1, + B = 2, + C = 3 +} + +--[[-- +Another function. Using a Lua block comment +@param p a parameter +]] +function mod1.zero_function(p) +end + + + diff --git a/tests/example/mylib.c b/tests/example/mylib.c index a5a3071..441a2c2 100644 --- a/tests/example/mylib.c +++ b/tests/example/mylib.c @@ -1,62 +1,62 @@ -/// A sample C extension. -// Demonstrates using ldoc's C/C++ support. Can either use /// or /*** */ etc. -// @module mylib -#include -#include - -// includes for Lua -#include -#include -#include - -/*** -Create a table with given array and hash slots. -@function createtable -@param narr initial array slots, default 0 -@param nrec initial hash slots, default 0 -*/ -static int l_createtable (lua_State *L) { - int narr = luaL_optint(L,1,0); - int nrec = luaL_optint(L,2,0); - lua_createtable(L,narr,nrec); - return 1; -} - -/*** -Solve a quadratic equation. -@function solve -@param a coefficient of x^2 -@param b coefficient of x -@param c constant -@return first root -@return second root -*/ -static int l_solve (lua_State *L) { - double a = lua_tonumber(L,1); // coeff of x*x - double b = lua_tonumber(L,2); // coef of x - double c = lua_tonumber(L,3); // constant - double abc = b*b - 4*a*c; - if (abc < 0.0) { - lua_pushnil(L); - lua_pushstring(L,"imaginary roots!"); - return 2; - } else { - abc = sqrt(abc); - a = 2*a; - lua_pushnumber(L,(-b + abc)/a); - lua_pushnumber(L,(+b - abc)/a); - return 2; - } -} - -static const luaL_reg mylib[] = { - {"createtable",l_createtable}, - {"solve",l_solve}, - {NULL,NULL} -}; - -int luaopen_mylib(lua_State *L) -{ - luaL_register (L, "mylib", mylib); - return 1; -} +/// A sample C extension. +// Demonstrates using ldoc's C/C++ support. Can either use /// or /*** */ etc. +// @module mylib +#include +#include + +// includes for Lua +#include +#include +#include + +/*** +Create a table with given array and hash slots. +@function createtable +@param narr initial array slots, default 0 +@param nrec initial hash slots, default 0 +*/ +static int l_createtable (lua_State *L) { + int narr = luaL_optint(L,1,0); + int nrec = luaL_optint(L,2,0); + lua_createtable(L,narr,nrec); + return 1; +} + +/*** +Solve a quadratic equation. +@function solve +@param a coefficient of x^2 +@param b coefficient of x +@param c constant +@return first root +@return second root +*/ +static int l_solve (lua_State *L) { + double a = lua_tonumber(L,1); // coeff of x*x + double b = lua_tonumber(L,2); // coef of x + double c = lua_tonumber(L,3); // constant + double abc = b*b - 4*a*c; + if (abc < 0.0) { + lua_pushnil(L); + lua_pushstring(L,"imaginary roots!"); + return 2; + } else { + abc = sqrt(abc); + a = 2*a; + lua_pushnumber(L,(-b + abc)/a); + lua_pushnumber(L,(+b - abc)/a); + return 2; + } +} + +static const luaL_reg mylib[] = { + {"createtable",l_createtable}, + {"solve",l_solve}, + {NULL,NULL} +}; + +int luaopen_mylib(lua_State *L) +{ + luaL_register (L, "mylib", mylib); + return 1; +} diff --git a/tests/md-test/config.ld b/tests/md-test/config.ld index 1bb9271..36f9c72 100644 --- a/tests/md-test/config.ld +++ b/tests/md-test/config.ld @@ -1,3 +1,3 @@ -project = 'md-test' -title = 'Markdown Docs' -format = 'markdown' +project = 'md-test' +title = 'Markdown Docs' +format = 'markdown' diff --git a/tests/simple/simple.lua b/tests/simple/simple.lua index b05b8bb..15d2a39 100644 --- a/tests/simple/simple.lua +++ b/tests/simple/simple.lua @@ -1,12 +1,12 @@ ------------- --- A little old-style module -local io = io --- we'll look for this -module 'simple' - --- if it were 'module (...)' then the name has to be deduced. - ---- return the answer. -function answer() - return 42 -end +------------ +-- A little old-style module +local io = io +-- we'll look for this +module 'simple' + +-- if it were 'module (...)' then the name has to be deduced. + +--- return the answer. +function answer() + return 42 +end diff --git a/tools.lua b/tools.lua index 70d6a58..c705a27 100644 --- a/tools.lua +++ b/tools.lua @@ -1,265 +1,265 @@ ---------- --- General utility functions for ldoc --- @module tools - -require 'pl' -local tools = {} -local M = tools -local append = table.insert -local lexer = require 'lexer' -local quit = utils.quit - --- this constructs an iterator over a list of objects which returns only --- those objects where a field has a certain value. It's used to iterate --- only over functions or tables, etc. --- (something rather similar exists in LuaDoc) -function M.type_iterator (list,field,value) - return function() - local i = 1 - return function() - local val = list[i] - while val and val[field] ~= value do - i = i + 1 - val = list[i] - end - i = i + 1 - if val then return val end - end - end -end - --- KindMap is used to iterate over a set of categories, called _kinds_, --- and the associated iterator over all items in that category. --- For instance, a module contains functions, tables, etc and we will --- want to iterate over these categories in a specified order: --- --- for kind, items in module.kinds() do --- print('kind',kind) --- for item in items() do print(item.name) end --- end --- --- The kind is typically used as a label or a Title, so for type 'function' the --- kind is 'Functions' and so on. - -local KindMap = class() -M.KindMap = KindMap - --- calling a KindMap returns an iterator. This returns the kind, the iterator --- over the items of that type, and the corresponding type. -function KindMap:__call () - local i = 1 - local klass = self.klass - return function() - local kind = klass.kinds[i] - if not kind then return nil end -- no more kinds - while not self[kind] do - i = i + 1 - kind = klass.kinds[i] - if not kind then return nil end - end - i = i + 1 - return kind, self[kind], klass.types_by_kind[kind] - end -end - --- called for each new item. It does not actually create separate lists, --- (although that would not break the interface) but creates iterators --- for that item type if not already created. -function KindMap:add (item,items) - local kname = self.klass.types_by_tag[item.type] - if not self[kname] then - self[kname] = M.type_iterator (items,'type',item.type) - end -end - --- KindMap has a 'class constructor' which is used to modify --- any new base class. -function KindMap._class_init (klass) - klass.kinds = {} -- list in correct order of kinds - klass.types_by_tag = {} -- indexed by tag - klass.types_by_kind = {} -- indexed by kind -end - - -function KindMap.add_kind (klass,tag,kind,subnames) - klass.types_by_tag[tag] = kind - klass.types_by_kind[kind] = {type=tag,subnames=subnames} - append(klass.kinds,kind) -end - - ------ some useful utility functions ------ - -function M.module_basepath() - local lpath = List.split(package.path,';') - for p in lpath:iter() do - local p = path.dirname(p) - if path.isabs(p) then - return p - end - end -end - --- split a qualified name into the module part and the name part, --- e.g 'pl.utils.split' becomes 'pl.utils' and 'split' -function M.split_dotted_name (s) - local s1,s2 = path.splitext(s) - if s2=='' then return nil - else return s1,s2:sub(2) - end -end - --- expand lists of possibly qualified identifiers --- given something like {'one , two.2','three.drei.drie)'} --- it will output {"one","two.2","three.drei.drie"} -function M.expand_comma_list (ls) - local new_ls = List() - for s in ls:iter() do - s = s:gsub('[^%.:%w]*$','') - if s:find ',' then - new_ls:extend(List.split(s,'%s*,%s*')) - else - new_ls:append(s) - end - end - return new_ls -end - -function M.extract_identifier (value) - return value:match('([%.:_%w]+)') -end - -function M.strip (s) - return s:gsub('^%s+',''):gsub('%s+$','') -end - -function M.check_directory(d) - if not path.isdir(d) then - lfs.mkdir(d) - end -end - -function M.check_file (f,original) - if not path.exists(f) then - dir.copyfile(original,f) - end -end - -function M.writefile(name,text) - local ok,err = utils.writefile(name,text) - if err then quit(err) end -end - -function M.name_of (lpath) - lpath,ext = path.splitext(lpath) - return lpath -end - -function M.this_module_name (basename,fname) - local ext - if basename == '' then - --quit("module(...) needs package basename") - return M.name_of(fname) - end - basename = path.abspath(basename) - if basename:sub(-1,-1) ~= path.sep then - basename = basename..path.sep - end - local lpath,cnt = fname:gsub('^'..utils.escape(basename),'') - if cnt ~= 1 then quit("module(...) name deduction failed: base "..basename.." "..fname) end - lpath = lpath:gsub(path.sep,'.') - return M.name_of(lpath) -end - - ---------- lexer tools ----- - -local tnext = lexer.skipws - -local function type_of (tok) return tok[1] end -local function value_of (tok) return tok[2] end - --- This parses Lua formal argument lists. It will return a list of argument --- names, which also has a comments field, which will contain any commments --- following the arguments. ldoc will use these in addition to explicit --- param tags. - -function M.get_parameters (tok) - local args = List() - args.comments = {} - local ltl = lexer.get_separated_list(tok) - - if #ltl[1] == 0 then return args end -- no arguments - - local function set_comment (idx,tok) - args.comments[args[idx]] = value_of(tok) - end - - for i = 1,#ltl do - local tl = ltl[i] - if type_of(tl[1]) == 'comment' then - if i > 1 then set_comment(i-1,tl[1]) end - if #tl > 1 then - args:append(value_of(tl[2])) - end - else - args:append(value_of(tl[1])) - end - if i == #ltl then - local last_tok = tl[#tl] - if #tl > 1 and type_of(last_tok) == 'comment' then - set_comment(i,last_tok) - end - end - end - - return args -end - --- parse a Lua identifier - contains names separated by . and :. -function M.get_fun_name (tok) - local res = {} - local _,name = tnext(tok) - _,sep = tnext(tok) - while sep == '.' or sep == ':' do - append(res,name) - append(res,sep) - _,name = tnext(tok) - _,sep = tnext(tok) - end - append(res,name) - return table.concat(res) -end - --- space-skipping version of token iterator -function M.space_skip_getter(tok) - return function () - local t,v = tok() - while t and t == 'space' do - t,v = tok() - end - return t,v - end -end - --- an embarassing function. The PL Lua lexer does not do block comments --- when used in line-grabbing mode, and in fact (0.9.4) does not even --- do them properly in full-text mode, due to a ordering mistake. --- So, we do what we can ;) -function M.grab_block_comment (v,tok,end1,end2) - local res = {v} - local t,last_v - repeat - last_v = v - t,v = tok() - append(res,v) - until last_v == end1 and v == end2 - table.remove(res) - table.remove(res) - res = table.concat(res) - return 'comment',res -end - - - -return tools +--------- +-- General utility functions for ldoc +-- @module tools + +require 'pl' +local tools = {} +local M = tools +local append = table.insert +local lexer = require 'lexer' +local quit = utils.quit + +-- this constructs an iterator over a list of objects which returns only +-- those objects where a field has a certain value. It's used to iterate +-- only over functions or tables, etc. +-- (something rather similar exists in LuaDoc) +function M.type_iterator (list,field,value) + return function() + local i = 1 + return function() + local val = list[i] + while val and val[field] ~= value do + i = i + 1 + val = list[i] + end + i = i + 1 + if val then return val end + end + end +end + +-- KindMap is used to iterate over a set of categories, called _kinds_, +-- and the associated iterator over all items in that category. +-- For instance, a module contains functions, tables, etc and we will +-- want to iterate over these categories in a specified order: +-- +-- for kind, items in module.kinds() do +-- print('kind',kind) +-- for item in items() do print(item.name) end +-- end +-- +-- The kind is typically used as a label or a Title, so for type 'function' the +-- kind is 'Functions' and so on. + +local KindMap = class() +M.KindMap = KindMap + +-- calling a KindMap returns an iterator. This returns the kind, the iterator +-- over the items of that type, and the corresponding type. +function KindMap:__call () + local i = 1 + local klass = self.klass + return function() + local kind = klass.kinds[i] + if not kind then return nil end -- no more kinds + while not self[kind] do + i = i + 1 + kind = klass.kinds[i] + if not kind then return nil end + end + i = i + 1 + return kind, self[kind], klass.types_by_kind[kind] + end +end + +-- called for each new item. It does not actually create separate lists, +-- (although that would not break the interface) but creates iterators +-- for that item type if not already created. +function KindMap:add (item,items) + local kname = self.klass.types_by_tag[item.type] + if not self[kname] then + self[kname] = M.type_iterator (items,'type',item.type) + end +end + +-- KindMap has a 'class constructor' which is used to modify +-- any new base class. +function KindMap._class_init (klass) + klass.kinds = {} -- list in correct order of kinds + klass.types_by_tag = {} -- indexed by tag + klass.types_by_kind = {} -- indexed by kind +end + + +function KindMap.add_kind (klass,tag,kind,subnames) + klass.types_by_tag[tag] = kind + klass.types_by_kind[kind] = {type=tag,subnames=subnames} + append(klass.kinds,kind) +end + + +----- some useful utility functions ------ + +function M.module_basepath() + local lpath = List.split(package.path,';') + for p in lpath:iter() do + local p = path.dirname(p) + if path.isabs(p) then + return p + end + end +end + +-- split a qualified name into the module part and the name part, +-- e.g 'pl.utils.split' becomes 'pl.utils' and 'split' +function M.split_dotted_name (s) + local s1,s2 = path.splitext(s) + if s2=='' then return nil + else return s1,s2:sub(2) + end +end + +-- expand lists of possibly qualified identifiers +-- given something like {'one , two.2','three.drei.drie)'} +-- it will output {"one","two.2","three.drei.drie"} +function M.expand_comma_list (ls) + local new_ls = List() + for s in ls:iter() do + s = s:gsub('[^%.:%w]*$','') + if s:find ',' then + new_ls:extend(List.split(s,'%s*,%s*')) + else + new_ls:append(s) + end + end + return new_ls +end + +function M.extract_identifier (value) + return value:match('([%.:_%w]+)') +end + +function M.strip (s) + return s:gsub('^%s+',''):gsub('%s+$','') +end + +function M.check_directory(d) + if not path.isdir(d) then + lfs.mkdir(d) + end +end + +function M.check_file (f,original) + if not path.exists(f) then + dir.copyfile(original,f) + end +end + +function M.writefile(name,text) + local ok,err = utils.writefile(name,text) + if err then quit(err) end +end + +function M.name_of (lpath) + lpath,ext = path.splitext(lpath) + return lpath +end + +function M.this_module_name (basename,fname) + local ext + if basename == '' then + --quit("module(...) needs package basename") + return M.name_of(fname) + end + basename = path.abspath(basename) + if basename:sub(-1,-1) ~= path.sep then + basename = basename..path.sep + end + local lpath,cnt = fname:gsub('^'..utils.escape(basename),'') + if cnt ~= 1 then quit("module(...) name deduction failed: base "..basename.." "..fname) end + lpath = lpath:gsub(path.sep,'.') + return M.name_of(lpath) +end + + +--------- lexer tools ----- + +local tnext = lexer.skipws + +local function type_of (tok) return tok[1] end +local function value_of (tok) return tok[2] end + +-- This parses Lua formal argument lists. It will return a list of argument +-- names, which also has a comments field, which will contain any commments +-- following the arguments. ldoc will use these in addition to explicit +-- param tags. + +function M.get_parameters (tok) + local args = List() + args.comments = {} + local ltl = lexer.get_separated_list(tok) + + if #ltl[1] == 0 then return args end -- no arguments + + local function set_comment (idx,tok) + args.comments[args[idx]] = value_of(tok) + end + + for i = 1,#ltl do + local tl = ltl[i] + if type_of(tl[1]) == 'comment' then + if i > 1 then set_comment(i-1,tl[1]) end + if #tl > 1 then + args:append(value_of(tl[2])) + end + else + args:append(value_of(tl[1])) + end + if i == #ltl then + local last_tok = tl[#tl] + if #tl > 1 and type_of(last_tok) == 'comment' then + set_comment(i,last_tok) + end + end + end + + return args +end + +-- parse a Lua identifier - contains names separated by . and :. +function M.get_fun_name (tok) + local res = {} + local _,name = tnext(tok) + _,sep = tnext(tok) + while sep == '.' or sep == ':' do + append(res,name) + append(res,sep) + _,name = tnext(tok) + _,sep = tnext(tok) + end + append(res,name) + return table.concat(res) +end + +-- space-skipping version of token iterator +function M.space_skip_getter(tok) + return function () + local t,v = tok() + while t and t == 'space' do + t,v = tok() + end + return t,v + end +end + +-- an embarassing function. The PL Lua lexer does not do block comments +-- when used in line-grabbing mode, and in fact (0.9.4) does not even +-- do them properly in full-text mode, due to a ordering mistake. +-- So, we do what we can ;) +function M.grab_block_comment (v,tok,end1,end2) + local res = {v} + local t,last_v + repeat + last_v = v + t,v = tok() + append(res,v) + until last_v == end1 and v == end2 + table.remove(res) + table.remove(res) + res = table.concat(res) + return 'comment',res +end + + + +return tools