mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-10-29 16:58:06 +00:00
This is an important refactor of the way documentation generation works
* Installs luarocks WITH it's executable (easy to install other rocks if necessary) * Use Lua supplied with luarocks * Create Utils/luadocumentor.bat, which works with RELATIVE PATH ! -> Everybody can generate the doc * Updated launch files accordingly
This commit is contained in:
241
Utils/luarocks/systree/share/lua/5.1/models/apimodel.lua
Normal file
241
Utils/luarocks/systree/share/lua/5.1/models/apimodel.lua
Normal file
@@ -0,0 +1,241 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2011-2012 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Simon BERNARD <sbernard@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
local M = {}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- API MODEL
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
function M._file()
|
||||
local file = {
|
||||
-- FIELDS
|
||||
tag = "file",
|
||||
name = nil, -- string
|
||||
shortdescription = "", -- string
|
||||
description = "", -- string
|
||||
types = {}, -- map from typename to type
|
||||
globalvars = {}, -- map from varname to item
|
||||
returns = {}, -- list of return
|
||||
|
||||
-- FUNCTIONS
|
||||
addtype = function (self,type)
|
||||
self.types[type.name] = type
|
||||
type.parent = self
|
||||
end,
|
||||
|
||||
mergetype = function (self,newtype,erase,erasesourcerangefield)
|
||||
local currenttype = self.types[newtype.name]
|
||||
if currenttype then
|
||||
-- merge recordtypedef
|
||||
if currenttype.tag =="recordtypedef" and newtype.tag == "recordtypedef" then
|
||||
-- merge fields
|
||||
for fieldname ,field in pairs( newtype.fields) do
|
||||
local currentfield = currenttype.fields[fieldname]
|
||||
if erase or not currentfield then
|
||||
currenttype:addfield(field)
|
||||
elseif erasesourcerangefield then
|
||||
if field.sourcerange.min and field.sourcerange.max then
|
||||
currentfield.sourcerange.min = field.sourcerange.min
|
||||
currentfield.sourcerange.max = field.sourcerange.max
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- merge descriptions and source ranges
|
||||
if erase then
|
||||
if newtype.description or newtype.description == "" then currenttype.description = newtype.description end
|
||||
if newtype.shortdescription or newtype.shortdescription == "" then currenttype.shortdescription = newtype.shortdescription end
|
||||
if newtype.sourcerange.min and newtype.sourcerange.max then
|
||||
currenttype.sourcerange.min = newtype.sourcerange.min
|
||||
currenttype.sourcerange.max = newtype.sourcerange.max
|
||||
end
|
||||
end
|
||||
-- merge functiontypedef
|
||||
elseif currenttype.tag == "functiontypedef" and newtype.tag == "functiontypedef" then
|
||||
-- merge params
|
||||
for i, param1 in ipairs(newtype.params) do
|
||||
local missing = true
|
||||
for j, param2 in ipairs(currenttype.params) do
|
||||
if param1.name == param2.name then
|
||||
missing = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if missing then
|
||||
table.insert(currenttype.params,param1)
|
||||
end
|
||||
end
|
||||
|
||||
-- merge descriptions and source ranges
|
||||
if erase then
|
||||
if newtype.description or newtype.description == "" then currenttype.description = newtype.description end
|
||||
if newtype.shortdescription or newtype.shortdescription == "" then currenttype.shortdescription = newtype.shortdescription end
|
||||
if newtype.sourcerange.min and newtype.sourcerange.max then
|
||||
currenttype.sourcerange.min = newtype.sourcerange.min
|
||||
currenttype.sourcerange.max = newtype.sourcerange.max
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
self:addtype(newtype)
|
||||
end
|
||||
end,
|
||||
|
||||
addglobalvar = function (self,item)
|
||||
self.globalvars[item.name] = item
|
||||
item.parent = self
|
||||
end,
|
||||
|
||||
moduletyperef = function (self)
|
||||
if self and self.returns[1] and self.returns[1].types[1] then
|
||||
local typeref = self.returns[1].types[1]
|
||||
return typeref
|
||||
end
|
||||
end
|
||||
}
|
||||
return file
|
||||
end
|
||||
|
||||
function M._recordtypedef(name)
|
||||
local recordtype = {
|
||||
-- FIELDS
|
||||
tag = "recordtypedef",
|
||||
name = name, -- string (mandatory)
|
||||
shortdescription = "", -- string
|
||||
description = "", -- string
|
||||
fields = {}, -- map from fieldname to field
|
||||
sourcerange = {min=0,max=0},
|
||||
|
||||
-- FUNCTIONS
|
||||
addfield = function (self,field)
|
||||
self.fields[field.name] = field
|
||||
field.parent = self
|
||||
end
|
||||
}
|
||||
return recordtype
|
||||
end
|
||||
|
||||
function M._functiontypedef(name)
|
||||
return {
|
||||
tag = "functiontypedef",
|
||||
name = name, -- string (mandatory)
|
||||
shortdescription = "", -- string
|
||||
description = "", -- string
|
||||
params = {}, -- list of parameter
|
||||
returns = {} -- list of return
|
||||
}
|
||||
end
|
||||
|
||||
function M._parameter(name)
|
||||
return {
|
||||
tag = "parameter",
|
||||
name = name, -- string (mandatory)
|
||||
description = "", -- string
|
||||
type = nil -- typeref (external or internal or primitive typeref)
|
||||
}
|
||||
end
|
||||
|
||||
function M._item(name)
|
||||
return {
|
||||
-- FIELDS
|
||||
tag = "item",
|
||||
name = name, -- string (mandatory)
|
||||
shortdescription = "", -- string
|
||||
description = "", -- string
|
||||
type = nil, -- typeref (external or internal or primitive typeref)
|
||||
occurrences = {}, -- list of identifier (see internalmodel)
|
||||
sourcerange = {min=0, max=0},
|
||||
|
||||
-- This is A TRICK
|
||||
-- This value is ALWAYS nil, except for internal purposes (short references).
|
||||
external = nil,
|
||||
|
||||
-- FUNCTIONS
|
||||
addoccurence = function (self,occ)
|
||||
table.insert(self.occurrences,occ)
|
||||
occ.definition = self
|
||||
end,
|
||||
|
||||
resolvetype = function (self,file)
|
||||
if self and self.type then
|
||||
if self.type.tag =="internaltyperef" then
|
||||
-- if file is not given try to retrieve it.
|
||||
if not file then
|
||||
if self.parent and self.parent.tag == 'recordtypedef' then
|
||||
file = self.parent.parent
|
||||
elseif self.parent.tag == 'file' then
|
||||
file = self.parent
|
||||
end
|
||||
end
|
||||
if file then return file.types[self.type.typename] end
|
||||
elseif self.type.tag =="inlinetyperef" then
|
||||
return self.type.def
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
function M._externaltypref(modulename, typename)
|
||||
return {
|
||||
tag = "externaltyperef",
|
||||
modulename = modulename, -- string
|
||||
typename = typename -- string
|
||||
}
|
||||
end
|
||||
|
||||
function M._internaltyperef(typename)
|
||||
return {
|
||||
tag = "internaltyperef",
|
||||
typename = typename -- string
|
||||
}
|
||||
end
|
||||
|
||||
function M._primitivetyperef(typename)
|
||||
return {
|
||||
tag = "primitivetyperef",
|
||||
typename = typename -- string
|
||||
}
|
||||
end
|
||||
|
||||
function M._moduletyperef(modulename,returnposition)
|
||||
return {
|
||||
tag = "moduletyperef",
|
||||
modulename = modulename, -- string
|
||||
returnposition = returnposition -- number
|
||||
}
|
||||
end
|
||||
|
||||
function M._exprtyperef(expression,returnposition)
|
||||
return {
|
||||
tag = "exprtyperef",
|
||||
expression = expression, -- expression (see internal model)
|
||||
returnposition = returnposition -- number
|
||||
}
|
||||
end
|
||||
|
||||
function M._inlinetyperef(definition)
|
||||
return {
|
||||
tag = "inlinetyperef",
|
||||
def = definition, -- expression (see internal model)
|
||||
|
||||
}
|
||||
end
|
||||
|
||||
function M._return(description)
|
||||
return {
|
||||
tag = "return",
|
||||
description = description or "", -- string
|
||||
types = {} -- list of typref (external or internal or primitive typeref)
|
||||
}
|
||||
end
|
||||
return M
|
||||
459
Utils/luarocks/systree/share/lua/5.1/models/apimodelbuilder.lua
Normal file
459
Utils/luarocks/systree/share/lua/5.1/models/apimodelbuilder.lua
Normal file
@@ -0,0 +1,459 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2011-2012 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Simon BERNARD <sbernard@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
local apimodel = require "models.apimodel"
|
||||
local ldp = require "models.ldparser"
|
||||
local Q = require "metalua.treequery"
|
||||
|
||||
local M = {}
|
||||
|
||||
local handledcomments={} -- cache to know the comment already handled
|
||||
|
||||
----
|
||||
-- UTILITY METHODS
|
||||
local primitivetypes = {
|
||||
['boolean'] = true,
|
||||
['function'] = true,
|
||||
['nil'] = true,
|
||||
['number'] = true,
|
||||
['string'] = true,
|
||||
['table'] = true,
|
||||
['thread'] = true,
|
||||
['userdata'] = true
|
||||
}
|
||||
|
||||
-- get or create the typedef with the name "name"
|
||||
local function gettypedef(_file,name,kind,sourcerangemin,sourcerangemax)
|
||||
local kind = kind or "recordtypedef"
|
||||
local _typedef = _file.types[name]
|
||||
if _typedef then
|
||||
if _typedef.tag == kind then return _typedef end
|
||||
else
|
||||
if kind == "recordtypedef" and name ~= "global" then
|
||||
local _recordtypedef = apimodel._recordtypedef(name)
|
||||
|
||||
-- define sourcerange
|
||||
_recordtypedef.sourcerange.min = sourcerangemin
|
||||
_recordtypedef.sourcerange.max = sourcerangemax
|
||||
|
||||
-- add to file if a name is defined
|
||||
if _recordtypedef.name then _file:addtype(_recordtypedef) end
|
||||
return _recordtypedef
|
||||
elseif kind == "functiontypedef" then
|
||||
-- TODO support function
|
||||
return nil
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
-- create a typeref from the typref doc_tag
|
||||
local function createtyperef(dt_typeref,_file,sourcerangemin,sourcerangemax)
|
||||
local _typeref
|
||||
if dt_typeref.tag == "typeref" then
|
||||
if dt_typeref.module then
|
||||
-- manage external type
|
||||
_typeref = apimodel._externaltypref()
|
||||
_typeref.modulename = dt_typeref.module
|
||||
_typeref.typename = dt_typeref.type
|
||||
else
|
||||
if primitivetypes[dt_typeref.type] then
|
||||
-- manage primitive type
|
||||
_typeref = apimodel._primitivetyperef()
|
||||
_typeref.typename = dt_typeref.type
|
||||
else
|
||||
-- manage internal type
|
||||
_typeref = apimodel._internaltyperef()
|
||||
_typeref.typename = dt_typeref.type
|
||||
if _file then
|
||||
gettypedef(_file, _typeref.typename, "recordtypedef", sourcerangemin,sourcerangemax)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return _typeref
|
||||
end
|
||||
|
||||
-- create a return from the return doc_tag
|
||||
local function createreturn(dt_return,_file,sourcerangemin,sourcerangemax)
|
||||
local _return = apimodel._return()
|
||||
|
||||
_return.description = dt_return.description
|
||||
|
||||
-- manage typeref
|
||||
if dt_return.types then
|
||||
for _, dt_typeref in ipairs(dt_return.types) do
|
||||
local _typeref = createtyperef(dt_typeref,_file,sourcerangemin,sourcerangemax)
|
||||
if _typeref then
|
||||
table.insert(_return.types,_typeref)
|
||||
end
|
||||
end
|
||||
end
|
||||
return _return
|
||||
end
|
||||
|
||||
-- create a item from the field doc_tag
|
||||
local function createfield(dt_field,_file,sourcerangemin,sourcerangemax)
|
||||
local _item = apimodel._item(dt_field.name)
|
||||
|
||||
if dt_field.shortdescription then
|
||||
_item.shortdescription = dt_field.shortdescription
|
||||
_item.description = dt_field.description
|
||||
else
|
||||
_item.shortdescription = dt_field.description
|
||||
end
|
||||
|
||||
-- manage typeref
|
||||
local dt_typeref = dt_field.type
|
||||
if dt_typeref then
|
||||
_item.type = createtyperef(dt_typeref,_file,sourcerangemin,sourcerangemax)
|
||||
end
|
||||
return _item
|
||||
end
|
||||
|
||||
-- create a param from the param doc_tag
|
||||
local function createparam(dt_param,_file,sourcerangemin,sourcerangemax)
|
||||
if not dt_param.name then return nil end
|
||||
|
||||
local _parameter = apimodel._parameter(dt_param.name)
|
||||
_parameter.description = dt_param.description
|
||||
|
||||
-- manage typeref
|
||||
local dt_typeref = dt_param.type
|
||||
if dt_typeref then
|
||||
_parameter.type = createtyperef(dt_typeref,_file,sourcerangemin,sourcerangemax)
|
||||
end
|
||||
return _parameter
|
||||
end
|
||||
|
||||
-- get or create the typedef with the name "name"
|
||||
function M.additemtoparent(_file,_item,scope,sourcerangemin,sourcerangemax)
|
||||
if scope and not scope.module then
|
||||
if _item.name then
|
||||
if scope.type == "global" then
|
||||
_file:addglobalvar(_item)
|
||||
else
|
||||
local _recordtypedef = gettypedef (_file, scope.type ,"recordtypedef",sourcerangemin,sourcerangemax)
|
||||
_recordtypedef:addfield(_item)
|
||||
end
|
||||
else
|
||||
-- if no item name precise we store the scope in the item to be able to add it to the right parent later
|
||||
_item.scope = scope
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Function type counter
|
||||
local i = 0
|
||||
|
||||
-- Reset function type counter
|
||||
local function resetfunctiontypeidgenerator()
|
||||
i = 0
|
||||
end
|
||||
|
||||
-- Provides an unique index for a function type
|
||||
local function generatefunctiontypeid()
|
||||
i = i + 1
|
||||
return i
|
||||
end
|
||||
|
||||
-- generate a function type name
|
||||
local function generatefunctiontypename(_functiontypedef)
|
||||
local name = {"__"}
|
||||
if _functiontypedef.returns and _functiontypedef.returns[1] then
|
||||
local ret = _functiontypedef.returns[1]
|
||||
for _, type in ipairs(ret.types) do
|
||||
if type.typename then
|
||||
if type.modulename then
|
||||
table.insert(name,type.modulename)
|
||||
end
|
||||
table.insert(name,"#")
|
||||
table.insert(name,type.typename)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
table.insert(name,"=")
|
||||
if _functiontypedef.params then
|
||||
for _, param in ipairs(_functiontypedef.params) do
|
||||
local type = param.type
|
||||
if type then
|
||||
if type.typename then
|
||||
if type.modulename then
|
||||
table.insert(name,type.modulename)
|
||||
end
|
||||
table.insert(name,"#")
|
||||
table.insert(name,type.typename)
|
||||
else
|
||||
table.insert(name,"#unknown")
|
||||
end
|
||||
end
|
||||
table.insert(name,"[")
|
||||
table.insert(name,param.name)
|
||||
table.insert(name,"]")
|
||||
end
|
||||
end
|
||||
table.insert(name,"__")
|
||||
table.insert(name, generatefunctiontypeid())
|
||||
return table.concat(name)
|
||||
end
|
||||
|
||||
|
||||
|
||||
------------------------------------------------------
|
||||
-- create the module api
|
||||
function M.createmoduleapi(ast,modulename)
|
||||
|
||||
-- Initialise function type naming
|
||||
resetfunctiontypeidgenerator()
|
||||
|
||||
local _file = apimodel._file()
|
||||
|
||||
local _comment2apiobj = {}
|
||||
|
||||
local function handlecomment(comment)
|
||||
|
||||
-- Extract information from tagged comments
|
||||
local parsedcomment = ldp.parse(comment[1])
|
||||
if not parsedcomment then return nil end
|
||||
|
||||
-- Get tags from the languages
|
||||
local regulartags = parsedcomment.tags
|
||||
|
||||
-- Will contain last API object generated from comments
|
||||
local _lastapiobject
|
||||
|
||||
-- if comment is an ld comment
|
||||
if regulartags then
|
||||
-- manage "module" comment
|
||||
if regulartags["module"] then
|
||||
-- get name
|
||||
_file.name = regulartags["module"][1].name or modulename
|
||||
_lastapiobject = _file
|
||||
|
||||
-- manage descriptions
|
||||
_file.shortdescription = parsedcomment.shortdescription
|
||||
_file.description = parsedcomment.description
|
||||
|
||||
local sourcerangemin = comment.lineinfo.first.offset
|
||||
local sourcerangemax = comment.lineinfo.last.offset
|
||||
|
||||
-- manage returns
|
||||
if regulartags ["return"] then
|
||||
for _, dt_return in ipairs(regulartags ["return"]) do
|
||||
local _return = createreturn(dt_return,_file,sourcerangemin,sourcerangemax)
|
||||
table.insert(_file.returns,_return)
|
||||
end
|
||||
end
|
||||
-- if no returns on module create a defaultreturn of type #modulename
|
||||
if #_file.returns == 0 and _file.name then
|
||||
-- create internal type ref
|
||||
local _typeref = apimodel._internaltyperef()
|
||||
_typeref.typename = _file.name
|
||||
|
||||
-- create return
|
||||
local _return = apimodel._return()
|
||||
table.insert(_return.types,_typeref)
|
||||
|
||||
-- add return
|
||||
table.insert(_file.returns,_return)
|
||||
|
||||
--create recordtypedef is not define
|
||||
gettypedef(_file,_typeref.typename,"recordtypedef",sourcerangemin,sourcerangemax)
|
||||
end
|
||||
-- manage "type" comment
|
||||
elseif regulartags["type"] and regulartags["type"][1].name ~= "global" then
|
||||
local dt_type = regulartags["type"][1];
|
||||
-- create record type if it doesn't exist
|
||||
local sourcerangemin = comment.lineinfo.first.offset
|
||||
local sourcerangemax = comment.lineinfo.last.offset
|
||||
local _recordtypedef = gettypedef (_file, dt_type.name ,"recordtypedef",sourcerangemin,sourcerangemax)
|
||||
_lastapiobject = _recordtypedef
|
||||
|
||||
-- re-set sourcerange in case the type was created before the type tag
|
||||
_recordtypedef.sourcerange.min = sourcerangemin
|
||||
_recordtypedef.sourcerange.max = sourcerangemax
|
||||
|
||||
-- manage description
|
||||
_recordtypedef.shortdescription = parsedcomment.shortdescription
|
||||
_recordtypedef.description = parsedcomment.description
|
||||
|
||||
-- manage fields
|
||||
if regulartags["field"] then
|
||||
for _, dt_field in ipairs(regulartags["field"]) do
|
||||
local _item = createfield(dt_field,_file,sourcerangemin,sourcerangemax)
|
||||
-- define sourcerange only if we create it
|
||||
_item.sourcerange.min = sourcerangemin
|
||||
_item.sourcerange.max = sourcerangemax
|
||||
if _item then _recordtypedef:addfield(_item) end
|
||||
end
|
||||
end
|
||||
elseif regulartags["field"] then
|
||||
local dt_field = regulartags["field"][1]
|
||||
|
||||
-- create item
|
||||
local sourcerangemin = comment.lineinfo.first.offset
|
||||
local sourcerangemax = comment.lineinfo.last.offset
|
||||
local _item = createfield(dt_field,_file,sourcerangemin,sourcerangemax)
|
||||
_item.shortdescription = parsedcomment.shortdescription
|
||||
_item.description = parsedcomment.description
|
||||
_lastapiobject = _item
|
||||
|
||||
-- define sourcerange
|
||||
_item.sourcerange.min = sourcerangemin
|
||||
_item.sourcerange.max = sourcerangemax
|
||||
|
||||
-- add item to its parent
|
||||
local scope = regulartags["field"][1].parent
|
||||
M.additemtoparent(_file,_item,scope,sourcerangemin,sourcerangemax)
|
||||
elseif regulartags["function"] or regulartags["param"] or regulartags["return"] then
|
||||
-- create item
|
||||
local _item = apimodel._item()
|
||||
_item.shortdescription = parsedcomment.shortdescription
|
||||
_item.description = parsedcomment.description
|
||||
_lastapiobject = _item
|
||||
|
||||
-- set name
|
||||
if regulartags["function"] then _item.name = regulartags["function"][1].name end
|
||||
|
||||
-- define sourcerange
|
||||
local sourcerangemin = comment.lineinfo.first.offset
|
||||
local sourcerangemax = comment.lineinfo.last.offset
|
||||
_item.sourcerange.min = sourcerangemin
|
||||
_item.sourcerange.max = sourcerangemax
|
||||
|
||||
|
||||
-- create function type
|
||||
local _functiontypedef = apimodel._functiontypedef()
|
||||
_functiontypedef.shortdescription = parsedcomment.shortdescription
|
||||
_functiontypedef.description = parsedcomment.description
|
||||
|
||||
|
||||
-- manage params
|
||||
if regulartags["param"] then
|
||||
for _, dt_param in ipairs(regulartags["param"]) do
|
||||
local _param = createparam(dt_param,_file,sourcerangemin,sourcerangemax)
|
||||
table.insert(_functiontypedef.params,_param)
|
||||
end
|
||||
end
|
||||
|
||||
-- manage returns
|
||||
if regulartags["return"] then
|
||||
for _, dt_return in ipairs(regulartags["return"]) do
|
||||
local _return = createreturn(dt_return,_file,sourcerangemin,sourcerangemax)
|
||||
table.insert(_functiontypedef.returns,_return)
|
||||
end
|
||||
end
|
||||
|
||||
-- add type name
|
||||
_functiontypedef.name = generatefunctiontypename(_functiontypedef)
|
||||
_file:addtype(_functiontypedef)
|
||||
|
||||
-- create ref to this type
|
||||
local _internaltyperef = apimodel._internaltyperef()
|
||||
_internaltyperef.typename = _functiontypedef.name
|
||||
_item.type=_internaltyperef
|
||||
|
||||
-- add item to its parent
|
||||
local sourcerangemin = comment.lineinfo.first.offset
|
||||
local sourcerangemax = comment.lineinfo.last.offset
|
||||
local scope = (regulartags["function"] and regulartags["function"][1].parent) or nil
|
||||
M.additemtoparent(_file,_item,scope,sourcerangemin,sourcerangemax)
|
||||
end
|
||||
end
|
||||
|
||||
-- when we could not know which type of api object it is, we suppose this is an item
|
||||
if not _lastapiobject then
|
||||
_lastapiobject = apimodel._item()
|
||||
_lastapiobject.shortdescription = parsedcomment.shortdescription
|
||||
_lastapiobject.description = parsedcomment.description
|
||||
_lastapiobject.sourcerange.min = comment.lineinfo.first.offset
|
||||
_lastapiobject.sourcerange.max = comment.lineinfo.last.offset
|
||||
end
|
||||
|
||||
--
|
||||
-- Store user defined tags
|
||||
--
|
||||
local thirdtags = parsedcomment and parsedcomment.unknowntags
|
||||
if thirdtags then
|
||||
-- Define a storage index for user defined tags on current API element
|
||||
if not _lastapiobject.metadata then _lastapiobject.metadata = {} end
|
||||
|
||||
-- Loop over user defined tags
|
||||
for usertag, taglist in pairs(thirdtags) do
|
||||
if not _lastapiobject.metadata[ usertag ] then
|
||||
_lastapiobject.metadata[ usertag ] = {
|
||||
tag = usertag
|
||||
}
|
||||
end
|
||||
for _, tag in ipairs( taglist ) do
|
||||
table.insert(_lastapiobject.metadata[usertag], tag)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- if we create an api object linked it to
|
||||
_comment2apiobj[comment] =_lastapiobject
|
||||
end
|
||||
|
||||
local function parsecomment(node, parent, ...)
|
||||
-- check for comments before this node
|
||||
if node.lineinfo and node.lineinfo.first.comments then
|
||||
local comments = node.lineinfo.first.comments
|
||||
-- check all comments
|
||||
for _,comment in ipairs(comments) do
|
||||
-- if not already handled
|
||||
if not handledcomments[comment] then
|
||||
handlecomment(comment)
|
||||
handledcomments[comment]=true
|
||||
end
|
||||
end
|
||||
end
|
||||
-- check for comments after this node
|
||||
if node.lineinfo and node.lineinfo.last.comments then
|
||||
local comments = node.lineinfo.last.comments
|
||||
-- check all comments
|
||||
for _,comment in ipairs(comments) do
|
||||
-- if not already handled
|
||||
if not handledcomments[comment] then
|
||||
handlecomment(comment)
|
||||
handledcomments[comment]=true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Q(ast):filter(function(x) return x.tag~=nil end):foreach(parsecomment)
|
||||
return _file, _comment2apiobj
|
||||
end
|
||||
|
||||
|
||||
function M.extractlocaltype ( commentblock,_file)
|
||||
if not commentblock then return nil end
|
||||
|
||||
local stringcomment = commentblock[1]
|
||||
|
||||
local parsedtag = ldp.parseinlinecomment(stringcomment)
|
||||
if parsedtag then
|
||||
local sourcerangemin = commentblock.lineinfo.first.offset
|
||||
local sourcerangemax = commentblock.lineinfo.last.offset
|
||||
|
||||
return createtyperef(parsedtag,_file,sourcerangemin,sourcerangemax), parsedtag.description
|
||||
end
|
||||
|
||||
return nil, stringcomment
|
||||
end
|
||||
|
||||
M.generatefunctiontypename = generatefunctiontypename
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,65 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2012 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Kevin KIN-FOO <kkinfoo@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
local M = {}
|
||||
|
||||
function M._internalcontent()
|
||||
return {
|
||||
content = nil, -- block
|
||||
unknownglobalvars = {}, -- list of item
|
||||
tag = "MInternalContent"
|
||||
}
|
||||
end
|
||||
|
||||
function M._block()
|
||||
return {
|
||||
content = {}, -- list of expr (identifier, index, call, invoke, block)
|
||||
localvars = {}, -- list of {var=item, scope ={min,max}}
|
||||
sourcerange = {min=0,max=0},
|
||||
tag = "MBlock"
|
||||
}
|
||||
end
|
||||
|
||||
function M._identifier()
|
||||
return {
|
||||
definition = nil, -- item
|
||||
sourcerange = {min=0,max=0},
|
||||
tag = "MIdentifier"
|
||||
}
|
||||
end
|
||||
|
||||
function M._index(key, value)
|
||||
return {
|
||||
left= key, -- expr (identifier, index, call, invoke, block)
|
||||
right= value, -- string
|
||||
sourcerange = {min=0,max=0},
|
||||
tag = "MIndex"
|
||||
}
|
||||
end
|
||||
|
||||
function M._call(funct)
|
||||
return {
|
||||
func = funct, -- expr (identifier, index, call, invoke, block)
|
||||
sourcerange = {min=0,max=0},
|
||||
tag = "MCall"
|
||||
}
|
||||
end
|
||||
|
||||
function M._invoke(name, expr)
|
||||
return {
|
||||
functionname = name, -- string
|
||||
record = expr, -- expr (identifier, index, call, invoke, block)
|
||||
sourcerange = {min=0,max=0},
|
||||
tag = "MInvoke"
|
||||
}
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,861 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2011-2012 Sierra Wireless.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Simon BERNARD <sbernard@sierrawireless.com>
|
||||
-- - initial API and implementation and initial documentation
|
||||
--------------------------------------------------------------------------------
|
||||
-{ extension ('match', ...) }
|
||||
|
||||
local Q = require 'metalua.treequery'
|
||||
|
||||
local internalmodel = require 'models.internalmodel'
|
||||
local apimodel = require 'models.apimodel'
|
||||
local apimodelbuilder = require 'models.apimodelbuilder'
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Analyzes an AST and returns two tables
|
||||
-- * `locals`, which associates `Id{ } nodes which create a local variable
|
||||
-- to a list of the `Id{ } occurrence nodes of that variable;
|
||||
-- * `globals` which associates variable names to occurrences of
|
||||
-- global variables having that name.
|
||||
function bindings(ast)
|
||||
local locals, globals = { }, { }
|
||||
local function f(id, ...)
|
||||
local name = id[1]
|
||||
if Q.is_binder(id, ...) then
|
||||
local binder = ... -- parent is the binder
|
||||
locals[binder] = locals[binder] or { }
|
||||
locals[binder][name]={ }
|
||||
else
|
||||
local _, binder = Q.get_binder(id, ...)
|
||||
if binder then -- this is a local
|
||||
table.insert(locals[binder][name], id)
|
||||
else
|
||||
local g = globals[name]
|
||||
if g then table.insert(g, id) else globals[name]={id} end
|
||||
end
|
||||
end
|
||||
end
|
||||
Q(ast) :filter('Id') :foreach(f)
|
||||
return locals, globals
|
||||
end
|
||||
|
||||
-- --------------------------------------
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- return the comment linked before to this node
|
||||
-- ----------------------------------------------------------
|
||||
local function getlinkedcommentbefore(node)
|
||||
local function _getlinkedcomment(node,line)
|
||||
if node and node.lineinfo and node.lineinfo.first.line == line then
|
||||
-- get the last comment before (the nearest of code)
|
||||
local comments = node.lineinfo.first.comments
|
||||
local comment = comments and comments[#comments]
|
||||
if comment and comment.lineinfo.last.line == line-1 then
|
||||
-- ignore the comment if there are code before on the same line
|
||||
if node.lineinfo.first.facing and (node.lineinfo.first.facing.line ~= comment.lineinfo.first.line) then
|
||||
return comment
|
||||
end
|
||||
else
|
||||
return _getlinkedcomment(node.parent,line)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
if node.lineinfo and node.lineinfo.first.line then
|
||||
return _getlinkedcomment(node,node.lineinfo.first.line)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- return the comment linked after to this node
|
||||
-- ----------------------------------------------------------
|
||||
local function getlinkedcommentafter(node)
|
||||
local function _getlinkedcomment(node,line)
|
||||
if node and node.lineinfo and node.lineinfo.last.line == line then
|
||||
-- get the first comment after (the nearest of code)
|
||||
local comments = node.lineinfo.last.comments
|
||||
local comment = comments and comments[1]
|
||||
if comment and comment.lineinfo.first.line == line then
|
||||
return comment
|
||||
else
|
||||
return _getlinkedcomment(node.parent,line)
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
if node.lineinfo and node.lineinfo.last.line then
|
||||
return _getlinkedcomment(node,node.lineinfo.last.line)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- return true if this node is a block for the internal representation
|
||||
-- ----------------------------------------------------------
|
||||
local supported_b = {
|
||||
Function = true,
|
||||
Do = true,
|
||||
While = true,
|
||||
Fornum = true,
|
||||
Forin = true,
|
||||
Repeat = true,
|
||||
}
|
||||
local function supportedblock(node, parent)
|
||||
return supported_b[ node.tag ] or
|
||||
(parent and parent.tag == "If" and node.tag == nil)
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- create a block from the metalua node
|
||||
-- ----------------------------------------------------------
|
||||
local function createblock(block, parent)
|
||||
local _block = internalmodel._block()
|
||||
match block with
|
||||
| `Function{param, body}
|
||||
| `Do{...}
|
||||
| `Fornum {identifier, min, max, body}
|
||||
| `Forin {identifiers, exprs, body}
|
||||
| `Repeat {body, expr} ->
|
||||
_block.sourcerange.min = block.lineinfo.first.offset
|
||||
_block.sourcerange.max = block.lineinfo.last.offset
|
||||
| `While {expr, body} ->
|
||||
_block.sourcerange.min = body.lineinfo.first.facing.offset
|
||||
_block.sourcerange.max = body.lineinfo.last.facing.offset
|
||||
| _ ->
|
||||
if parent and parent.tag == "If" and block.tag == nil then
|
||||
_block.sourcerange.min = block.lineinfo.first.facing.offset
|
||||
_block.sourcerange.max = block.lineinfo.last.facing.offset
|
||||
end
|
||||
end
|
||||
return _block
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- return true if this node is a expression in the internal representation
|
||||
-- ----------------------------------------------------------
|
||||
local supported_e = {
|
||||
Index = true,
|
||||
Id = true,
|
||||
Call = true,
|
||||
Invoke = true
|
||||
}
|
||||
local function supportedexpr(node)
|
||||
return supported_e[ node.tag ]
|
||||
end
|
||||
|
||||
local idto_block = {} -- cache from metalua id to internal model block
|
||||
local idto_identifier = {} -- cache from metalua id to internal model indentifier
|
||||
local expreto_expression = {} -- cache from metalua expression to internal model expression
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- create an expression from a metalua node
|
||||
-- ----------------------------------------------------------
|
||||
local function createexpr(expr,_block)
|
||||
local _expr = nil
|
||||
|
||||
match expr with
|
||||
| `Id { name } ->
|
||||
-- we store the block which hold this node
|
||||
-- to be able to define
|
||||
idto_block[expr]= _block
|
||||
|
||||
-- if expr has not line info, it means expr has no representation in the code
|
||||
-- so we don't need it.
|
||||
if not expr.lineinfo then return nil end
|
||||
|
||||
-- create identifier
|
||||
local _identifier = internalmodel._identifier()
|
||||
idto_identifier[expr]= _identifier
|
||||
_expr = _identifier
|
||||
| `Index { innerexpr, `String{fieldname} } ->
|
||||
if not expr.lineinfo then return nil end
|
||||
-- create index
|
||||
local _expression = createexpr(innerexpr,_block)
|
||||
if _expression then _expr = internalmodel._index(_expression,fieldname) end
|
||||
| `Call{innerexpr, ...} ->
|
||||
if not expr.lineinfo then return nil end
|
||||
-- create call
|
||||
local _expression = createexpr(innerexpr,_block)
|
||||
if _expression then _expr = internalmodel._call(_expression) end
|
||||
| `Invoke{innerexpr,`String{functionname},...} ->
|
||||
if not expr.lineinfo then return nil end
|
||||
-- create invoke
|
||||
local _expression = createexpr(innerexpr,_block)
|
||||
if _expression then _expr = internalmodel._invoke(functionname,_expression) end
|
||||
| _ ->
|
||||
end
|
||||
|
||||
if _expr then
|
||||
_expr.sourcerange.min = expr.lineinfo.first.offset
|
||||
_expr.sourcerange.max = expr.lineinfo.last.offset
|
||||
|
||||
expreto_expression[expr] = _expr
|
||||
end
|
||||
|
||||
return _expr
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- create block and expression node
|
||||
-- ----------------------------------------------------------
|
||||
local function createtreestructure(ast)
|
||||
-- create internal content
|
||||
local _internalcontent = internalmodel._internalcontent()
|
||||
|
||||
-- create root block
|
||||
local _block = internalmodel._block()
|
||||
local _blocks = { _block }
|
||||
_block.sourcerange.min = ast.lineinfo.first.facing.offset
|
||||
-- TODO remove the math.max when we support partial AST
|
||||
_block.sourcerange.max = math.max(ast.lineinfo.last.facing.offset, 10000)
|
||||
|
||||
_internalcontent.content = _block
|
||||
|
||||
-- visitor function (down)
|
||||
local function down (node,parent)
|
||||
if supportedblock(node,parent) then
|
||||
-- create the block
|
||||
local _block = createblock(node,parent)
|
||||
-- add it to parent block
|
||||
table.insert(_blocks[#_blocks].content, _block)
|
||||
-- enqueue the last block to know the "current" block
|
||||
table.insert(_blocks,_block)
|
||||
elseif supportedexpr(node) then
|
||||
-- we handle expression only if it was not already do
|
||||
if not expreto_expression[node] then
|
||||
-- create expr
|
||||
local _expression = createexpr(node,_blocks[#_blocks])
|
||||
-- add it to parent block
|
||||
if _expression then
|
||||
table.insert(_blocks[#_blocks].content, _expression)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- visitor function (up)
|
||||
local function up (node, parent)
|
||||
if supportedblock(node,parent) then
|
||||
-- dequeue the last block to know the "current" block
|
||||
table.remove(_blocks,#_blocks)
|
||||
end
|
||||
end
|
||||
|
||||
-- visit ast and build internal model
|
||||
Q(ast):foreach(down,up)
|
||||
|
||||
return _internalcontent
|
||||
end
|
||||
|
||||
local getitem
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- create the type from the node and position
|
||||
-- ----------------------------------------------------------
|
||||
local function createtype(node,position,comment2apiobj,file)
|
||||
-- create module type ref
|
||||
match node with
|
||||
| `Call{ `Id "require", `String {modulename}} ->
|
||||
return apimodel._moduletyperef(modulename,position)
|
||||
| `Function {params, body} ->
|
||||
-- create the functiontypedef from code
|
||||
local _functiontypedef = apimodel._functiontypedef()
|
||||
for _, p in ipairs(params) do
|
||||
-- create parameters
|
||||
local paramname
|
||||
if p.tag=="Dots" then
|
||||
paramname = "..."
|
||||
else
|
||||
paramname = p[1]
|
||||
end
|
||||
local _param = apimodel._parameter(paramname)
|
||||
table.insert(_functiontypedef.params,_param)
|
||||
end
|
||||
_functiontypedef.name = "___" -- no name for inline type
|
||||
|
||||
return apimodel._inlinetyperef(_functiontypedef)
|
||||
| `String {value} ->
|
||||
local typeref = apimodel._primitivetyperef("string")
|
||||
return typeref
|
||||
| `Number {value} ->
|
||||
local typeref = apimodel._primitivetyperef("number")
|
||||
return typeref
|
||||
| `True | `False ->
|
||||
local typeref = apimodel._primitivetyperef("boolean")
|
||||
return typeref
|
||||
| `Table {...} ->
|
||||
-- create recordtypedef from code
|
||||
local _recordtypedef = apimodel._recordtypedef("___") -- no name for inline type
|
||||
-- for each element of the table
|
||||
for i=1,select("#", ...) do
|
||||
local pair = select(i, ...)
|
||||
-- if this is a pair we create a new item in the type
|
||||
if pair.tag == "Pair" then
|
||||
-- create an item
|
||||
local _item = getitem(pair,nil, comment2apiobj,file)
|
||||
if _item then
|
||||
_recordtypedef:addfield(_item)
|
||||
end
|
||||
end
|
||||
end
|
||||
return apimodel._inlinetyperef(_recordtypedef)
|
||||
| _ ->
|
||||
end
|
||||
-- if node is an expression supported
|
||||
local supportedexpr = expreto_expression[node]
|
||||
if supportedexpr then
|
||||
-- create expression type ref
|
||||
return apimodel._exprtyperef(supportedexpr,position)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local function completeapidoctype(apidoctype,itemname,init,file,comment2apiobj)
|
||||
if not apidoctype.name then
|
||||
apidoctype.name = itemname
|
||||
file:mergetype(apidoctype)
|
||||
end
|
||||
|
||||
-- create type from code
|
||||
local typeref = createtype(init,1,comment2apiobj,file)
|
||||
if typeref and typeref.tag == "inlinetyperef"
|
||||
and typeref.def.tag == "recordtypedef" then
|
||||
|
||||
-- set the name
|
||||
typeref.def.name = apidoctype.name
|
||||
|
||||
-- merge the type with priority to documentation except for source range
|
||||
file:mergetype(typeref.def,false,true)
|
||||
end
|
||||
end
|
||||
|
||||
local function completeapidocitem (apidocitem, itemname, init, file, binder, comment2apiobj)
|
||||
-- manage the case item has no name
|
||||
if not apidocitem.name then
|
||||
apidocitem.name = itemname
|
||||
|
||||
-- if item has no name this means it could not be attach to a parent
|
||||
if apidocitem.scope then
|
||||
apimodelbuilder.additemtoparent(file,apidocitem,apidocitem.scope,apidocitem.sourcerange.min,apidocitem.sourcerange.max)
|
||||
apidocitem.scope = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- for function try to merge definition
|
||||
local apitype = apidocitem:resolvetype(file)
|
||||
if apitype and apitype.tag == "functiontypedef" then
|
||||
local codetype = createtype(init,1,comment2apiobj,file)
|
||||
if codetype and codetype.tag =="inlinetyperef" then
|
||||
codetype.def.name = apitype.name
|
||||
file:mergetype(codetype.def)
|
||||
end
|
||||
end
|
||||
|
||||
-- manage the case item has no type
|
||||
if not apidocitem.type then
|
||||
-- extract typing from comment
|
||||
local type, desc = apimodelbuilder.extractlocaltype(getlinkedcommentafter(binder),file)
|
||||
|
||||
if type then
|
||||
apidocitem.type = type
|
||||
else
|
||||
-- if not found extracttype from code
|
||||
apidocitem.type = createtype(init,1,comment2apiobj,file)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- create or get the item finding in the binder with the given itemname
|
||||
-- return also the ast node corresponding to this item
|
||||
-- ----------------------------------------------------------
|
||||
getitem = function (binder, itemname, comment2apiobj, file)
|
||||
|
||||
-- local function to create item
|
||||
local function createitem(itemname, astnode, itemtype, description)
|
||||
local _item = apimodel._item(itemname)
|
||||
if description then _item.description = description end
|
||||
_item.type = itemtype
|
||||
if astnode and astnode.lineinfo then
|
||||
_item.sourcerange.min = astnode.lineinfo.first.offset
|
||||
_item.sourcerange.max = astnode.lineinfo.last.offset
|
||||
end
|
||||
return _item, astnode
|
||||
end
|
||||
|
||||
-- try to match binder with known patter of item declaration
|
||||
match binder with
|
||||
| `Pair {string, init}
|
||||
| `Set { {`Index { right , string}}, {init,...}} if string and string.tag =="String" ->
|
||||
-- Pair and set is for searching field from type ..
|
||||
-- if the itemname is given this mean we search for a local or a global not a field type.
|
||||
if not itemname then
|
||||
local itemname = string[1]
|
||||
|
||||
-- check for luadoc typing
|
||||
local commentbefore = getlinkedcommentbefore(binder)
|
||||
local apiobj = comment2apiobj[commentbefore] -- find apiobj linked to this comment
|
||||
if apiobj then
|
||||
if apiobj.tag=="item" then
|
||||
if not apiobj.name or apiobj.name == itemname then
|
||||
-- use code to complete api information if it's necessary
|
||||
completeapidocitem(apiobj, itemname, init,file,binder,comment2apiobj)
|
||||
-- for item use code source range rather than doc source range
|
||||
if string and string.lineinfo then
|
||||
apiobj.sourcerange.min = string.lineinfo.first.offset
|
||||
apiobj.sourcerange.max = string.lineinfo.last.offset
|
||||
end
|
||||
return apiobj, string
|
||||
end
|
||||
elseif apiobj.tag=="recordtypedef" then
|
||||
-- use code to complete api information if it's necessary
|
||||
completeapidoctype(apiobj, itemname, init,file,comment2apiobj)
|
||||
return createitem(itemname, string, apimodel._internaltyperef(apiobj.name), nil)
|
||||
end
|
||||
|
||||
-- if the apiobj could not be associated to the current obj,
|
||||
-- we do not use the documentation neither
|
||||
commentbefore = nil
|
||||
end
|
||||
|
||||
-- else we use code to extract the type and description
|
||||
-- check for "local" typing
|
||||
local type, desc = apimodelbuilder.extractlocaltype(getlinkedcommentafter(binder),file)
|
||||
local desc = desc or (commentbefore and commentbefore[1])
|
||||
if type then
|
||||
return createitem(itemname, string, type, desc )
|
||||
else
|
||||
-- if no "local typing" extract type from code
|
||||
return createitem(itemname, string, createtype(init,1,comment2apiobj,file), desc)
|
||||
end
|
||||
end
|
||||
| `Set {ids, inits}
|
||||
| `Local {ids, inits} ->
|
||||
-- if this is a single local var declaration
|
||||
-- we check if there are a comment block linked and try to extract the type
|
||||
if #ids == 1 then
|
||||
local currentid, currentinit = ids[1],inits[1]
|
||||
-- ignore non Ids node
|
||||
if currentid.tag ~= 'Id' or currentid[1] ~= itemname then return nil end
|
||||
|
||||
-- check for luadoc typing
|
||||
local commentbefore = getlinkedcommentbefore(binder)
|
||||
local apiobj = comment2apiobj[commentbefore] -- find apiobj linked to this comment
|
||||
if apiobj then
|
||||
if apiobj.tag=="item" then
|
||||
-- use code to complete api information if it's necessary
|
||||
if not apiobj.name or apiobj.name == itemname then
|
||||
completeapidocitem(apiobj, itemname, currentinit,file,binder,comment2apiobj)
|
||||
-- if this is a global var or if is has no parent
|
||||
-- we do not create a new item
|
||||
if not apiobj.parent or apiobj.parent == file then
|
||||
-- for item use code source range rather than doc source range
|
||||
if currentid and currentid.lineinfo then
|
||||
apiobj.sourcerange.min = currentid.lineinfo.first.offset
|
||||
apiobj.sourcerange.max = currentid.lineinfo.last.offset
|
||||
end
|
||||
return apiobj, currentid
|
||||
else
|
||||
return createitem(itemname, currentid, apiobj.type, nil)
|
||||
end
|
||||
end
|
||||
elseif apiobj.tag=="recordtypedef" then
|
||||
-- use code to complete api information if it's necessary
|
||||
completeapidoctype(apiobj, itemname, currentinit,file,comment2apiobj)
|
||||
return createitem(itemname, currentid, apimodel._internaltyperef(apiobj.name), nil)
|
||||
end
|
||||
|
||||
-- if the apiobj could not be associated to the current obj,
|
||||
-- we do not use the documentation neither
|
||||
commentbefore = nil
|
||||
end
|
||||
|
||||
-- else we use code to extract the type and description
|
||||
-- check for "local" typing
|
||||
local type,desc = apimodelbuilder.extractlocaltype(getlinkedcommentafter(binder),file)
|
||||
desc = desc or (commentbefore and commentbefore[1])
|
||||
if type then
|
||||
return createitem(itemname, currentid, type, desc)
|
||||
else
|
||||
-- if no "local typing" extract type from code
|
||||
return createitem(itemname, currentid, createtype(currentinit,1,comment2apiobj,file), desc)
|
||||
end
|
||||
end
|
||||
-- else we use code to extract the type
|
||||
local init,returnposition = nil,1
|
||||
for i,id in ipairs(ids) do
|
||||
-- calculate the current return position
|
||||
if init and (init.tag == "Call" or init.tag == "Invoke") then
|
||||
-- if previous init was a call or an invoke
|
||||
-- we increment the returnposition
|
||||
returnposition= returnposition+1
|
||||
else
|
||||
-- if init is not a function call
|
||||
-- we change the init used to determine the type
|
||||
init = inits[i]
|
||||
end
|
||||
|
||||
-- get the name of the current id
|
||||
local idname = id[1]
|
||||
|
||||
-- if this is the good id
|
||||
if itemname == idname then
|
||||
-- create type from init node and return position
|
||||
return createitem (itemname, id, createtype(init,returnposition,comment2apiobj,file),nil)
|
||||
end
|
||||
end
|
||||
| `Function {params, body} ->
|
||||
for i,id in ipairs(params) do
|
||||
-- get the name of the current id
|
||||
local idname = id[1]
|
||||
-- if this is the good id
|
||||
if itemname == idname then
|
||||
-- extract param's type from luadocumentation
|
||||
local obj = comment2apiobj[getlinkedcommentbefore(binder)]
|
||||
if obj and obj.tag=="item" then
|
||||
local typedef = obj:resolvetype(file)
|
||||
if typedef and typedef.tag =="functiontypedef" then
|
||||
for j, param in ipairs(typedef.params) do
|
||||
if i==j then
|
||||
if i ==1 and itemname == "self" and param.type == nil
|
||||
and obj.parent and obj.parent.tag == "recordtypedef" and obj.parent.name then
|
||||
param.type = apimodel._internaltyperef(obj.parent.name)
|
||||
end
|
||||
-- TODO perhaps we must clone the typeref
|
||||
return createitem(itemname,id, param.type,param.description)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return createitem(itemname,id)
|
||||
end
|
||||
end
|
||||
| `Forin {ids, expr, body} ->
|
||||
for i,id in ipairs(ids) do
|
||||
-- get the name of the current id
|
||||
local idname = id[1]
|
||||
-- if this is the good id
|
||||
if itemname == idname then
|
||||
-- return data : we can not guess the type for now
|
||||
return createitem(itemname,id)
|
||||
end
|
||||
end
|
||||
| `Fornum {id, ...} ->
|
||||
-- get the name of the current id
|
||||
local idname = id[1]
|
||||
-- if this is the good id
|
||||
if itemname == idname then
|
||||
-- return data : we can not guess the type for now
|
||||
return createitem(itemname,id)
|
||||
end
|
||||
| `Localrec {{id}, {func}} ->
|
||||
-- get the name of the current id
|
||||
local idname = id[1]
|
||||
-- if this is the good id
|
||||
if itemname == idname then
|
||||
-- check for luadoc typing
|
||||
local commentbefore = getlinkedcommentbefore(binder)
|
||||
local apiobj = comment2apiobj[commentbefore] -- find apiobj linked to this comment
|
||||
if apiobj then
|
||||
if apiobj.tag=="item" then
|
||||
if not apiobj.name or apiobj.name == itemname then
|
||||
-- use code to complete api information if it's necessary
|
||||
completeapidocitem(apiobj, itemname, func,file,binder,comment2apiobj)
|
||||
return createitem(itemname,id,apiobj.type,nil)
|
||||
end
|
||||
end
|
||||
|
||||
-- if the apiobj could not be associated to the current obj,
|
||||
-- we do not use the documentation neither
|
||||
commentbefore = nil
|
||||
end
|
||||
|
||||
-- else we use code to extract the type and description
|
||||
-- check for "local" typing
|
||||
local type,desc = apimodelbuilder.extractlocaltype(getlinkedcommentafter(binder),file)
|
||||
desc = desc or (commentbefore and commentbefore[1])
|
||||
if type then
|
||||
return createitem(itemname, id, type, desc)
|
||||
else
|
||||
-- if no "local typing" extract type from code
|
||||
return createitem(itemname, id, createtype(func,1,comment2apiobj,file), desc)
|
||||
end
|
||||
end
|
||||
| _ ->
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- Search from Id node to Set node to find field of type.
|
||||
--
|
||||
-- Lua code : table.field1.field2 = 12
|
||||
-- looks like that in metalua :
|
||||
-- `Set{
|
||||
-- `Index { `Index { `Id "table", `String "field1" },
|
||||
-- `String "field2"},
|
||||
-- `Number "12"}
|
||||
-- ----------------------------------------------------------
|
||||
local function searchtypefield(node,_currentitem,comment2apiobj,file)
|
||||
|
||||
-- we are just interested :
|
||||
-- by item which is field of recordtypedef
|
||||
-- by ast node which are Index
|
||||
if _currentitem then
|
||||
local type = _currentitem:resolvetype(file)
|
||||
if type and type.tag == "recordtypedef" then
|
||||
if node and node.tag == "Index" then
|
||||
local rightpart = node[2]
|
||||
local _newcurrentitem = type.fields[rightpart[1]]
|
||||
|
||||
if _newcurrentitem then
|
||||
-- if this index represent a known field of the type we continue to search
|
||||
searchtypefield (node.parent,_newcurrentitem,comment2apiobj,file)
|
||||
else
|
||||
-- if not, this is perhaps a new field, but
|
||||
-- to be a new field this index must be include in a Set
|
||||
if node.parent and node.parent.tag =="Set" then
|
||||
-- in this case we create the new item ans add it to the type
|
||||
local set = node.parent
|
||||
local item, string = getitem(set,nil, comment2apiobj,file)
|
||||
-- add this item to the type, only if it has no parent and if this type does not contain already this field
|
||||
if item and not item.parent and string and not type.fields[string[1]] then
|
||||
type:addfield(item)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- create local vars, global vars and linked it with theirs occurences
|
||||
-- ----------------------------------------------------------
|
||||
local function createvardefinitions(_internalcontent,ast,file,comment2apiobj)
|
||||
-- use bindings to get locals and globals definition
|
||||
local locals, globals = bindings( ast )
|
||||
|
||||
-- create locals var
|
||||
for binder, namesAndOccurrences in pairs(locals) do
|
||||
for name, occurrences in pairs(namesAndOccurrences) do
|
||||
-- get item, id
|
||||
local _item, id = getitem(binder, name,comment2apiobj,file)
|
||||
if id then
|
||||
-- add definition as occurence
|
||||
-- we consider the identifier in the binder as an occurence
|
||||
local _identifierdef = idto_identifier[id]
|
||||
if _identifierdef then
|
||||
table.insert(_item.occurrences, _identifierdef)
|
||||
_identifierdef.definition = _item
|
||||
end
|
||||
|
||||
-- add occurences
|
||||
for _,occurrence in ipairs(occurrences) do
|
||||
searchtypefield(occurrence.parent, _item,comment2apiobj,file)
|
||||
local _identifier = idto_identifier[occurrence]
|
||||
if _identifier then
|
||||
table.insert(_item.occurrences, _identifier)
|
||||
_identifier.definition = _item
|
||||
end
|
||||
end
|
||||
|
||||
-- add item to block
|
||||
local _block = idto_block[id]
|
||||
table.insert(_block.localvars,{item=_item,scope = {min=0,max=0}})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- create globals var
|
||||
for name, occurrences in pairs( globals ) do
|
||||
|
||||
-- get or create definition
|
||||
local _item = file.globalvars[name]
|
||||
local binder = occurrences[1].parent
|
||||
if not _item then
|
||||
-- global declaration is only if the first occurence in left part of a 'Set'
|
||||
if binder and binder.tag == "Set" then
|
||||
_item = getitem(binder, name,comment2apiobj,file)
|
||||
end
|
||||
|
||||
-- if we find and item this is a global var declaration
|
||||
if _item then
|
||||
file:addglobalvar(_item)
|
||||
else
|
||||
-- else it is an unknown global var
|
||||
_item = apimodel._item(name)
|
||||
local _firstoccurrence = idto_identifier[occurrences[1]]
|
||||
if _firstoccurrence then
|
||||
_item.sourcerange.min = _firstoccurrence.sourcerange.min
|
||||
_item.sourcerange.max = _firstoccurrence.sourcerange.max
|
||||
end
|
||||
table.insert(_internalcontent.unknownglobalvars,_item)
|
||||
end
|
||||
else
|
||||
-- if the global var definition already exists, we just try to it
|
||||
if binder then
|
||||
match binder with
|
||||
| `Set {ids, inits} ->
|
||||
-- manage case only if there are 1 element in the Set
|
||||
if #ids == 1 then
|
||||
local currentid, currentinit = ids[1],inits[1]
|
||||
-- ignore non Ids node and bad name
|
||||
if currentid.tag == 'Id' and currentid[1] == name then
|
||||
completeapidocitem(_item, name, currentinit,file,binder,comment2apiobj)
|
||||
|
||||
if currentid and currentid.lineinfo then
|
||||
_item.sourcerange.min = currentid.lineinfo.first.offset
|
||||
_item.sourcerange.max = currentid.lineinfo.last.offset
|
||||
end
|
||||
end
|
||||
end
|
||||
| _ ->
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- add occurences
|
||||
for _,occurence in ipairs(occurrences) do
|
||||
local _identifier = idto_identifier[occurence]
|
||||
searchtypefield(occurence.parent, _item,comment2apiobj,file)
|
||||
if _identifier then
|
||||
table.insert(_item.occurrences, _identifier)
|
||||
_identifier.definition = _item
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- add parent to all ast node
|
||||
-- ----------------------------------------------------------
|
||||
local function addparents(ast)
|
||||
-- visitor function (down)
|
||||
local function down (node,parent)
|
||||
node.parent = parent
|
||||
end
|
||||
|
||||
-- visit ast and build internal model
|
||||
Q(ast):foreach(down,up)
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- try to detect a module declaration from code
|
||||
-- ----------------------------------------------------------
|
||||
local function searchmodule(ast,file,comment2apiobj,modulename)
|
||||
-- if the last statement is a return
|
||||
if ast then
|
||||
local laststatement = ast[#ast]
|
||||
if laststatement and laststatement.tag == "Return" then
|
||||
-- and if the first expression returned is an identifier.
|
||||
local firstexpr = laststatement[1]
|
||||
if firstexpr and firstexpr.tag == "Id" then
|
||||
-- get identifier in internal model
|
||||
local _identifier = idto_identifier [firstexpr]
|
||||
-- the definition should be an inline type
|
||||
if _identifier
|
||||
and _identifier.definition
|
||||
and _identifier.definition.type
|
||||
and _identifier.definition.type.tag == "inlinetyperef"
|
||||
and _identifier.definition.type.def.tag == "recordtypedef" then
|
||||
|
||||
--set modulename if needed
|
||||
if not file.name then file.name = modulename end
|
||||
|
||||
-- create or merge type
|
||||
local _type = _identifier.definition.type.def
|
||||
_type.name = modulename
|
||||
|
||||
-- if file (module) has no documentation add item documentation to it
|
||||
-- else add it to the type.
|
||||
if not file.description or file.description == "" then
|
||||
file.description = _identifier.definition.description
|
||||
else
|
||||
_type.description = _identifier.definition.description
|
||||
end
|
||||
_identifier.definition.description = ""
|
||||
if not file.shortdescription or file.shortdescription == "" then
|
||||
file.shortdescription = _identifier.definition.shortdescription
|
||||
else
|
||||
_type.shortdescription = _identifier.definition.shortdescription
|
||||
end
|
||||
_identifier.definition.shortdescription = ""
|
||||
|
||||
-- WORKAROUND FOR BUG 421622: [outline]module selection in outline does not select it in texteditor
|
||||
--_type.sourcerange.min = _identifier.definition.sourcerange.min
|
||||
--_type.sourcerange.max = _identifier.definition.sourcerange.max
|
||||
|
||||
-- merge the type with priority to documentation except for source range
|
||||
file:mergetype(_type,false,true)
|
||||
|
||||
-- create return if needed
|
||||
if not file.returns[1] then
|
||||
file.returns[1] = apimodel._return()
|
||||
file.returns[1].types = { apimodel._internaltyperef(modulename) }
|
||||
end
|
||||
|
||||
-- change the type of the identifier
|
||||
_identifier.definition.type = apimodel._internaltyperef(modulename)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------
|
||||
-- create the internalcontent from an ast metalua
|
||||
-- ----------------------------------------------------------
|
||||
function M.createinternalcontent (ast,file,comment2apiobj,modulename)
|
||||
-- init cache
|
||||
idto_block = {}
|
||||
idto_identifier = {}
|
||||
expreto_expression = {}
|
||||
comment2apiobj = comment2apiobj or {}
|
||||
file = file or apimodel._file()
|
||||
|
||||
-- execute code safely to be sure to clean cache correctly
|
||||
local internalcontent
|
||||
local ok, errmsg = pcall(function ()
|
||||
-- add parent to all node
|
||||
addparents(ast)
|
||||
|
||||
-- create block and expression node
|
||||
internalcontent = createtreestructure(ast)
|
||||
|
||||
-- create Local vars, global vars and linked occurences (Items)
|
||||
createvardefinitions(internalcontent,ast,file,comment2apiobj)
|
||||
|
||||
-- try to dectect module information from code
|
||||
local moduletyperef = file:moduletyperef()
|
||||
if moduletyperef and moduletyperef.tag == "internaltyperef" then
|
||||
modulename = moduletyperef.typename or modulename
|
||||
end
|
||||
if modulename then
|
||||
searchmodule(ast,file,comment2apiobj,modulename)
|
||||
end
|
||||
end)
|
||||
|
||||
-- clean cache
|
||||
idto_block = {}
|
||||
idto_identifier = {}
|
||||
expreto_expression = {}
|
||||
|
||||
-- if not ok raise an error
|
||||
if not ok then error (errmsg) end
|
||||
|
||||
return internalcontent
|
||||
end
|
||||
|
||||
return M
|
||||
656
Utils/luarocks/systree/share/lua/5.1/models/ldparser.lua
Normal file
656
Utils/luarocks/systree/share/lua/5.1/models/ldparser.lua
Normal file
@@ -0,0 +1,656 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- Copyright (c) 2011-2013 Sierra Wireless and others.
|
||||
-- All rights reserved. This program and the accompanying materials
|
||||
-- are made available under the terms of the Eclipse Public License v1.0
|
||||
-- which accompanies this distribution, and is available at
|
||||
-- http://www.eclipse.org/legal/epl-v10.html
|
||||
--
|
||||
-- Contributors:
|
||||
-- Sierra Wireless - initial API and implementation
|
||||
-------------------------------------------------------------------------------
|
||||
local mlc = require ('metalua.compiler').new()
|
||||
local gg = require 'metalua.grammar.generator'
|
||||
local lexer = require 'metalua.grammar.lexer'
|
||||
local mlp = mlc.parser
|
||||
|
||||
local M = {} -- module
|
||||
local lx -- lexer used to parse tag
|
||||
local registeredparsers -- table {tagname => {list de parsers}}
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- raise an error if result contains a node error
|
||||
-- ----------------------------------------------------
|
||||
local function raiserror(result)
|
||||
for i, node in ipairs(result) do
|
||||
assert(not node or node.tag ~= "Error")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- copy key and value from one table to an other
|
||||
-- ----------------------------------------------------
|
||||
local function copykey(tablefrom, tableto)
|
||||
for key, value in pairs(tablefrom) do
|
||||
if key ~= "lineinfos" then
|
||||
tableto[key] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- Handle keyword and identifiers as word
|
||||
-- ----------------------------------------------------
|
||||
local function parseword(lx)
|
||||
local word = lx :peek()
|
||||
local tag = word.tag
|
||||
|
||||
if tag=='Keyword' or tag=='Id' then
|
||||
lx:next()
|
||||
return {tag='Word', lineinfo=word.lineinfo, word[1]}
|
||||
else
|
||||
return gg.parse_error(lx,'Id or Keyword expected')
|
||||
end
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse an id
|
||||
-- return a table {name, lineinfo)
|
||||
-- ----------------------------------------------------
|
||||
local idparser = gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { name = result[1][1] }
|
||||
end,
|
||||
parseword
|
||||
})
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a modulename (id.)?id
|
||||
-- return a table {name, lineinfo)
|
||||
-- ----------------------------------------------------
|
||||
local modulenameparser = gg.list({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
local ids = {}
|
||||
for i, id in ipairs(result) do
|
||||
table.insert(ids,id.name)
|
||||
end
|
||||
return {name = table.concat(ids,".")}
|
||||
end,
|
||||
primary = idparser,
|
||||
separators = '.'
|
||||
})
|
||||
-- ----------------------------------------------------
|
||||
-- parse a typename (id.)?id
|
||||
-- return a table {name, lineinfo)
|
||||
-- ----------------------------------------------------
|
||||
local typenameparser= modulenameparser
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse an internaltype ref
|
||||
-- return a table {name, lineinfo)
|
||||
-- ----------------------------------------------------
|
||||
local internaltyperefparser = gg.sequence({
|
||||
builder = function(result)
|
||||
raiserror(result)
|
||||
return {tag = "typeref",type=result[1].name}
|
||||
end,
|
||||
"#", typenameparser
|
||||
})
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse en external type ref
|
||||
-- return a table {name, lineinfo)
|
||||
-- ----------------------------------------------------
|
||||
local externaltyperefparser = gg.sequence({
|
||||
builder = function(result)
|
||||
raiserror(result)
|
||||
return {tag = "typeref",module=result[1].name,type=result[2].name}
|
||||
end,
|
||||
modulenameparser,"#", typenameparser
|
||||
})
|
||||
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a typeref
|
||||
-- return a table {name, lineinfo)
|
||||
-- ----------------------------------------------------
|
||||
local typerefparser = gg.multisequence{
|
||||
internaltyperefparser,
|
||||
externaltyperefparser}
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a list of typeref
|
||||
-- return a list of table {name, lineinfo)
|
||||
-- ----------------------------------------------------
|
||||
local typereflistparser = gg.list({
|
||||
primary = typerefparser,
|
||||
separators = ','
|
||||
})
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- TODO use a more generic way to parse (modifier if not always a typeref)
|
||||
-- TODO support more than one modifier
|
||||
-- ----------------------------------------------------
|
||||
local modifiersparser = gg.sequence({
|
||||
builder = function(result)
|
||||
raiserror(result)
|
||||
return {[result[1].name]=result[2]}
|
||||
end,
|
||||
"[", idparser , "=" , internaltyperefparser , "]"
|
||||
})
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a return tag
|
||||
-- ----------------------------------------------------
|
||||
local returnparsers = {
|
||||
-- full parser
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { types= result[1]}
|
||||
end,
|
||||
'@','return', typereflistparser
|
||||
}),
|
||||
-- parser without typerefs
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { types = {}}
|
||||
end,
|
||||
'@','return'
|
||||
})
|
||||
}
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a param tag
|
||||
-- ----------------------------------------------------
|
||||
local paramparsers = {
|
||||
-- full parser
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { name = result[2].name, type = result[1]}
|
||||
end,
|
||||
'@','param', typerefparser, idparser
|
||||
}),
|
||||
|
||||
-- full parser without type
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { name = result[1].name}
|
||||
end,
|
||||
'@','param', idparser
|
||||
}),
|
||||
|
||||
-- Parser for `Dots
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { name = '...' }
|
||||
end,
|
||||
'@','param', '...'
|
||||
}),
|
||||
}
|
||||
-- ----------------------------------------------------
|
||||
-- parse a field tag
|
||||
-- ----------------------------------------------------
|
||||
local fieldparsers = {
|
||||
-- full parser
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
local tag = {}
|
||||
copykey(result[1],tag)
|
||||
tag.type = result[2]
|
||||
tag.name = result[3].name
|
||||
return tag
|
||||
end,
|
||||
'@','field', modifiersparser, typerefparser, idparser
|
||||
}),
|
||||
|
||||
-- parser without name
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
local tag = {}
|
||||
copykey(result[1],tag)
|
||||
tag.type = result[2]
|
||||
return tag
|
||||
end,
|
||||
'@','field', modifiersparser, typerefparser
|
||||
}),
|
||||
|
||||
-- parser without type
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
local tag = {}
|
||||
copykey(result[1],tag)
|
||||
tag.name = result[2].name
|
||||
return tag
|
||||
end,
|
||||
'@','field', modifiersparser, idparser
|
||||
}),
|
||||
|
||||
-- parser without type and name
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
local tag = {}
|
||||
copykey(result[1],tag)
|
||||
return tag
|
||||
end,
|
||||
'@','field', modifiersparser
|
||||
}),
|
||||
|
||||
-- parser without modifiers
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { name = result[2].name, type = result[1]}
|
||||
end,
|
||||
'@','field', typerefparser, idparser
|
||||
}),
|
||||
|
||||
-- parser without modifiers and name
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return {type = result[1]}
|
||||
end,
|
||||
'@','field', typerefparser
|
||||
}),
|
||||
|
||||
-- parser without type and modifiers
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { name = result[1].name}
|
||||
end,
|
||||
'@','field', idparser
|
||||
}),
|
||||
|
||||
-- parser with nothing
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return {}
|
||||
end,
|
||||
'@','field'
|
||||
})
|
||||
}
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a function tag
|
||||
-- TODO use a more generic way to parse modifier !
|
||||
-- ----------------------------------------------------
|
||||
local functionparsers = {
|
||||
-- full parser
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
local tag = {}
|
||||
copykey(result[1],tag)
|
||||
tag.name = result[2].name
|
||||
return tag
|
||||
end,
|
||||
'@','function', modifiersparser, idparser
|
||||
}),
|
||||
|
||||
-- parser without name
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
local tag = {}
|
||||
copykey(result[1],tag)
|
||||
return tag
|
||||
end,
|
||||
'@','function', modifiersparser
|
||||
}),
|
||||
|
||||
-- parser without modifier
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
local tag = {}
|
||||
tag.name = result[1].name
|
||||
return tag
|
||||
end,
|
||||
'@','function', idparser
|
||||
}),
|
||||
|
||||
-- empty parser
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return {}
|
||||
end,
|
||||
'@','function'
|
||||
})
|
||||
}
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a type tag
|
||||
-- ----------------------------------------------------
|
||||
local typeparsers = {
|
||||
-- full parser
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { name = result[1].name}
|
||||
end,
|
||||
'@','type',typenameparser
|
||||
}),
|
||||
-- parser without name
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return {}
|
||||
end,
|
||||
'@','type'
|
||||
})
|
||||
}
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a module tag
|
||||
-- ----------------------------------------------------
|
||||
local moduleparsers = {
|
||||
-- full parser
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { name = result[1].name }
|
||||
end,
|
||||
'@','module', modulenameparser
|
||||
}),
|
||||
-- parser without name
|
||||
gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return {}
|
||||
end,
|
||||
'@','module'
|
||||
})
|
||||
}
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a third tag
|
||||
-- ----------------------------------------------------
|
||||
local thirdtagsparser = gg.sequence({
|
||||
builder = function (result)
|
||||
raiserror(result)
|
||||
return { name = result[1][1] }
|
||||
end,
|
||||
'@', mlp.id
|
||||
})
|
||||
-- ----------------------------------------------------
|
||||
-- init parser
|
||||
-- ----------------------------------------------------
|
||||
local function initparser()
|
||||
-- register parsers
|
||||
-- each tag name has several parsers
|
||||
registeredparsers = {
|
||||
["module"] = moduleparsers,
|
||||
["return"] = returnparsers,
|
||||
["type"] = typeparsers,
|
||||
["field"] = fieldparsers,
|
||||
["function"] = functionparsers,
|
||||
["param"] = paramparsers
|
||||
}
|
||||
|
||||
-- create lexer used for parsing
|
||||
lx = lexer.lexer:clone()
|
||||
lx.extractors = {
|
||||
-- "extract_long_comment",
|
||||
-- "extract_short_comment",
|
||||
-- "extract_long_string",
|
||||
"extract_short_string",
|
||||
"extract_word",
|
||||
"extract_number",
|
||||
"extract_symbol"
|
||||
}
|
||||
|
||||
-- Add dots as keyword
|
||||
local tagnames = { '...' }
|
||||
|
||||
-- Add tag names as key word
|
||||
for tagname, _ in pairs(registeredparsers) do
|
||||
table.insert(tagnames,tagname)
|
||||
end
|
||||
lx:add(tagnames)
|
||||
|
||||
return lx, parsers
|
||||
end
|
||||
|
||||
initparser()
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- get the string pattern to remove for each line of description
|
||||
-- the goal is to fix the indentation problems
|
||||
-- ----------------------------------------------------
|
||||
local function getstringtoremove (stringcomment,commentstart)
|
||||
local _,_,capture = string.find(stringcomment,"\n?([ \t]*)@[^{]+",commentstart)
|
||||
if not capture then
|
||||
_,_,capture = string.find(stringcomment,"^([ \t]*)",commentstart)
|
||||
end
|
||||
capture = string.gsub(capture,"(.)","%1?")
|
||||
return capture
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse comment tag partition and return table structure
|
||||
-- ----------------------------------------------------
|
||||
local function parsetag(part)
|
||||
if part.comment:find("^@") then
|
||||
-- check if the part start by a supported tag
|
||||
for tagname,parsers in pairs(registeredparsers) do
|
||||
if (part.comment:find("^@"..tagname)) then
|
||||
-- try the registered parsers for this tag
|
||||
local result
|
||||
for i, parser in ipairs(parsers) do
|
||||
local valid, tag = pcall(parser, lx:newstream(part.comment, tagname .. 'tag lexer'))
|
||||
if valid then
|
||||
-- add tagname
|
||||
tag.tagname = tagname
|
||||
|
||||
-- add description
|
||||
local endoffset = tag.lineinfo.last.offset
|
||||
tag.description = part.comment:sub(endoffset+2,-1)
|
||||
return tag
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- Parse third party tags.
|
||||
--
|
||||
-- Enable to parse a tag not defined in language.
|
||||
-- So for, accepted format is: @sometagname adescription
|
||||
-- ----------------------------------------------------
|
||||
local function parsethirdtag( part )
|
||||
|
||||
-- Check it there is someting to process
|
||||
if not part.comment:find("^@") then
|
||||
return nil, 'No tag to parse'
|
||||
end
|
||||
|
||||
-- Apply parser
|
||||
local status, parsedtag = pcall(thirdtagsparser, lx:newstream(part.comment, 'Third party tag lexer'))
|
||||
if not status then
|
||||
return nil, "Unable to parse given string."
|
||||
end
|
||||
|
||||
-- Retrieve description
|
||||
local endoffset = parsedtag.lineinfo.last.offset
|
||||
local tag = {
|
||||
description = part.comment:sub(endoffset+2,-1)
|
||||
}
|
||||
return parsedtag.name, tag
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------
|
||||
-- split string comment in several part
|
||||
-- return list of {comment = string, offset = number}
|
||||
-- the first part is the part before the first tag
|
||||
-- the others are the part from a tag to the next one
|
||||
-- ----------------------------------------------------
|
||||
local function split(stringcomment,commentstart)
|
||||
local partstart = commentstart
|
||||
local result = {}
|
||||
|
||||
-- manage case where the comment start by @
|
||||
-- (we must ignore the inline see tag @{..})
|
||||
local at_startoffset, at_endoffset = stringcomment:find("^[ \t]*@[^{]",partstart)
|
||||
if at_endoffset then
|
||||
partstart = at_endoffset-1 -- we start before the @ and the non '{' character
|
||||
end
|
||||
|
||||
-- split comment
|
||||
-- (we must ignore the inline see tag @{..})
|
||||
repeat
|
||||
at_startoffset, at_endoffset = stringcomment:find("\n[ \t]*@[^{]",partstart)
|
||||
local partend
|
||||
if at_startoffset then
|
||||
partend= at_startoffset-1 -- the end is before the separator pattern (just before the \n)
|
||||
else
|
||||
partend = #stringcomment -- we don't find any pattern so the end is the end of the string
|
||||
end
|
||||
table.insert(result, { comment = stringcomment:sub (partstart,partend) ,
|
||||
offset = partstart})
|
||||
if at_endoffset then
|
||||
partstart = at_endoffset-1 -- the new start is befire the @ and the non { char
|
||||
end
|
||||
until not at_endoffset
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
-- ----------------------------------------------------
|
||||
-- parse a comment block and return a table
|
||||
-- ----------------------------------------------------
|
||||
function M.parse(stringcomment)
|
||||
|
||||
local _comment = {description="", shortdescription=""}
|
||||
|
||||
-- clean windows carriage return
|
||||
stringcomment = string.gsub(stringcomment,"\r\n","\n")
|
||||
|
||||
-- check if it's a ld comment
|
||||
-- get the begin of the comment
|
||||
-- ============================
|
||||
if not stringcomment:find("^-") then
|
||||
-- if this comment don't start by -, we will not handle it.
|
||||
return nil
|
||||
end
|
||||
|
||||
-- retrieve the real start
|
||||
local commentstart = 2 --after the first hyphen
|
||||
-- if the first line is an empty comment line with at least 3 hyphens we ignore it
|
||||
local _ , endoffset = stringcomment:find("^-+[ \t]*\n")
|
||||
if endoffset then
|
||||
commentstart = endoffset+1
|
||||
end
|
||||
|
||||
-- clean comments
|
||||
-- ===================
|
||||
-- remove line of "-"
|
||||
stringcomment = string.sub(stringcomment,commentstart)
|
||||
-- clean indentation
|
||||
local pattern = getstringtoremove (stringcomment,1)
|
||||
stringcomment = string.gsub(stringcomment,"^"..pattern,"")
|
||||
stringcomment = string.gsub(stringcomment,"\n"..pattern,"\n")
|
||||
|
||||
-- split comment part
|
||||
-- ====================
|
||||
local commentparts = split(stringcomment, 1)
|
||||
|
||||
-- Extract descriptions
|
||||
-- ====================
|
||||
local firstpart = commentparts[1].comment
|
||||
if firstpart:find("^[^@]") or firstpart:find("^@{") then
|
||||
-- if the comment part don't start by @
|
||||
-- it's the part which contains descriptions
|
||||
-- (there are an exception for the in-line see tag @{..})
|
||||
local shortdescription, description = string.match(firstpart,'^(.-[.?])(%s.+)')
|
||||
-- store description
|
||||
if shortdescription then
|
||||
_comment.shortdescription = shortdescription
|
||||
-- clean description
|
||||
-- remove always the first space character
|
||||
-- (this manage the case short and long description is on the same line)
|
||||
description = string.gsub(description, "^[ \t]","")
|
||||
-- if first line is only an empty string remove it
|
||||
description = string.gsub(description, "^[ \t]*\n","")
|
||||
_comment.description = description
|
||||
else
|
||||
_comment.shortdescription = firstpart
|
||||
_comment.description = ""
|
||||
end
|
||||
end
|
||||
|
||||
-- Extract tags
|
||||
-- ===================
|
||||
-- Parse regular tags
|
||||
local tag
|
||||
for i, part in ipairs(commentparts) do
|
||||
tag = parsetag(part)
|
||||
--if it's a supported tag (so tag is not nil, it's a table)
|
||||
if tag then
|
||||
if not _comment.tags then _comment.tags = {} end
|
||||
if not _comment.tags[tag.tagname] then
|
||||
_comment.tags[tag.tagname] = {}
|
||||
end
|
||||
table.insert(_comment.tags[tag.tagname], tag)
|
||||
else
|
||||
|
||||
-- Try user defined tags, so far they will look like
|
||||
-- @identifier description
|
||||
local tagname, thirdtag = parsethirdtag( part )
|
||||
if tagname then
|
||||
--
|
||||
-- Append found tag
|
||||
--
|
||||
local reservedname = 'unknowntags'
|
||||
if not _comment.unknowntags then
|
||||
_comment.unknowntags = {}
|
||||
end
|
||||
|
||||
-- Create specific section for parsed tag
|
||||
if not _comment.unknowntags[tagname] then
|
||||
_comment.unknowntags[tagname] = {}
|
||||
end
|
||||
-- Append to specific section
|
||||
table.insert(_comment.unknowntags[tagname], thirdtag)
|
||||
end
|
||||
end
|
||||
end
|
||||
return _comment
|
||||
end
|
||||
|
||||
|
||||
function M.parseinlinecomment(stringcomment)
|
||||
--TODO this code is use to activate typage only on --- comments. (deactivate for now)
|
||||
-- if not stringcomment or not stringcomment:find("^-") then
|
||||
-- -- if this comment don't start by -, we will not handle it.
|
||||
-- return nil
|
||||
-- end
|
||||
-- -- remove the first '-'
|
||||
-- stringcomment = string.sub(stringcomment,2)
|
||||
-- print (stringcomment)
|
||||
-- io.flush()
|
||||
local valid, parsedtag = pcall(typerefparser, lx:newstream(stringcomment, 'typeref parser'))
|
||||
if valid then
|
||||
local endoffset = parsedtag.lineinfo.last.offset
|
||||
parsedtag.description = stringcomment:sub(endoffset+2,-1)
|
||||
return parsedtag
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user