| -------------------------------------------------------------------------------- |
| -- 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 |
| |
| -- |
| -- Store user defined tags |
| -- |
| local function attachmetadata(apiobj, parsedcomment) |
| local thirdtags = parsedcomment and parsedcomment.unknowntags |
| if thirdtags then |
| -- Define a storage index for user defined tags on current API element |
| if not apiobj.metadata then apiobj.metadata = {} end |
| |
| -- Loop over user defined tags |
| for usertag, taglist in pairs(thirdtags) do |
| if not apiobj.metadata[ usertag ] then |
| apiobj.metadata[ usertag ] = { |
| tag = usertag |
| } |
| end |
| for _, tag in ipairs( taglist ) do |
| table.insert(apiobj.metadata[usertag], tag) |
| end |
| end |
| end |
| 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 |
| end |
| 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 |
| _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 and _item.name 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"] or regulartags["callof"] 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) |
| attachmetadata(_functiontypedef, parsedcomment) |
| _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) |
| |
| -- manage callof |
| if regulartags["callof"] and regulartags["callof"][1] and regulartags["callof"][1].type then |
| -- get the type which will be callable ! |
| local _internaltyperef = createtyperef(regulartags["callof"][1].type) |
| if _internaltyperef and _internaltyperef.tag == "internaltyperef" then |
| local _typedeftypedef = gettypedef(_file,_internaltyperef.typename,"recordtypedef",sourcerangemin,sourcerangemax) |
| if _typedeftypedef then |
| -- refer the function used when the type is called |
| local _internaltyperef = apimodel._internaltyperef() |
| _internaltyperef.typename = _functiontypedef.name |
| _typedeftypedef.call =_internaltyperef |
| end |
| end |
| end |
| 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 |
| |
| attachmetadata(_lastapiobject, parsedcomment) |
| |
| -- 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 |