mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-10-29 16:58:06 +00:00
* Installs luarocks WITH it's executable (easy to install other rocks if necessary) * Use Lua supplied with luarocks * Create Utils/luadocumentor.bat, which works with RELATIVE PATH ! -> Everybody can generate the doc * Updated launch files accordingly
862 lines
30 KiB
Plaintext
862 lines
30 KiB
Plaintext
--------------------------------------------------------------------------------
|
|
-- 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 {identifier, min, max, body}
|
|
| `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, `String{fieldname} } ->
|
|
if not expr.lineinfo then return nil end
|
|
-- create index
|
|
local _expression = createexpr(innerexpr,_block)
|
|
if _expression then _expr = internalmodel._index(_expression,fieldname) 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("___") -- no name for inline type
|
|
-- 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
|
|
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
|
|
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 = ""
|
|
|
|
-- WORKAROUND FOR BUG 421622: [outline]module selection in outline does not select it in texteditor
|
|
--_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
|