-------------------------------------------------------------------------------- -- 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 -- - 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