| -------------------------------------------------------------------------------- | |
| -- 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' } | |
| require 'metalua.walk' | |
| require 'metalua.walk.bindings' | |
| local ldp = require "models.ldparser" | |
| local apimodel = require "models.apimodel" | |
| 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 | |
| _recordtypedef = apimodel._recordtypedef(name) | |
| -- define sourcerange | |
| _recordtypedef.sourcerange.min = sourcerangemin | |
| _recordtypedef.sourcerange.max = sourcerangemax | |
| -- add to file | |
| _file:addtype(_recordtypedef) | |
| 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 _typref | |
| 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) | |
| if not dt_field.name then return nil end | |
| 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" | |
| local function additemtoparent(_file,_item,scope,sourcerangemin,sourcerangemax) | |
| if scope and not scope.module then | |
| if scope.type == "global" then | |
| _file:addglobalvar(_item) | |
| else | |
| local _recordtypedef = gettypedef (_file, scope.type ,"recordtypedef",sourcerangemin,sourcerangemax) | |
| _recordtypedef:addfield(_item) | |
| 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) | |
| -- Initialise function type naming | |
| resetfunctiontypeidgenerator() | |
| local _file = apimodel._file() | |
| local function handlecomment(comment) | |
| -- Extract information from tagged comments | |
| local parsedcomment = ldp.parse(comment[1]) | |
| -- Get tags from the languages | |
| local regulartags = parsedcomment and 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 | |
| _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 | |
| _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 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 | |
| -- 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 _item = createfield(dt_field,_file,sourcerangemin,sourcerangemax) | |
| _item.shortdescription = parsedcomment.shortdescription | |
| _item.description = parsedcomment.description | |
| _lastapiobject = _item | |
| -- define sourcerange | |
| local sourcerangemin = comment.lineinfo.first.offset | |
| local sourcerangemax = comment.lineinfo.last.offset | |
| _item.sourcerange.min = sourcerangemin | |
| _item.sourcerange.max = sourcerangemax | |
| -- add item to its parent | |
| local scope = regulartags["field"][1].parent | |
| additemtoparent(_file,_item,scope,sourcerangemin,sourcerangemax) | |
| elseif regulartags["function"] and regulartags["function"][1].name then | |
| -- create item | |
| local _item = apimodel._item(regulartags["function"][1].name) | |
| _item.shortdescription = parsedcomment.shortdescription | |
| _item.description = parsedcomment.description | |
| _lastapiobject = _item | |
| -- 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"][1].parent | |
| additemtoparent(_file,_item,scope,sourcerangemin,sourcerangemax) | |
| end | |
| -- | |
| -- Store user defined tags | |
| -- | |
| local thirdtags = parsedcomment and parsedcomment.unknowntags | |
| if thirdtags and _lastapiobject 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 | |
| end | |
| 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 | |
| local cfg = { expr={down=parsecomment}, stat={down=parsecomment}} | |
| walk.block(cfg, ast) | |
| return _file | |
| end | |
| return M |