-------------------------------------------------------------------------------- | |
-- 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, | |
['thread'] = true, | |
['userdata'] = true, | |
['list'] = true, | |
['map'] = true, | |
['any'] = 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 dt_typeref.type == "table" then | |
-- manage special table type | |
_typeref = apimodel._inlinetyperef(apimodel._recordtypedef("table")) | |
elseif dt_typeref.type == "list" or dt_typeref.type == "map" then | |
-- manage structures | |
local structuretypedef = apimodel._recordtypedef(dt_typeref) | |
structuretypedef.defaultvaluetyperef = createtyperef(dt_typeref.valuetype) | |
if dt_typeref.type == "map" then | |
structuretypedef.defaultkeytyperef = createtyperef(dt_typeref.keytype) | |
end | |
structuretypedef.structurekind = dt_typeref.type | |
structuretypedef.name = dt_typeref.type | |
_typeref = apimodel._inlinetyperef(structuretypedef) | |
elseif primitivetypes[dt_typeref.type] then | |
-- manage primitive types | |
_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 | |
local _moduletypedef = gettypedef(_file,_typeref.typename,"recordtypedef",sourcerangemin,sourcerangemax) | |
-- manage extends (inheritance) and structure tags | |
if _moduletypedef and _moduletypedef.tag == "recordtypedef" then | |
if regulartags["extends"] and regulartags["extends"][1] then | |
local supertype = regulartags["extends"][1].type | |
if supertype then _moduletypedef.supertype = createtyperef(supertype) end | |
elseif regulartags["map"] and regulartags["map"][1] then | |
local keytype = regulartags["map"][1].keytype | |
local valuetype = regulartags["map"][1].valuetype | |
if keytype and valuetype then | |
_moduletypedef.defaultkeytyperef = createtyperef(keytype) | |
_moduletypedef.defaultvaluetyperef = createtyperef(valuetype) | |
_moduletypedef.structurekind = "map" | |
_moduletypedef.structuredescription = regulartags["map"][1].description | |
end | |
elseif regulartags["list"] and regulartags["list"][1] then | |
local type = regulartags["list"][1].type | |
if type then | |
_moduletypedef.defaultvaluetyperef = createtyperef(type) | |
_moduletypedef.structurekind = "list" | |
_moduletypedef.structuredescription = regulartags["list"][1].description | |
end | |
end | |
end | |
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 | |
-- manage extends (inheritance) | |
if regulartags["extends"] and regulartags["extends"][1] then | |
local supertype = regulartags["extends"][1].type | |
if supertype then _recordtypedef.supertype = createtyperef(supertype) end | |
end | |
-- manage structure tag | |
if regulartags["map"] and regulartags["map"][1] then | |
local keytype = regulartags["map"][1].keytype | |
local valuetype = regulartags["map"][1].valuetype | |
if keytype and valuetype then | |
_recordtypedef.defaultkeytyperef = createtyperef(keytype) | |
_recordtypedef.defaultvaluetyperef = createtyperef(valuetype) | |
_recordtypedef.structurekind = "map" | |
_recordtypedef.structuredescription = regulartags["map"][1].description | |
end | |
elseif regulartags["list"] and regulartags["list"][1] then | |
local type = regulartags["list"][1].type | |
if type then | |
_recordtypedef.defaultvaluetyperef = createtyperef(type) | |
_recordtypedef.structurekind = "list" | |
_recordtypedef.structuredescription = regulartags["list"][1].description | |
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 |