convert to sensible line feeds

This commit is contained in:
Steve Donovan 2011-04-13 13:02:40 +02:00
parent 73e22a3c6c
commit 05bd15a266
7 changed files with 743 additions and 743 deletions

658
doc.lua
View File

@ -1,329 +1,329 @@
------ ------
-- Defining the ldoc document model. -- Defining the ldoc document model.
require 'pl' require 'pl'
local doc = {} local doc = {}
local tools = require 'tools' local tools = require 'tools'
local split_dotted_name = tools.split_dotted_name local split_dotted_name = tools.split_dotted_name
-- these are the basic tags known to ldoc. They come in several varieties: -- these are the basic tags known to ldoc. They come in several varieties:
-- - tags with multiple values like 'param' (TAG_MULTI) -- - tags with multiple values like 'param' (TAG_MULTI)
-- - tags which are identifiers, like 'name' (TAG_ID) -- - tags which are identifiers, like 'name' (TAG_ID)
-- - tags with a single value, like 'release' (TAG_SINGLE) -- - tags with a single value, like 'release' (TAG_SINGLE)
-- - tags which represent a type, like 'function' (TAG_TYPE) -- - tags which represent a type, like 'function' (TAG_TYPE)
local known_tags = { local known_tags = {
param = 'M', see = 'M', usage = 'M', ['return'] = 'M', field = 'M', author='M'; param = 'M', see = 'M', usage = 'M', ['return'] = 'M', field = 'M', author='M';
class = 'id', name = 'id', pragma = 'id'; class = 'id', name = 'id', pragma = 'id';
copyright = 'S', description = 'S', release = 'S'; copyright = 'S', description = 'S', release = 'S';
module = 'T', script = 'T',['function'] = 'T', table = 'T' module = 'T', script = 'T',['function'] = 'T', table = 'T'
} }
known_tags._alias = {} known_tags._alias = {}
known_tags._project_level = { known_tags._project_level = {
module = true, module = true,
script = true script = true
} }
local TAG_MULTI,TAG_ID,TAG_SINGLE,TAG_TYPE = 'M','id','S','T' 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 doc.TAG_MULTI,doc.TAG_ID,doc.TAG_SINGLE,doc.TAG_TYPE = TAG_MULTI,TAG_ID,TAG_SINGLE,TAG_TYPE
-- add a new tag. -- add a new tag.
function doc.add_tag(tag,type,project_level) function doc.add_tag(tag,type,project_level)
if not known_tags[tag] then if not known_tags[tag] then
known_tags[tag] = type known_tags[tag] = type
known_tags._project_level[tag] = project_level known_tags._project_level[tag] = project_level
end end
end end
-- add an alias to an existing tag (exposed through ldoc API) -- add an alias to an existing tag (exposed through ldoc API)
function doc.add_alias (a,tag) function doc.add_alias (a,tag)
known_tags._alias[a] = tag known_tags._alias[a] = tag
end end
-- get the tag alias value, if it exists. -- get the tag alias value, if it exists.
function doc.get_alias(tag) function doc.get_alias(tag)
return known_tags._alias[tag] return known_tags._alias[tag]
end end
-- is it a'project level' tag, such as 'module' or 'script'? -- is it a'project level' tag, such as 'module' or 'script'?
function doc.project_level(tag) function doc.project_level(tag)
return known_tags._project_level[tag] return known_tags._project_level[tag]
end end
-- we process each file, resulting in a File object, which has a list of Item objects. -- 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. -- Items can be modules, scripts ('project level') or functions, tables, etc.
-- (In the code 'module' refers to any project level tag.) -- (In the code 'module' refers to any project level tag.)
-- When the File object is finalized, we specialize some items as modules which -- When the File object is finalized, we specialize some items as modules which
-- are 'container' types containing functions and tables, etc. -- are 'container' types containing functions and tables, etc.
local File = class() local File = class()
local Item = class() local Item = class()
local Module = class(Item) -- a specialized kind of Item local Module = class(Item) -- a specialized kind of Item
doc.File = File doc.File = File
doc.Item = Item doc.Item = Item
doc.Module = Module doc.Module = Module
function File:_init(filename) function File:_init(filename)
self.filename = filename self.filename = filename
self.items = List() self.items = List()
self.modules = List() self.modules = List()
end end
function File:new_item(tags,line) function File:new_item(tags,line)
local item = Item(tags) local item = Item(tags)
self.items:append(item) self.items:append(item)
item.file = self item.file = self
item.lineno = line item.lineno = line
return item return item
end end
function File:finish() function File:finish()
local this_mod local this_mod
local items = self.items local items = self.items
for item in items:iter() do for item in items:iter() do
item:finish() item:finish()
if doc.project_level(item.type) then if doc.project_level(item.type) then
this_mod = item this_mod = item
-- if name is 'package.mod', then mod_name is 'mod' -- if name is 'package.mod', then mod_name is 'mod'
local package,mname = split_dotted_name(this_mod.name) local package,mname = split_dotted_name(this_mod.name)
if not package then if not package then
mname = this_mod.name mname = this_mod.name
package = '' package = ''
else else
package = package .. '.' package = package .. '.'
end end
self.modules:append(this_mod) self.modules:append(this_mod)
this_mod.package = package this_mod.package = package
this_mod.mod_name = mname this_mod.mod_name = mname
this_mod.kinds = ModuleMap() -- the iterator over the module contents this_mod.kinds = ModuleMap() -- the iterator over the module contents
else -- add the item to the module's item list else -- add the item to the module's item list
if this_mod then if this_mod then
-- new-style modules will have qualified names like 'mod.foo' -- new-style modules will have qualified names like 'mod.foo'
local mod,fname = split_dotted_name(item.name) local mod,fname = split_dotted_name(item.name)
-- warning for inferred unqualified names in new style modules -- warning for inferred unqualified names in new style modules
-- (retired until we handle methods like Set:unset() properly) -- (retired until we handle methods like Set:unset() properly)
if not mod and not this_mod.old_style and item.inferred then if not mod and not this_mod.old_style and item.inferred then
--item:warning(item.name .. ' is declared in global scope') --item:warning(item.name .. ' is declared in global scope')
end end
-- if that's the mod_name, then we want to only use 'foo' -- 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 if mod == this_mod.mod_name and this_mod.tags.pragma ~= 'nostrip' then
item.name = fname item.name = fname
end end
item.module = this_mod item.module = this_mod
local these_items = this_mod.items local these_items = this_mod.items
these_items.by_name[item.name] = item these_items.by_name[item.name] = item
these_items:append(item) these_items:append(item)
-- register this item with the iterator -- register this item with the iterator
this_mod.kinds:add(item,these_items) this_mod.kinds:add(item,these_items)
else else
-- must be a free-standing function (sometimes a problem...) -- must be a free-standing function (sometimes a problem...)
end end
end end
end end
end end
function Item:_init(tags) function Item:_init(tags)
self.summary = tags.summary self.summary = tags.summary
self.description = tags.description self.description = tags.description
tags.summary = nil tags.summary = nil
tags.description = nil tags.description = nil
self.tags = {} self.tags = {}
self.formal_args = tags.formal_args self.formal_args = tags.formal_args
tags.formal_args = nil tags.formal_args = nil
for tag,value in pairs(tags) do for tag,value in pairs(tags) do
local ttype = known_tags[tag] local ttype = known_tags[tag]
if ttype == TAG_MULTI then if ttype == TAG_MULTI then
if type(value) == 'string' then if type(value) == 'string' then
value = List{value} value = List{value}
end end
self.tags[tag] = value self.tags[tag] = value
elseif ttype == TAG_ID then elseif ttype == TAG_ID then
if type(value) ~= 'string' then if type(value) ~= 'string' then
-- such tags are _not_ multiple, e.g. name -- such tags are _not_ multiple, e.g. name
self:error(tag..' cannot have multiple values') self:error(tag..' cannot have multiple values')
else else
self.tags[tag] = tools.extract_identifier(value) self.tags[tag] = tools.extract_identifier(value)
end end
elseif ttype == TAG_SINGLE then elseif ttype == TAG_SINGLE then
self.tags[tag] = value self.tags[tag] = value
else else
self:warning ('unknown tag: '..tag) self:warning ('unknown tag: '..tag)
end end
end end
end end
-- preliminary processing of tags. We check for any aliases, and for tags -- preliminary processing of tags. We check for any aliases, and for tags
-- which represent types. This implements the shortcut notation. -- which represent types. This implements the shortcut notation.
function Item.check_tag(tags,tag) function Item.check_tag(tags,tag)
tag = doc.get_alias(tag) or tag tag = doc.get_alias(tag) or tag
local ttype = known_tags[tag] local ttype = known_tags[tag]
if ttype == TAG_TYPE then if ttype == TAG_TYPE then
tags.class = tag tags.class = tag
tag = 'name' tag = 'name'
end end
return tag return tag
end end
function Item:finish() function Item:finish()
local tags = self.tags local tags = self.tags
self.name = tags.name self.name = tags.name
self.type = tags.class self.type = tags.class
self.usage = tags.usage self.usage = tags.usage
tags.name = nil tags.name = nil
tags.class = nil tags.class = nil
tags.usage = nil tags.usage = nil
-- see tags are multiple, but they may also be comma-separated -- see tags are multiple, but they may also be comma-separated
if tags.see then if tags.see then
tags.see = tools.expand_comma_list(tags.see) tags.see = tools.expand_comma_list(tags.see)
end end
if doc.project_level(self.type) then if doc.project_level(self.type) then
-- we are a module, so become one! -- we are a module, so become one!
self.items = List() self.items = List()
self.items.by_name = {} self.items.by_name = {}
setmetatable(self,Module) setmetatable(self,Module)
else else
-- params are either a function's arguments, or a table's fields, etc. -- params are either a function's arguments, or a table's fields, etc.
local params local params
if self.type == 'function' then if self.type == 'function' then
params = tags.param or List() params = tags.param or List()
if tags['return'] then if tags['return'] then
self.ret = tags['return'] self.ret = tags['return']
end end
else else
params = tags.field or List() params = tags.field or List()
end end
tags.param = nil tags.param = nil
local names,comments = List(),List() local names,comments = List(),List()
for p in params:iter() do for p in params:iter() do
local name,comment = p:match('%s*([%w_%.:]+)(.*)') local name,comment = p:match('%s*([%w_%.:]+)(.*)')
names:append(name) names:append(name)
comments:append(comment) comments:append(comment)
end end
-- not all arguments may be commented -- -- not all arguments may be commented --
if self.formal_args then if self.formal_args then
-- however, ldoc allows comments in the arg list to be used -- however, ldoc allows comments in the arg list to be used
local fargs = self.formal_args local fargs = self.formal_args
for a in fargs:iter() do for a in fargs:iter() do
if not names:index(a) then if not names:index(a) then
names:append(a) names:append(a)
comments:append (fargs.comments[a] or '') comments:append (fargs.comments[a] or '')
end end
end end
end end
self.params = names self.params = names
for i,name in ipairs(self.params) do for i,name in ipairs(self.params) do
self.params[name] = comments[i] self.params[name] = comments[i]
end end
self.args = '('..self.params:join(', ')..')' self.args = '('..self.params:join(', ')..')'
end end
end end
function Item:warning(msg) function Item:warning(msg)
local name = self.file and self.file.filename local name = self.file and self.file.filename
if type(name) == 'table' then pretty.dump(name); name = '?' end if type(name) == 'table' then pretty.dump(name); name = '?' end
name = name or '?' name = name or '?'
io.stderr:write(name,':',self.lineno or '?',' ',msg,'\n') io.stderr:write(name,':',self.lineno or '?',' ',msg,'\n')
end end
-- resolving @see references. A word may be either a function in this module, -- 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. -- or a module in this package. A MOD.NAME reference is within this package.
-- Otherwise, the full qualified name must be used. -- Otherwise, the full qualified name must be used.
-- First, check whether it is already a fully qualified module name. -- First, check whether it is already a fully qualified module name.
-- Then split it and see if the module part is a qualified module -- Then split it and see if the module part is a qualified module
-- and try look up the name part in that 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, -- If this isn't successful then try prepending the current package to the reference,
-- and try to to resolve this. -- and try to to resolve this.
function Module:resolve_references(modules) function Module:resolve_references(modules)
local found = List() local found = List()
local function process_see_reference (item,see,s) local function process_see_reference (item,see,s)
local mod_ref,fun_ref,name,packmod local mod_ref,fun_ref,name,packmod
-- is this a fully qualified module name? -- is this a fully qualified module name?
local mod_ref = modules.by_name[s] local mod_ref = modules.by_name[s]
if mod_ref then return mod_ref,nil end if mod_ref then return mod_ref,nil end
local packmod,name = split_dotted_name(s) -- e.g. 'pl.utils','split' local packmod,name = split_dotted_name(s) -- e.g. 'pl.utils','split'
if packmod then -- qualified name if packmod then -- qualified name
mod_ref = modules.by_name[packmod] -- fully qualified mod name? mod_ref = modules.by_name[packmod] -- fully qualified mod name?
if not mod_ref then if not mod_ref then
mod_ref = modules.by_name[self.package..packmod] mod_ref = modules.by_name[self.package..packmod]
end end
if not mod_ref then if not mod_ref then
item:warning("module not found: "..packmod) item:warning("module not found: "..packmod)
return nil return nil
end end
fun_ref = mod_ref.items.by_name[name] fun_ref = mod_ref.items.by_name[name]
if fun_ref then return mod_ref,fun_ref if fun_ref then return mod_ref,fun_ref
else else
item:warning("function not found: "..s.." in "..mod_ref.name) item:warning("function not found: "..s.." in "..mod_ref.name)
end end
else -- plain jane name; module in this package, function in this module else -- plain jane name; module in this package, function in this module
mod_ref = modules.by_name[self.package..s] mod_ref = modules.by_name[self.package..s]
if mod_ref then return mod_ref,nil end if mod_ref then return mod_ref,nil end
fun_ref = self.items.by_name[s] fun_ref = self.items.by_name[s]
if fun_ref then return self,fun_ref if fun_ref then return self,fun_ref
else else
item:warning("function not found: "..s.." in this module") item:warning("function not found: "..s.." in this module")
end end
end end
end end
for item in self.items:iter() do for item in self.items:iter() do
local see = item.tags.see local see = item.tags.see
if see then -- this guy has @see references if see then -- this guy has @see references
item.see = List() item.see = List()
for s in see:iter() do for s in see:iter() do
local mod_ref, item_ref = process_see_reference(item,see,s) local mod_ref, item_ref = process_see_reference(item,see,s)
if mod_ref then if mod_ref then
local name = item_ref and item_ref.name or '' local name = item_ref and item_ref.name or ''
item.see:append {mod=mod_ref.name,name=name,label=s} item.see:append {mod=mod_ref.name,name=name,label=s}
found:append{item,s} found:append{item,s}
end end
end end
end end
end end
-- mark as found, so we don't waste time re-searching -- mark as found, so we don't waste time re-searching
for f in found:iter() do for f in found:iter() do
f[1].tags.see:remove_value(f[2]) f[1].tags.see:remove_value(f[2])
end end
end end
-- make a text dump of the contents of this File object. -- make a text dump of the contents of this File object.
-- The level of detail is controlled by the 'verbose' parameter. -- The level of detail is controlled by the 'verbose' parameter.
-- Primarily intended as a debugging tool. -- Primarily intended as a debugging tool.
function File:dump(verbose) function File:dump(verbose)
for mod in self.modules:iter() do for mod in self.modules:iter() do
print('Module:',mod.name,mod.summary,mod.description) print('Module:',mod.name,mod.summary,mod.description)
for item in mod.items:iter() do for item in mod.items:iter() do
item:dump(verbose) item:dump(verbose)
end end
end end
end end
function Item:dump(verbose) function Item:dump(verbose)
local tags = self.tags local tags = self.tags
local name = self.name local name = self.name
if self.type == 'function' then if self.type == 'function' then
name = name .. self.args name = name .. self.args
end end
if verbose then if verbose then
print(self.type,name,self.summary) print(self.type,name,self.summary)
print(self.description) print(self.description)
for p in self.params:iter() do for p in self.params:iter() do
print(p,self.params[p]) print(p,self.params[p])
end end
for tag, value in pairs(self.tags) do for tag, value in pairs(self.tags) do
print(tag,value) print(tag,value)
end end
else else
print('* '..name..' - '..self.summary) print('* '..name..' - '..self.summary)
end end
end end
return doc return doc

View File

@ -1,12 +1,12 @@
-- ldoc configuration file -- ldoc configuration file
title = "testmod docs" title = "testmod docs"
project = "testmod" project = "testmod"
description = [[ description = [[
This description applies to the project as a whole. This description applies to the project as a whole.
]] ]]
alias("p","param") alias("p","param")
new_type("macro","Macros") new_type("macro","Macros")

View File

@ -1,60 +1,60 @@
--------------------------- ---------------------------
-- Test module providing bonzo.dog. -- Test module providing bonzo.dog.
-- Rest is a longer description -- Rest is a longer description
-- @class module -- @class module
-- @name mod1 -- @name mod1
--- zero function. Two new ldoc features here; item types --- zero function. Two new ldoc features here; item types
-- can be used directly as tags, and aliases for tags -- can be used directly as tags, and aliases for tags
-- can be defined in config.lp. -- can be defined in config.lp.
-- @function zero_fun -- @function zero_fun
-- @p k1 first -- @p k1 first
-- @p k2 second -- @p k2 second
--- first function. Some description --- first function. Some description
-- @param p1 first parameter -- @param p1 first parameter
-- @param p2 second parameter -- @param p2 second parameter
function mod1.first_fun(p1,p2) function mod1.first_fun(p1,p2)
end end
------------------------- -------------------------
-- second function. -- second function.
-- @param ... var args! -- @param ... var args!
function mod1.second_function(...) function mod1.second_function(...)
end end
------------ ------------
-- third function. Can also provide parameter comments inline, -- third function. Can also provide parameter comments inline,
-- provided they follow this pattern. -- provided they follow this pattern.
function mod1.third_function( function mod1.third_function(
alpha, -- correction A alpha, -- correction A
beta, -- correction B beta, -- correction B
gamma -- factor C gamma -- factor C
) )
end end
----- -----
-- A useful macro. This is an example of a custom 'kind'. -- A useful macro. This is an example of a custom 'kind'.
-- @macro first_macro -- @macro first_macro
-- @see second_function -- @see second_function
---- general configuration table ---- general configuration table
-- @table config -- @table config
-- @field A alpha -- @field A alpha
-- @field B beta -- @field B beta
-- @field C gamma -- @field C gamma
mod1.config = { mod1.config = {
A = 1, A = 1,
B = 2, B = 2,
C = 3 C = 3
} }
--[[-- --[[--
Another function. Using a Lua block comment Another function. Using a Lua block comment
@param p a parameter @param p a parameter
]] ]]
function mod1.zero_function(p) function mod1.zero_function(p)
end end

View File

@ -1,62 +1,62 @@
/// A sample C extension. /// A sample C extension.
// Demonstrates using ldoc's C/C++ support. Can either use /// or /*** */ etc. // Demonstrates using ldoc's C/C++ support. Can either use /// or /*** */ etc.
// @module mylib // @module mylib
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>
// includes for Lua // includes for Lua
#include <lua.h> #include <lua.h>
#include <lauxlib.h> #include <lauxlib.h>
#include <lualib.h> #include <lualib.h>
/*** /***
Create a table with given array and hash slots. Create a table with given array and hash slots.
@function createtable @function createtable
@param narr initial array slots, default 0 @param narr initial array slots, default 0
@param nrec initial hash slots, default 0 @param nrec initial hash slots, default 0
*/ */
static int l_createtable (lua_State *L) { static int l_createtable (lua_State *L) {
int narr = luaL_optint(L,1,0); int narr = luaL_optint(L,1,0);
int nrec = luaL_optint(L,2,0); int nrec = luaL_optint(L,2,0);
lua_createtable(L,narr,nrec); lua_createtable(L,narr,nrec);
return 1; return 1;
} }
/*** /***
Solve a quadratic equation. Solve a quadratic equation.
@function solve @function solve
@param a coefficient of x^2 @param a coefficient of x^2
@param b coefficient of x @param b coefficient of x
@param c constant @param c constant
@return first root @return first root
@return second root @return second root
*/ */
static int l_solve (lua_State *L) { static int l_solve (lua_State *L) {
double a = lua_tonumber(L,1); // coeff of x*x double a = lua_tonumber(L,1); // coeff of x*x
double b = lua_tonumber(L,2); // coef of x double b = lua_tonumber(L,2); // coef of x
double c = lua_tonumber(L,3); // constant double c = lua_tonumber(L,3); // constant
double abc = b*b - 4*a*c; double abc = b*b - 4*a*c;
if (abc < 0.0) { if (abc < 0.0) {
lua_pushnil(L); lua_pushnil(L);
lua_pushstring(L,"imaginary roots!"); lua_pushstring(L,"imaginary roots!");
return 2; return 2;
} else { } else {
abc = sqrt(abc); abc = sqrt(abc);
a = 2*a; a = 2*a;
lua_pushnumber(L,(-b + abc)/a); lua_pushnumber(L,(-b + abc)/a);
lua_pushnumber(L,(+b - abc)/a); lua_pushnumber(L,(+b - abc)/a);
return 2; return 2;
} }
} }
static const luaL_reg mylib[] = { static const luaL_reg mylib[] = {
{"createtable",l_createtable}, {"createtable",l_createtable},
{"solve",l_solve}, {"solve",l_solve},
{NULL,NULL} {NULL,NULL}
}; };
int luaopen_mylib(lua_State *L) int luaopen_mylib(lua_State *L)
{ {
luaL_register (L, "mylib", mylib); luaL_register (L, "mylib", mylib);
return 1; return 1;
} }

View File

@ -1,3 +1,3 @@
project = 'md-test' project = 'md-test'
title = 'Markdown Docs' title = 'Markdown Docs'
format = 'markdown' format = 'markdown'

View File

@ -1,12 +1,12 @@
------------ ------------
-- A little old-style module -- A little old-style module
local io = io local io = io
-- we'll look for this -- we'll look for this
module 'simple' module 'simple'
-- if it were 'module (...)' then the name has to be deduced. -- if it were 'module (...)' then the name has to be deduced.
--- return the answer. --- return the answer.
function answer() function answer()
return 42 return 42
end end

530
tools.lua
View File

@ -1,265 +1,265 @@
--------- ---------
-- General utility functions for ldoc -- General utility functions for ldoc
-- @module tools -- @module tools
require 'pl' require 'pl'
local tools = {} local tools = {}
local M = tools local M = tools
local append = table.insert local append = table.insert
local lexer = require 'lexer' local lexer = require 'lexer'
local quit = utils.quit local quit = utils.quit
-- this constructs an iterator over a list of objects which returns only -- 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 -- those objects where a field has a certain value. It's used to iterate
-- only over functions or tables, etc. -- only over functions or tables, etc.
-- (something rather similar exists in LuaDoc) -- (something rather similar exists in LuaDoc)
function M.type_iterator (list,field,value) function M.type_iterator (list,field,value)
return function() return function()
local i = 1 local i = 1
return function() return function()
local val = list[i] local val = list[i]
while val and val[field] ~= value do while val and val[field] ~= value do
i = i + 1 i = i + 1
val = list[i] val = list[i]
end end
i = i + 1 i = i + 1
if val then return val end if val then return val end
end end
end end
end end
-- KindMap is used to iterate over a set of categories, called _kinds_, -- KindMap is used to iterate over a set of categories, called _kinds_,
-- and the associated iterator over all items in that category. -- and the associated iterator over all items in that category.
-- For instance, a module contains functions, tables, etc and we will -- For instance, a module contains functions, tables, etc and we will
-- want to iterate over these categories in a specified order: -- want to iterate over these categories in a specified order:
-- --
-- for kind, items in module.kinds() do -- for kind, items in module.kinds() do
-- print('kind',kind) -- print('kind',kind)
-- for item in items() do print(item.name) end -- for item in items() do print(item.name) end
-- end -- end
-- --
-- The kind is typically used as a label or a Title, so for type 'function' the -- The kind is typically used as a label or a Title, so for type 'function' the
-- kind is 'Functions' and so on. -- kind is 'Functions' and so on.
local KindMap = class() local KindMap = class()
M.KindMap = KindMap M.KindMap = KindMap
-- calling a KindMap returns an iterator. This returns the kind, the iterator -- calling a KindMap returns an iterator. This returns the kind, the iterator
-- over the items of that type, and the corresponding type. -- over the items of that type, and the corresponding type.
function KindMap:__call () function KindMap:__call ()
local i = 1 local i = 1
local klass = self.klass local klass = self.klass
return function() return function()
local kind = klass.kinds[i] local kind = klass.kinds[i]
if not kind then return nil end -- no more kinds if not kind then return nil end -- no more kinds
while not self[kind] do while not self[kind] do
i = i + 1 i = i + 1
kind = klass.kinds[i] kind = klass.kinds[i]
if not kind then return nil end if not kind then return nil end
end end
i = i + 1 i = i + 1
return kind, self[kind], klass.types_by_kind[kind] return kind, self[kind], klass.types_by_kind[kind]
end end
end end
-- called for each new item. It does not actually create separate lists, -- called for each new item. It does not actually create separate lists,
-- (although that would not break the interface) but creates iterators -- (although that would not break the interface) but creates iterators
-- for that item type if not already created. -- for that item type if not already created.
function KindMap:add (item,items) function KindMap:add (item,items)
local kname = self.klass.types_by_tag[item.type] local kname = self.klass.types_by_tag[item.type]
if not self[kname] then if not self[kname] then
self[kname] = M.type_iterator (items,'type',item.type) self[kname] = M.type_iterator (items,'type',item.type)
end end
end end
-- KindMap has a 'class constructor' which is used to modify -- KindMap has a 'class constructor' which is used to modify
-- any new base class. -- any new base class.
function KindMap._class_init (klass) function KindMap._class_init (klass)
klass.kinds = {} -- list in correct order of kinds klass.kinds = {} -- list in correct order of kinds
klass.types_by_tag = {} -- indexed by tag klass.types_by_tag = {} -- indexed by tag
klass.types_by_kind = {} -- indexed by kind klass.types_by_kind = {} -- indexed by kind
end end
function KindMap.add_kind (klass,tag,kind,subnames) function KindMap.add_kind (klass,tag,kind,subnames)
klass.types_by_tag[tag] = kind klass.types_by_tag[tag] = kind
klass.types_by_kind[kind] = {type=tag,subnames=subnames} klass.types_by_kind[kind] = {type=tag,subnames=subnames}
append(klass.kinds,kind) append(klass.kinds,kind)
end end
----- some useful utility functions ------ ----- some useful utility functions ------
function M.module_basepath() function M.module_basepath()
local lpath = List.split(package.path,';') local lpath = List.split(package.path,';')
for p in lpath:iter() do for p in lpath:iter() do
local p = path.dirname(p) local p = path.dirname(p)
if path.isabs(p) then if path.isabs(p) then
return p return p
end end
end end
end end
-- split a qualified name into the module part and the name part, -- split a qualified name into the module part and the name part,
-- e.g 'pl.utils.split' becomes 'pl.utils' and 'split' -- e.g 'pl.utils.split' becomes 'pl.utils' and 'split'
function M.split_dotted_name (s) function M.split_dotted_name (s)
local s1,s2 = path.splitext(s) local s1,s2 = path.splitext(s)
if s2=='' then return nil if s2=='' then return nil
else return s1,s2:sub(2) else return s1,s2:sub(2)
end end
end end
-- expand lists of possibly qualified identifiers -- expand lists of possibly qualified identifiers
-- given something like {'one , two.2','three.drei.drie)'} -- given something like {'one , two.2','three.drei.drie)'}
-- it will output {"one","two.2","three.drei.drie"} -- it will output {"one","two.2","three.drei.drie"}
function M.expand_comma_list (ls) function M.expand_comma_list (ls)
local new_ls = List() local new_ls = List()
for s in ls:iter() do for s in ls:iter() do
s = s:gsub('[^%.:%w]*$','') s = s:gsub('[^%.:%w]*$','')
if s:find ',' then if s:find ',' then
new_ls:extend(List.split(s,'%s*,%s*')) new_ls:extend(List.split(s,'%s*,%s*'))
else else
new_ls:append(s) new_ls:append(s)
end end
end end
return new_ls return new_ls
end end
function M.extract_identifier (value) function M.extract_identifier (value)
return value:match('([%.:_%w]+)') return value:match('([%.:_%w]+)')
end end
function M.strip (s) function M.strip (s)
return s:gsub('^%s+',''):gsub('%s+$','') return s:gsub('^%s+',''):gsub('%s+$','')
end end
function M.check_directory(d) function M.check_directory(d)
if not path.isdir(d) then if not path.isdir(d) then
lfs.mkdir(d) lfs.mkdir(d)
end end
end end
function M.check_file (f,original) function M.check_file (f,original)
if not path.exists(f) then if not path.exists(f) then
dir.copyfile(original,f) dir.copyfile(original,f)
end end
end end
function M.writefile(name,text) function M.writefile(name,text)
local ok,err = utils.writefile(name,text) local ok,err = utils.writefile(name,text)
if err then quit(err) end if err then quit(err) end
end end
function M.name_of (lpath) function M.name_of (lpath)
lpath,ext = path.splitext(lpath) lpath,ext = path.splitext(lpath)
return lpath return lpath
end end
function M.this_module_name (basename,fname) function M.this_module_name (basename,fname)
local ext local ext
if basename == '' then if basename == '' then
--quit("module(...) needs package basename") --quit("module(...) needs package basename")
return M.name_of(fname) return M.name_of(fname)
end end
basename = path.abspath(basename) basename = path.abspath(basename)
if basename:sub(-1,-1) ~= path.sep then if basename:sub(-1,-1) ~= path.sep then
basename = basename..path.sep basename = basename..path.sep
end end
local lpath,cnt = fname:gsub('^'..utils.escape(basename),'') local lpath,cnt = fname:gsub('^'..utils.escape(basename),'')
if cnt ~= 1 then quit("module(...) name deduction failed: base "..basename.." "..fname) end if cnt ~= 1 then quit("module(...) name deduction failed: base "..basename.." "..fname) end
lpath = lpath:gsub(path.sep,'.') lpath = lpath:gsub(path.sep,'.')
return M.name_of(lpath) return M.name_of(lpath)
end end
--------- lexer tools ----- --------- lexer tools -----
local tnext = lexer.skipws local tnext = lexer.skipws
local function type_of (tok) return tok[1] end local function type_of (tok) return tok[1] end
local function value_of (tok) return tok[2] end local function value_of (tok) return tok[2] end
-- This parses Lua formal argument lists. It will return a list of argument -- 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 -- names, which also has a comments field, which will contain any commments
-- following the arguments. ldoc will use these in addition to explicit -- following the arguments. ldoc will use these in addition to explicit
-- param tags. -- param tags.
function M.get_parameters (tok) function M.get_parameters (tok)
local args = List() local args = List()
args.comments = {} args.comments = {}
local ltl = lexer.get_separated_list(tok) local ltl = lexer.get_separated_list(tok)
if #ltl[1] == 0 then return args end -- no arguments if #ltl[1] == 0 then return args end -- no arguments
local function set_comment (idx,tok) local function set_comment (idx,tok)
args.comments[args[idx]] = value_of(tok) args.comments[args[idx]] = value_of(tok)
end end
for i = 1,#ltl do for i = 1,#ltl do
local tl = ltl[i] local tl = ltl[i]
if type_of(tl[1]) == 'comment' then if type_of(tl[1]) == 'comment' then
if i > 1 then set_comment(i-1,tl[1]) end if i > 1 then set_comment(i-1,tl[1]) end
if #tl > 1 then if #tl > 1 then
args:append(value_of(tl[2])) args:append(value_of(tl[2]))
end end
else else
args:append(value_of(tl[1])) args:append(value_of(tl[1]))
end end
if i == #ltl then if i == #ltl then
local last_tok = tl[#tl] local last_tok = tl[#tl]
if #tl > 1 and type_of(last_tok) == 'comment' then if #tl > 1 and type_of(last_tok) == 'comment' then
set_comment(i,last_tok) set_comment(i,last_tok)
end end
end end
end end
return args return args
end end
-- parse a Lua identifier - contains names separated by . and :. -- parse a Lua identifier - contains names separated by . and :.
function M.get_fun_name (tok) function M.get_fun_name (tok)
local res = {} local res = {}
local _,name = tnext(tok) local _,name = tnext(tok)
_,sep = tnext(tok) _,sep = tnext(tok)
while sep == '.' or sep == ':' do while sep == '.' or sep == ':' do
append(res,name) append(res,name)
append(res,sep) append(res,sep)
_,name = tnext(tok) _,name = tnext(tok)
_,sep = tnext(tok) _,sep = tnext(tok)
end end
append(res,name) append(res,name)
return table.concat(res) return table.concat(res)
end end
-- space-skipping version of token iterator -- space-skipping version of token iterator
function M.space_skip_getter(tok) function M.space_skip_getter(tok)
return function () return function ()
local t,v = tok() local t,v = tok()
while t and t == 'space' do while t and t == 'space' do
t,v = tok() t,v = tok()
end end
return t,v return t,v
end end
end end
-- an embarassing function. The PL Lua lexer does not do block comments -- 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 -- 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. -- do them properly in full-text mode, due to a ordering mistake.
-- So, we do what we can ;) -- So, we do what we can ;)
function M.grab_block_comment (v,tok,end1,end2) function M.grab_block_comment (v,tok,end1,end2)
local res = {v} local res = {v}
local t,last_v local t,last_v
repeat repeat
last_v = v last_v = v
t,v = tok() t,v = tok()
append(res,v) append(res,v)
until last_v == end1 and v == end2 until last_v == end1 and v == end2
table.remove(res) table.remove(res)
table.remove(res) table.remove(res)
res = table.concat(res) res = table.concat(res)
return 'comment',res return 'comment',res
end end
return tools return tools