| -------------------------------------------------------------------------------- |
| -- 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 {...} |
| | `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, rightpart } -> |
| if not expr.lineinfo then return nil end |
| -- create index |
| local _expression = createexpr(innerexpr,_block) |
| if _expression then |
| if rightpart and rightpart.tag=='String' then |
| _expr = internalmodel._index(_expression,rightpart[1]) |
| else |
| _expr = internalmodel._index(_expression,nil) |
| end |
| 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("table") |
| -- 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 |
| |
| local apitype = apidocitem:resolvetype(file) |
| if apitype and apitype.tag == "functiontypedef" and apidocitem.metadata then |
| apitype.metadata = apidocitem.metadata |
| 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 |
| else |
| -- this is the case where we have |
| -- function expr.f(self) or function expr:f() |
| -- we suppose the type of self is the type of "expr" |
| if itemname == "self" and i==1 and binder.parent.tag == "Set" then |
| match binder.parent with |
| |`Set { {`Index { right , string}}, {...}} -> |
| -- if node is an expression supported |
| local supportedexpr = expreto_expression[right] |
| if supportedexpr then |
| -- create expression type ref |
| local selftype = apimodel._exprtyperef(supportedexpr,1) |
| return createitem(itemname,id,selftype) |
| 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 = "" |
| |
| _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 |