2071 lines
57 KiB
Lua
2071 lines
57 KiB
Lua
|
local m = require 'lpeglabel'
|
|||
|
local re = require 'parser.relabel'
|
|||
|
local guide = require 'parser.guide'
|
|||
|
local compile = require 'parser.compile'
|
|||
|
local util = require 'utility'
|
|||
|
|
|||
|
local TokenTypes, TokenStarts, TokenFinishs, TokenContents, TokenMarks
|
|||
|
---@type integer
|
|||
|
local Ci
|
|||
|
---@type integer
|
|||
|
local Offset
|
|||
|
local pushWarning, NextComment, Lines
|
|||
|
local parseType, parseTypeUnit
|
|||
|
---@type any
|
|||
|
local Parser = re.compile([[
|
|||
|
Main <- (Token / Sp)*
|
|||
|
Sp <- %s+
|
|||
|
X16 <- [a-fA-F0-9]
|
|||
|
Token <- Integer / Name / String / Code / Symbol
|
|||
|
Name <- ({} {%name} {})
|
|||
|
-> Name
|
|||
|
Integer <- ({} {'-'? [0-9]+} !'.' {})
|
|||
|
-> Integer
|
|||
|
Code <- ({} '`' { (!'`' .)*} '`' {})
|
|||
|
-> Code
|
|||
|
String <- ({} StringDef {})
|
|||
|
-> String
|
|||
|
StringDef <- {'"'}
|
|||
|
{~(Esc / !'"' .)*~} -> 1
|
|||
|
('"'?)
|
|||
|
/ {"'"}
|
|||
|
{~(Esc / !"'" .)*~} -> 1
|
|||
|
("'"?)
|
|||
|
/ '[' {:eq: '='* :} '['
|
|||
|
=eq -> LongStringMark
|
|||
|
{(!StringClose .)*} -> 1
|
|||
|
StringClose?
|
|||
|
StringClose <- ']' =eq ']'
|
|||
|
Esc <- '\' -> ''
|
|||
|
EChar
|
|||
|
EChar <- 'a' -> ea
|
|||
|
/ 'b' -> eb
|
|||
|
/ 'f' -> ef
|
|||
|
/ 'n' -> en
|
|||
|
/ 'r' -> er
|
|||
|
/ 't' -> et
|
|||
|
/ 'v' -> ev
|
|||
|
/ '\'
|
|||
|
/ '"'
|
|||
|
/ "'"
|
|||
|
/ %nl
|
|||
|
/ ('z' (%nl / %s)*) -> ''
|
|||
|
/ ('x' {X16 X16}) -> Char16
|
|||
|
/ ([0-9] [0-9]? [0-9]?) -> Char10
|
|||
|
/ ('u{' {X16*} '}') -> CharUtf8
|
|||
|
Symbol <- ({} {
|
|||
|
[:|,;<>()?+#{}]
|
|||
|
/ '[]'
|
|||
|
/ '...'
|
|||
|
/ '['
|
|||
|
/ ']'
|
|||
|
/ '-' !'-'
|
|||
|
} {})
|
|||
|
-> Symbol
|
|||
|
]], {
|
|||
|
s = m.S' \t\v\f',
|
|||
|
ea = '\a',
|
|||
|
eb = '\b',
|
|||
|
ef = '\f',
|
|||
|
en = '\n',
|
|||
|
er = '\r',
|
|||
|
et = '\t',
|
|||
|
ev = '\v',
|
|||
|
name = (m.R('az', 'AZ', '09', '\x80\xff') + m.S('_')) * (m.R('az', 'AZ', '__', '09', '\x80\xff') + m.S('_.*-'))^0,
|
|||
|
Char10 = function (char)
|
|||
|
---@type integer?
|
|||
|
char = tonumber(char)
|
|||
|
if not char or char < 0 or char > 255 then
|
|||
|
return ''
|
|||
|
end
|
|||
|
return string.char(char)
|
|||
|
end,
|
|||
|
Char16 = function (char)
|
|||
|
return string.char(tonumber(char, 16))
|
|||
|
end,
|
|||
|
CharUtf8 = function (char)
|
|||
|
if #char == 0 then
|
|||
|
return ''
|
|||
|
end
|
|||
|
local v = tonumber(char, 16)
|
|||
|
if not v then
|
|||
|
return ''
|
|||
|
end
|
|||
|
if v >= 0 and v <= 0x10FFFF then
|
|||
|
return utf8.char(v)
|
|||
|
end
|
|||
|
return ''
|
|||
|
end,
|
|||
|
LongStringMark = function (back)
|
|||
|
return '[' .. back .. '['
|
|||
|
end,
|
|||
|
Name = function (start, content, finish)
|
|||
|
Ci = Ci + 1
|
|||
|
TokenTypes[Ci] = 'name'
|
|||
|
TokenStarts[Ci] = start
|
|||
|
TokenFinishs[Ci] = finish - 1
|
|||
|
TokenContents[Ci] = content
|
|||
|
end,
|
|||
|
String = function (start, mark, content, finish)
|
|||
|
Ci = Ci + 1
|
|||
|
TokenTypes[Ci] = 'string'
|
|||
|
TokenStarts[Ci] = start
|
|||
|
TokenFinishs[Ci] = finish - 1
|
|||
|
TokenContents[Ci] = content
|
|||
|
TokenMarks[Ci] = mark
|
|||
|
end,
|
|||
|
Integer = function (start, content, finish)
|
|||
|
Ci = Ci + 1
|
|||
|
TokenTypes[Ci] = 'integer'
|
|||
|
TokenStarts[Ci] = start
|
|||
|
TokenFinishs[Ci] = finish - 1
|
|||
|
TokenContents[Ci] = math.tointeger(content)
|
|||
|
end,
|
|||
|
Code = function (start, content, finish)
|
|||
|
Ci = Ci + 1
|
|||
|
TokenTypes[Ci] = 'code'
|
|||
|
TokenStarts[Ci] = start
|
|||
|
TokenFinishs[Ci] = finish - 1
|
|||
|
TokenContents[Ci] = content
|
|||
|
end,
|
|||
|
Symbol = function (start, content, finish)
|
|||
|
Ci = Ci + 1
|
|||
|
TokenTypes[Ci] = 'symbol'
|
|||
|
TokenStarts[Ci] = start
|
|||
|
TokenFinishs[Ci] = finish - 1
|
|||
|
TokenContents[Ci] = content
|
|||
|
end,
|
|||
|
})
|
|||
|
|
|||
|
---@alias parser.visibleType 'public' | 'protected' | 'private' | 'package'
|
|||
|
|
|||
|
---@class parser.object
|
|||
|
---@field literal boolean
|
|||
|
---@field signs parser.object[]
|
|||
|
---@field originalComment parser.object
|
|||
|
---@field as? parser.object
|
|||
|
---@field touch? integer
|
|||
|
---@field module? string
|
|||
|
---@field async? boolean
|
|||
|
---@field versions? table[]
|
|||
|
---@field names? parser.object[]
|
|||
|
---@field path? string
|
|||
|
---@field bindComments? parser.object[]
|
|||
|
---@field visible? parser.visibleType
|
|||
|
---@field operators? parser.object[]
|
|||
|
---@field calls? parser.object[]
|
|||
|
---@field generics? parser.object[]
|
|||
|
---@field generic? parser.object
|
|||
|
|
|||
|
local function parseTokens(text, offset)
|
|||
|
Ci = 0
|
|||
|
Offset = offset
|
|||
|
TokenTypes = {}
|
|||
|
TokenStarts = {}
|
|||
|
TokenFinishs = {}
|
|||
|
TokenContents = {}
|
|||
|
TokenMarks = {}
|
|||
|
Parser:match(text)
|
|||
|
Ci = 0
|
|||
|
end
|
|||
|
|
|||
|
local function peekToken(offset)
|
|||
|
offset = offset or 1
|
|||
|
return TokenTypes[Ci + offset], TokenContents[Ci + offset]
|
|||
|
end
|
|||
|
|
|||
|
---@return string? tokenType
|
|||
|
---@return string? tokenContent
|
|||
|
local function nextToken()
|
|||
|
Ci = Ci + 1
|
|||
|
if not TokenTypes[Ci] then
|
|||
|
Ci = Ci - 1
|
|||
|
return nil, nil
|
|||
|
end
|
|||
|
return TokenTypes[Ci], TokenContents[Ci]
|
|||
|
end
|
|||
|
|
|||
|
local function checkToken(tp, content, offset)
|
|||
|
offset = offset or 0
|
|||
|
return TokenTypes[Ci + offset] == tp
|
|||
|
and TokenContents[Ci + offset] == content
|
|||
|
end
|
|||
|
|
|||
|
local function getStart()
|
|||
|
if Ci == 0 then
|
|||
|
return Offset
|
|||
|
end
|
|||
|
return TokenStarts[Ci] + Offset
|
|||
|
end
|
|||
|
|
|||
|
---@return integer
|
|||
|
local function getFinish()
|
|||
|
if Ci == 0 then
|
|||
|
return Offset
|
|||
|
end
|
|||
|
return TokenFinishs[Ci] + Offset + 1
|
|||
|
end
|
|||
|
|
|||
|
local function getMark()
|
|||
|
return TokenMarks[Ci]
|
|||
|
end
|
|||
|
|
|||
|
local function try(callback)
|
|||
|
local savePoint = Ci
|
|||
|
-- rollback
|
|||
|
local suc = callback()
|
|||
|
if not suc then
|
|||
|
Ci = savePoint
|
|||
|
end
|
|||
|
return suc
|
|||
|
end
|
|||
|
|
|||
|
local function parseName(tp, parent)
|
|||
|
local nameTp, nameText = peekToken()
|
|||
|
if nameTp ~= 'name' then
|
|||
|
return nil
|
|||
|
end
|
|||
|
nextToken()
|
|||
|
local name = {
|
|||
|
type = tp,
|
|||
|
start = getStart(),
|
|||
|
finish = getFinish(),
|
|||
|
parent = parent,
|
|||
|
[1] = nameText,
|
|||
|
}
|
|||
|
return name
|
|||
|
end
|
|||
|
|
|||
|
local function nextSymbolOrError(symbol)
|
|||
|
if checkToken('symbol', symbol, 1) then
|
|||
|
nextToken()
|
|||
|
return true
|
|||
|
end
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_SYMBOL',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
info = {
|
|||
|
symbol = symbol,
|
|||
|
}
|
|||
|
}
|
|||
|
return false
|
|||
|
end
|
|||
|
|
|||
|
local function parseIndexField(parent)
|
|||
|
if not checkToken('symbol', '[', 1) then
|
|||
|
return nil
|
|||
|
end
|
|||
|
nextToken()
|
|||
|
local field = parseType(parent)
|
|||
|
nextSymbolOrError ']'
|
|||
|
return field
|
|||
|
end
|
|||
|
|
|||
|
local function parseTable(parent)
|
|||
|
if not checkToken('symbol', '{', 1) then
|
|||
|
return nil
|
|||
|
end
|
|||
|
nextToken()
|
|||
|
local typeUnit = {
|
|||
|
type = 'doc.type.table',
|
|||
|
start = getStart(),
|
|||
|
parent = parent,
|
|||
|
fields = {},
|
|||
|
}
|
|||
|
|
|||
|
while true do
|
|||
|
if checkToken('symbol', '}', 1) then
|
|||
|
nextToken()
|
|||
|
break
|
|||
|
end
|
|||
|
local field = {
|
|||
|
type = 'doc.type.field',
|
|||
|
parent = typeUnit,
|
|||
|
}
|
|||
|
|
|||
|
do
|
|||
|
local needCloseParen
|
|||
|
if checkToken('symbol', '(', 1) then
|
|||
|
nextToken()
|
|||
|
needCloseParen = true
|
|||
|
end
|
|||
|
field.name = parseName('doc.field.name', field)
|
|||
|
or parseIndexField(field)
|
|||
|
if not field.name then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_FIELD_NAME',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
break
|
|||
|
end
|
|||
|
if not field.start then
|
|||
|
field.start = field.name.start
|
|||
|
end
|
|||
|
if checkToken('symbol', '?', 1) then
|
|||
|
nextToken()
|
|||
|
field.optional = true
|
|||
|
end
|
|||
|
field.finish = getFinish()
|
|||
|
if not nextSymbolOrError(':') then
|
|||
|
break
|
|||
|
end
|
|||
|
field.extends = parseType(field)
|
|||
|
if not field.extends then
|
|||
|
break
|
|||
|
end
|
|||
|
field.finish = getFinish()
|
|||
|
if needCloseParen then
|
|||
|
nextSymbolOrError ')'
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
typeUnit.fields[#typeUnit.fields+1] = field
|
|||
|
if checkToken('symbol', ',', 1)
|
|||
|
or checkToken('symbol', ';', 1) then
|
|||
|
nextToken()
|
|||
|
else
|
|||
|
nextSymbolOrError('}')
|
|||
|
break
|
|||
|
end
|
|||
|
end
|
|||
|
typeUnit.finish = getFinish()
|
|||
|
return typeUnit
|
|||
|
end
|
|||
|
|
|||
|
local function parseSigns(parent)
|
|||
|
if not checkToken('symbol', '<', 1) then
|
|||
|
return nil
|
|||
|
end
|
|||
|
nextToken()
|
|||
|
local signs = {}
|
|||
|
while true do
|
|||
|
local sign = parseName('doc.generic.name', parent)
|
|||
|
if not sign then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_SIGN_NAME',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
break
|
|||
|
end
|
|||
|
signs[#signs+1] = sign
|
|||
|
if checkToken('symbol', ',', 1) then
|
|||
|
nextToken()
|
|||
|
else
|
|||
|
break
|
|||
|
end
|
|||
|
end
|
|||
|
nextSymbolOrError '>'
|
|||
|
return signs
|
|||
|
end
|
|||
|
|
|||
|
local function parseDots(tp, parent)
|
|||
|
if not checkToken('symbol', '...', 1) then
|
|||
|
return
|
|||
|
end
|
|||
|
nextToken()
|
|||
|
local dots = {
|
|||
|
type = tp,
|
|||
|
start = getStart(),
|
|||
|
finish = getFinish(),
|
|||
|
parent = parent,
|
|||
|
[1] = '...',
|
|||
|
}
|
|||
|
return dots
|
|||
|
end
|
|||
|
|
|||
|
local function parseTypeUnitFunction(parent)
|
|||
|
if not checkToken('name', 'fun', 1) then
|
|||
|
return nil
|
|||
|
end
|
|||
|
nextToken()
|
|||
|
local typeUnit = {
|
|||
|
type = 'doc.type.function',
|
|||
|
parent = parent,
|
|||
|
start = getStart(),
|
|||
|
args = {},
|
|||
|
returns = {},
|
|||
|
}
|
|||
|
if not nextSymbolOrError('(') then
|
|||
|
return nil
|
|||
|
end
|
|||
|
while true do
|
|||
|
if checkToken('symbol', ')', 1) then
|
|||
|
nextToken()
|
|||
|
break
|
|||
|
end
|
|||
|
local arg = {
|
|||
|
type = 'doc.type.arg',
|
|||
|
parent = typeUnit,
|
|||
|
}
|
|||
|
arg.name = parseName('doc.type.arg.name', arg)
|
|||
|
or parseDots('doc.type.arg.name', arg)
|
|||
|
if not arg.name then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_ARG_NAME',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
break
|
|||
|
end
|
|||
|
if not arg.start then
|
|||
|
arg.start = arg.name.start
|
|||
|
end
|
|||
|
if checkToken('symbol', '?', 1) then
|
|||
|
nextToken()
|
|||
|
arg.optional = true
|
|||
|
end
|
|||
|
arg.finish = getFinish()
|
|||
|
if checkToken('symbol', ':', 1) then
|
|||
|
nextToken()
|
|||
|
arg.extends = parseType(arg)
|
|||
|
end
|
|||
|
arg.finish = getFinish()
|
|||
|
typeUnit.args[#typeUnit.args+1] = arg
|
|||
|
if checkToken('symbol', ',', 1) then
|
|||
|
nextToken()
|
|||
|
else
|
|||
|
nextSymbolOrError(')')
|
|||
|
break
|
|||
|
end
|
|||
|
end
|
|||
|
if checkToken('symbol', ':', 1) then
|
|||
|
nextToken()
|
|||
|
local needCloseParen
|
|||
|
if checkToken('symbol', '(', 1) then
|
|||
|
nextToken()
|
|||
|
needCloseParen = true
|
|||
|
end
|
|||
|
while true do
|
|||
|
local name
|
|||
|
try(function ()
|
|||
|
local returnName = parseName('doc.return.name', typeUnit)
|
|||
|
or parseDots('doc.return.name', typeUnit)
|
|||
|
if not returnName then
|
|||
|
return false
|
|||
|
end
|
|||
|
if checkToken('symbol', ':', 1) then
|
|||
|
nextToken()
|
|||
|
name = returnName
|
|||
|
return true
|
|||
|
end
|
|||
|
if returnName[1] == '...' then
|
|||
|
name = returnName
|
|||
|
return false
|
|||
|
end
|
|||
|
return false
|
|||
|
end)
|
|||
|
local rtn = parseType(typeUnit)
|
|||
|
if not rtn then
|
|||
|
break
|
|||
|
end
|
|||
|
rtn.name = name
|
|||
|
if checkToken('symbol', '?', 1) then
|
|||
|
nextToken()
|
|||
|
rtn.optional = true
|
|||
|
end
|
|||
|
typeUnit.returns[#typeUnit.returns+1] = rtn
|
|||
|
if checkToken('symbol', ',', 1) then
|
|||
|
nextToken()
|
|||
|
else
|
|||
|
break
|
|||
|
end
|
|||
|
end
|
|||
|
if needCloseParen then
|
|||
|
nextSymbolOrError ')'
|
|||
|
end
|
|||
|
end
|
|||
|
typeUnit.finish = getFinish()
|
|||
|
return typeUnit
|
|||
|
end
|
|||
|
|
|||
|
local function parseFunction(parent)
|
|||
|
local _, content = peekToken()
|
|||
|
if content == 'async' then
|
|||
|
nextToken()
|
|||
|
local pos = getStart()
|
|||
|
local tp, cont = peekToken()
|
|||
|
if tp == 'name' then
|
|||
|
if cont == 'fun' then
|
|||
|
local func = parseTypeUnit(parent)
|
|||
|
if func then
|
|||
|
func.async = true
|
|||
|
func.asyncPos = pos
|
|||
|
return func
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
if content == 'fun' then
|
|||
|
return parseTypeUnitFunction(parent)
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
local function parseTypeUnitArray(parent, node)
|
|||
|
if not checkToken('symbol', '[]', 1) then
|
|||
|
return nil
|
|||
|
end
|
|||
|
nextToken()
|
|||
|
local result = {
|
|||
|
type = 'doc.type.array',
|
|||
|
start = node.start,
|
|||
|
finish = getFinish(),
|
|||
|
node = node,
|
|||
|
parent = parent,
|
|||
|
}
|
|||
|
node.parent = result
|
|||
|
return result
|
|||
|
end
|
|||
|
|
|||
|
local function parseTypeUnitSign(parent, node)
|
|||
|
if not checkToken('symbol', '<', 1) then
|
|||
|
return nil
|
|||
|
end
|
|||
|
nextToken()
|
|||
|
local result = {
|
|||
|
type = 'doc.type.sign',
|
|||
|
start = node.start,
|
|||
|
finish = getFinish(),
|
|||
|
node = node,
|
|||
|
parent = parent,
|
|||
|
signs = {},
|
|||
|
}
|
|||
|
node.parent = result
|
|||
|
while true do
|
|||
|
local sign = parseType(result)
|
|||
|
if not sign then
|
|||
|
pushWarning {
|
|||
|
type = 'LUA_DOC_MISS_SIGN',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
break
|
|||
|
end
|
|||
|
result.signs[#result.signs+1] = sign
|
|||
|
if checkToken('symbol', ',', 1) then
|
|||
|
nextToken()
|
|||
|
else
|
|||
|
break
|
|||
|
end
|
|||
|
end
|
|||
|
nextSymbolOrError '>'
|
|||
|
result.finish = getFinish()
|
|||
|
return result
|
|||
|
end
|
|||
|
|
|||
|
local function parseString(parent)
|
|||
|
local tp, content = peekToken()
|
|||
|
if not tp or tp ~= 'string' then
|
|||
|
return nil
|
|||
|
end
|
|||
|
|
|||
|
nextToken()
|
|||
|
local mark = getMark()
|
|||
|
-- compatibility
|
|||
|
if content:sub(1, 1) == '"'
|
|||
|
or content:sub(1, 1) == "'" then
|
|||
|
if content:sub(1, 1) == content:sub(-1, -1) then
|
|||
|
mark = content:sub(1, 1)
|
|||
|
content = content:sub(2, -2)
|
|||
|
end
|
|||
|
end
|
|||
|
local str = {
|
|||
|
type = 'doc.type.string',
|
|||
|
start = getStart(),
|
|||
|
finish = getFinish(),
|
|||
|
parent = parent,
|
|||
|
[1] = content,
|
|||
|
[2] = mark,
|
|||
|
}
|
|||
|
return str
|
|||
|
end
|
|||
|
|
|||
|
local function parseCode(parent)
|
|||
|
local tp, content = peekToken()
|
|||
|
if not tp or tp ~= 'code' then
|
|||
|
return nil
|
|||
|
end
|
|||
|
nextToken()
|
|||
|
local code = {
|
|||
|
type = 'doc.type.code',
|
|||
|
start = getStart(),
|
|||
|
finish = getFinish(),
|
|||
|
parent = parent,
|
|||
|
[1] = content,
|
|||
|
}
|
|||
|
return code
|
|||
|
end
|
|||
|
|
|||
|
local function parseInteger(parent)
|
|||
|
local tp, content = peekToken()
|
|||
|
if not tp or tp ~= 'integer' then
|
|||
|
return nil
|
|||
|
end
|
|||
|
|
|||
|
nextToken()
|
|||
|
local integer = {
|
|||
|
type = 'doc.type.integer',
|
|||
|
start = getStart(),
|
|||
|
finish = getFinish(),
|
|||
|
parent = parent,
|
|||
|
[1] = content,
|
|||
|
}
|
|||
|
return integer
|
|||
|
end
|
|||
|
|
|||
|
local function parseBoolean(parent)
|
|||
|
local tp, content = peekToken()
|
|||
|
if not tp
|
|||
|
or tp ~= 'name'
|
|||
|
or (content ~= 'true' and content ~= 'false') then
|
|||
|
return nil
|
|||
|
end
|
|||
|
|
|||
|
nextToken()
|
|||
|
local boolean = {
|
|||
|
type = 'doc.type.boolean',
|
|||
|
start = getStart(),
|
|||
|
finish = getFinish(),
|
|||
|
parent = parent,
|
|||
|
[1] = content == 'true' and true or false,
|
|||
|
}
|
|||
|
return boolean
|
|||
|
end
|
|||
|
|
|||
|
local function parseParen(parent)
|
|||
|
if not checkToken('symbol', '(', 1) then
|
|||
|
return
|
|||
|
end
|
|||
|
nextToken()
|
|||
|
local tp = parseType(parent)
|
|||
|
nextSymbolOrError(')')
|
|||
|
return tp
|
|||
|
end
|
|||
|
|
|||
|
function parseTypeUnit(parent)
|
|||
|
local result = parseFunction(parent)
|
|||
|
or parseTable(parent)
|
|||
|
or parseString(parent)
|
|||
|
or parseCode(parent)
|
|||
|
or parseInteger(parent)
|
|||
|
or parseBoolean(parent)
|
|||
|
or parseParen(parent)
|
|||
|
if not result then
|
|||
|
result = parseName('doc.type.name', parent)
|
|||
|
or parseDots('doc.type.name', parent)
|
|||
|
if not result then
|
|||
|
return nil
|
|||
|
end
|
|||
|
if result[1] == '...' then
|
|||
|
result[1] = 'unknown'
|
|||
|
end
|
|||
|
end
|
|||
|
while true do
|
|||
|
local newResult = parseTypeUnitSign(parent, result)
|
|||
|
if not newResult then
|
|||
|
break
|
|||
|
end
|
|||
|
result = newResult
|
|||
|
end
|
|||
|
while true do
|
|||
|
local newResult = parseTypeUnitArray(parent, result)
|
|||
|
if not newResult then
|
|||
|
break
|
|||
|
end
|
|||
|
result = newResult
|
|||
|
end
|
|||
|
return result
|
|||
|
end
|
|||
|
|
|||
|
local function parseResume(parent)
|
|||
|
local default, additional
|
|||
|
if checkToken('symbol', '>', 1) then
|
|||
|
nextToken()
|
|||
|
default = true
|
|||
|
end
|
|||
|
|
|||
|
if checkToken('symbol', '+', 1) then
|
|||
|
nextToken()
|
|||
|
additional = true
|
|||
|
end
|
|||
|
|
|||
|
local result = parseTypeUnit(parent)
|
|||
|
if result then
|
|||
|
result.default = default
|
|||
|
result.additional = additional
|
|||
|
end
|
|||
|
|
|||
|
return result
|
|||
|
end
|
|||
|
|
|||
|
function parseType(parent)
|
|||
|
local result = {
|
|||
|
type = 'doc.type',
|
|||
|
parent = parent,
|
|||
|
types = {},
|
|||
|
}
|
|||
|
while true do
|
|||
|
local typeUnit = parseTypeUnit(result)
|
|||
|
if not typeUnit then
|
|||
|
break
|
|||
|
end
|
|||
|
|
|||
|
result.types[#result.types+1] = typeUnit
|
|||
|
if not result.start then
|
|||
|
result.start = typeUnit.start
|
|||
|
end
|
|||
|
|
|||
|
if not checkToken('symbol', '|', 1) then
|
|||
|
break
|
|||
|
end
|
|||
|
nextToken()
|
|||
|
end
|
|||
|
if not result.start then
|
|||
|
result.start = getFinish()
|
|||
|
end
|
|||
|
if checkToken('symbol', '?', 1) then
|
|||
|
nextToken()
|
|||
|
result.optional = true
|
|||
|
end
|
|||
|
result.finish = getFinish()
|
|||
|
result.firstFinish = result.finish
|
|||
|
|
|||
|
local row = guide.rowColOf(result.finish)
|
|||
|
|
|||
|
local function pushResume()
|
|||
|
local comments
|
|||
|
for i = 0, 100 do
|
|||
|
local nextComm = NextComment(i,'peek')
|
|||
|
if not nextComm then
|
|||
|
return false
|
|||
|
end
|
|||
|
local nextCommRow = guide.rowColOf(nextComm.start)
|
|||
|
local currentRow = row + i + 1
|
|||
|
if currentRow < nextCommRow then
|
|||
|
return false
|
|||
|
end
|
|||
|
if nextComm.text:match '^%-%s*%@' then
|
|||
|
return false
|
|||
|
else
|
|||
|
local resumeHead = nextComm.text:match '^%-%s*%|'
|
|||
|
if resumeHead then
|
|||
|
NextComment(i)
|
|||
|
row = row + i + 1
|
|||
|
local finishPos = nextComm.text:find('#', #resumeHead + 1) or #nextComm.text
|
|||
|
parseTokens(nextComm.text:sub(#resumeHead + 1, finishPos), nextComm.start + #resumeHead + 1)
|
|||
|
local resume = parseResume(result)
|
|||
|
if resume then
|
|||
|
if comments then
|
|||
|
resume.comment = table.concat(comments, '\n')
|
|||
|
else
|
|||
|
resume.comment = nextComm.text:match('%s*#?%s*(.+)', resume.finish - nextComm.start)
|
|||
|
end
|
|||
|
result.types[#result.types+1] = resume
|
|||
|
result.finish = resume.finish
|
|||
|
end
|
|||
|
comments = nil
|
|||
|
return true
|
|||
|
else
|
|||
|
if not comments then
|
|||
|
comments = {}
|
|||
|
end
|
|||
|
comments[#comments+1] = nextComm.text:sub(2)
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
return false
|
|||
|
end
|
|||
|
|
|||
|
local checkResume = true
|
|||
|
local nsymbol, ncontent = peekToken()
|
|||
|
if nsymbol == 'symbol' then
|
|||
|
if ncontent == ','
|
|||
|
or ncontent == ':'
|
|||
|
or ncontent == '|'
|
|||
|
or ncontent == ')'
|
|||
|
or ncontent == '}' then
|
|||
|
checkResume = false
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
if checkResume then
|
|||
|
while pushResume() do end
|
|||
|
end
|
|||
|
|
|||
|
if #result.types == 0 then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_TYPE_NAME',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
return nil
|
|||
|
end
|
|||
|
return result
|
|||
|
end
|
|||
|
|
|||
|
local docSwitch = util.switch()
|
|||
|
: case 'class'
|
|||
|
: call(function ()
|
|||
|
local result = {
|
|||
|
type = 'doc.class',
|
|||
|
fields = {},
|
|||
|
operators = {},
|
|||
|
calls = {},
|
|||
|
}
|
|||
|
result.class = parseName('doc.class.name', result)
|
|||
|
if not result.class then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_CLASS_NAME',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
return nil
|
|||
|
end
|
|||
|
result.start = getStart()
|
|||
|
result.finish = getFinish()
|
|||
|
result.signs = parseSigns(result)
|
|||
|
if not checkToken('symbol', ':', 1) then
|
|||
|
return result
|
|||
|
end
|
|||
|
nextToken()
|
|||
|
|
|||
|
result.extends = {}
|
|||
|
|
|||
|
while true do
|
|||
|
local extend = parseName('doc.extends.name', result)
|
|||
|
or parseTable(result)
|
|||
|
if not extend then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_CLASS_EXTENDS_NAME',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
return result
|
|||
|
end
|
|||
|
result.extends[#result.extends+1] = extend
|
|||
|
result.finish = getFinish()
|
|||
|
if not checkToken('symbol', ',', 1) then
|
|||
|
break
|
|||
|
end
|
|||
|
nextToken()
|
|||
|
end
|
|||
|
return result
|
|||
|
end)
|
|||
|
: case 'type'
|
|||
|
: call(function ()
|
|||
|
local first = parseType()
|
|||
|
if not first then
|
|||
|
return nil
|
|||
|
end
|
|||
|
local rests
|
|||
|
while checkToken('symbol', ',', 1) do
|
|||
|
nextToken()
|
|||
|
local rest = parseType()
|
|||
|
if not rests then
|
|||
|
rests = {}
|
|||
|
end
|
|||
|
rests[#rests+1] = rest
|
|||
|
end
|
|||
|
return first, rests
|
|||
|
end)
|
|||
|
: case 'alias'
|
|||
|
: call(function ()
|
|||
|
local result = {
|
|||
|
type = 'doc.alias',
|
|||
|
}
|
|||
|
result.alias = parseName('doc.alias.name', result)
|
|||
|
if not result.alias then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_ALIAS_NAME',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
return nil
|
|||
|
end
|
|||
|
result.start = getStart()
|
|||
|
result.signs = parseSigns(result)
|
|||
|
result.extends = parseType(result)
|
|||
|
if not result.extends then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_ALIAS_EXTENDS',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
return nil
|
|||
|
end
|
|||
|
result.finish = getFinish()
|
|||
|
return result
|
|||
|
end)
|
|||
|
: case 'param'
|
|||
|
: call(function ()
|
|||
|
local result = {
|
|||
|
type = 'doc.param',
|
|||
|
}
|
|||
|
result.param = parseName('doc.param.name', result)
|
|||
|
or parseDots('doc.param.name', result)
|
|||
|
if not result.param then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_PARAM_NAME',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
return nil
|
|||
|
end
|
|||
|
if checkToken('symbol', '?', 1) then
|
|||
|
nextToken()
|
|||
|
result.optional = true
|
|||
|
end
|
|||
|
result.start = result.param.start
|
|||
|
result.finish = getFinish()
|
|||
|
result.extends = parseType(result)
|
|||
|
if not result.extends then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_PARAM_EXTENDS',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
return result
|
|||
|
end
|
|||
|
result.finish = getFinish()
|
|||
|
result.firstFinish = result.extends.firstFinish
|
|||
|
return result
|
|||
|
end)
|
|||
|
: case 'return'
|
|||
|
: call(function ()
|
|||
|
local result = {
|
|||
|
type = 'doc.return',
|
|||
|
returns = {},
|
|||
|
}
|
|||
|
while true do
|
|||
|
local dots = parseDots('doc.return.name')
|
|||
|
if dots then
|
|||
|
Ci = Ci - 1
|
|||
|
end
|
|||
|
local docType = parseType(result)
|
|||
|
if not docType then
|
|||
|
break
|
|||
|
end
|
|||
|
if not result.start then
|
|||
|
result.start = docType.start
|
|||
|
end
|
|||
|
if checkToken('symbol', '?', 1) then
|
|||
|
nextToken()
|
|||
|
docType.optional = true
|
|||
|
end
|
|||
|
if dots then
|
|||
|
docType.name = dots
|
|||
|
dots.parent = docType
|
|||
|
else
|
|||
|
docType.name = parseName('doc.return.name', docType)
|
|||
|
or parseDots('doc.return.name', docType)
|
|||
|
end
|
|||
|
result.returns[#result.returns+1] = docType
|
|||
|
if not checkToken('symbol', ',', 1) then
|
|||
|
break
|
|||
|
end
|
|||
|
nextToken()
|
|||
|
end
|
|||
|
if #result.returns == 0 then
|
|||
|
return nil
|
|||
|
end
|
|||
|
result.finish = getFinish()
|
|||
|
return result
|
|||
|
end)
|
|||
|
: case 'field'
|
|||
|
: call(function ()
|
|||
|
local result = {
|
|||
|
type = 'doc.field',
|
|||
|
}
|
|||
|
try(function ()
|
|||
|
local tp, value = nextToken()
|
|||
|
if tp == 'name' then
|
|||
|
if value == 'public'
|
|||
|
or value == 'protected'
|
|||
|
or value == 'private'
|
|||
|
or value == 'package' then
|
|||
|
local tp2 = peekToken(1)
|
|||
|
local tp3 = peekToken(2)
|
|||
|
if tp2 == 'name' and not tp3 then
|
|||
|
return false
|
|||
|
end
|
|||
|
result.visible = value
|
|||
|
result.start = getStart()
|
|||
|
return true
|
|||
|
end
|
|||
|
end
|
|||
|
return false
|
|||
|
end)
|
|||
|
result.field = parseName('doc.field.name', result)
|
|||
|
or parseIndexField(result)
|
|||
|
if not result.field then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_FIELD_NAME',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
return nil
|
|||
|
end
|
|||
|
if not result.start then
|
|||
|
result.start = result.field.start
|
|||
|
end
|
|||
|
if checkToken('symbol', '?', 1) then
|
|||
|
nextToken()
|
|||
|
result.optional = true
|
|||
|
end
|
|||
|
result.extends = parseType(result)
|
|||
|
if not result.extends then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_FIELD_EXTENDS',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
return nil
|
|||
|
end
|
|||
|
result.finish = getFinish()
|
|||
|
return result
|
|||
|
end)
|
|||
|
: case 'generic'
|
|||
|
: call(function ()
|
|||
|
local result = {
|
|||
|
type = 'doc.generic',
|
|||
|
generics = {},
|
|||
|
}
|
|||
|
while true do
|
|||
|
local object = {
|
|||
|
type = 'doc.generic.object',
|
|||
|
parent = result,
|
|||
|
}
|
|||
|
object.generic = parseName('doc.generic.name', object)
|
|||
|
if not object.generic then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_GENERIC_NAME',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
return nil
|
|||
|
end
|
|||
|
object.start = object.generic.start
|
|||
|
if not result.start then
|
|||
|
result.start = object.start
|
|||
|
end
|
|||
|
if checkToken('symbol', ':', 1) then
|
|||
|
nextToken()
|
|||
|
object.extends = parseType(object)
|
|||
|
end
|
|||
|
object.finish = getFinish()
|
|||
|
result.generics[#result.generics+1] = object
|
|||
|
if not checkToken('symbol', ',', 1) then
|
|||
|
break
|
|||
|
end
|
|||
|
nextToken()
|
|||
|
end
|
|||
|
result.finish = getFinish()
|
|||
|
return result
|
|||
|
end)
|
|||
|
: case 'vararg'
|
|||
|
: call(function ()
|
|||
|
local result = {
|
|||
|
type = 'doc.vararg',
|
|||
|
}
|
|||
|
result.vararg = parseType(result)
|
|||
|
if not result.vararg then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_VARARG_TYPE',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
return
|
|||
|
end
|
|||
|
result.start = result.vararg.start
|
|||
|
result.finish = result.vararg.finish
|
|||
|
return result
|
|||
|
end)
|
|||
|
: case 'overload'
|
|||
|
: call(function ()
|
|||
|
local tp, name = peekToken()
|
|||
|
if tp ~= 'name'
|
|||
|
or (name ~= 'fun' and name ~= 'async') then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_FUN_AFTER_OVERLOAD',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
return nil
|
|||
|
end
|
|||
|
local result = {
|
|||
|
type = 'doc.overload',
|
|||
|
}
|
|||
|
result.overload = parseFunction(result)
|
|||
|
if not result.overload then
|
|||
|
return nil
|
|||
|
end
|
|||
|
result.overload.parent = result
|
|||
|
result.start = result.overload.start
|
|||
|
result.finish = result.overload.finish
|
|||
|
return result
|
|||
|
end)
|
|||
|
: case 'deprecated'
|
|||
|
: call(function ()
|
|||
|
return {
|
|||
|
type = 'doc.deprecated',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
end)
|
|||
|
: case 'meta'
|
|||
|
: call(function ()
|
|||
|
return {
|
|||
|
type = 'doc.meta',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
end)
|
|||
|
: case 'version'
|
|||
|
: call(function ()
|
|||
|
local result = {
|
|||
|
type = 'doc.version',
|
|||
|
versions = {},
|
|||
|
}
|
|||
|
while true do
|
|||
|
local tp, text = nextToken()
|
|||
|
if not tp then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_VERSION',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
break
|
|||
|
end
|
|||
|
if not result.start then
|
|||
|
result.start = getStart()
|
|||
|
end
|
|||
|
local version = {
|
|||
|
type = 'doc.version.unit',
|
|||
|
parent = result,
|
|||
|
start = getStart(),
|
|||
|
}
|
|||
|
if tp == 'symbol' then
|
|||
|
if text == '>' then
|
|||
|
version.ge = true
|
|||
|
elseif text == '<' then
|
|||
|
version.le = true
|
|||
|
end
|
|||
|
tp, text = nextToken()
|
|||
|
end
|
|||
|
if tp ~= 'name' then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_VERSION',
|
|||
|
start = getStart(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
break
|
|||
|
end
|
|||
|
version.version = tonumber(text) or text
|
|||
|
version.finish = getFinish()
|
|||
|
result.versions[#result.versions+1] = version
|
|||
|
if not checkToken('symbol', ',', 1) then
|
|||
|
break
|
|||
|
end
|
|||
|
nextToken()
|
|||
|
end
|
|||
|
if #result.versions == 0 then
|
|||
|
return nil
|
|||
|
end
|
|||
|
result.finish = getFinish()
|
|||
|
return result
|
|||
|
end)
|
|||
|
: case 'see'
|
|||
|
: call(function ()
|
|||
|
local result = {
|
|||
|
type = 'doc.see',
|
|||
|
}
|
|||
|
result.name = parseName('doc.see.name', result)
|
|||
|
if not result.name then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_SEE_NAME',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
return nil
|
|||
|
end
|
|||
|
result.start = result.name.start
|
|||
|
result.finish = result.name.finish
|
|||
|
return result
|
|||
|
end)
|
|||
|
: case 'diagnostic'
|
|||
|
: call(function ()
|
|||
|
local result = {
|
|||
|
type = 'doc.diagnostic',
|
|||
|
}
|
|||
|
local nextTP, mode = nextToken()
|
|||
|
if nextTP ~= 'name' then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_DIAG_MODE',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
return nil
|
|||
|
end
|
|||
|
result.mode = mode
|
|||
|
result.start = getStart()
|
|||
|
result.finish = getFinish()
|
|||
|
if mode ~= 'disable-next-line'
|
|||
|
and mode ~= 'disable-line'
|
|||
|
and mode ~= 'disable'
|
|||
|
and mode ~= 'enable' then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_ERROR_DIAG_MODE',
|
|||
|
start = result.start,
|
|||
|
finish = result.finish,
|
|||
|
}
|
|||
|
end
|
|||
|
|
|||
|
if checkToken('symbol', ':', 1) then
|
|||
|
nextToken()
|
|||
|
result.names = {}
|
|||
|
while true do
|
|||
|
local name = parseName('doc.diagnostic.name', result)
|
|||
|
if not name then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_DIAG_NAME',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
return result
|
|||
|
end
|
|||
|
result.names[#result.names+1] = name
|
|||
|
if not checkToken('symbol', ',', 1) then
|
|||
|
break
|
|||
|
end
|
|||
|
nextToken()
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
result.finish = getFinish()
|
|||
|
|
|||
|
return result
|
|||
|
end)
|
|||
|
: case 'module'
|
|||
|
: call(function ()
|
|||
|
local result = {
|
|||
|
type = 'doc.module',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
local tp, content = peekToken()
|
|||
|
if tp == 'string' then
|
|||
|
result.module = content
|
|||
|
nextToken()
|
|||
|
result.start = getStart()
|
|||
|
result.finish = getFinish()
|
|||
|
result.smark = getMark()
|
|||
|
else
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_MODULE_NAME',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
end
|
|||
|
return result
|
|||
|
end)
|
|||
|
: case 'async'
|
|||
|
: call(function ()
|
|||
|
return {
|
|||
|
type = 'doc.async',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
end)
|
|||
|
: case 'nodiscard'
|
|||
|
: call(function ()
|
|||
|
return {
|
|||
|
type = 'doc.nodiscard',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
end)
|
|||
|
: case 'as'
|
|||
|
: call(function ()
|
|||
|
local result = {
|
|||
|
type = 'doc.as',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
result.as = parseType(result)
|
|||
|
result.finish = getFinish()
|
|||
|
return result
|
|||
|
end)
|
|||
|
: case 'cast'
|
|||
|
: call(function ()
|
|||
|
local result = {
|
|||
|
type = 'doc.cast',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
casts = {},
|
|||
|
}
|
|||
|
|
|||
|
local loc = parseName('doc.cast.name', result)
|
|||
|
if not loc then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_LOCAL_NAME',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
return result
|
|||
|
end
|
|||
|
|
|||
|
result.loc = loc
|
|||
|
result.finish = loc.finish
|
|||
|
|
|||
|
while true do
|
|||
|
local block = {
|
|||
|
type = 'doc.cast.block',
|
|||
|
parent = result,
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
if checkToken('symbol', '+', 1) then
|
|||
|
block.mode = '+'
|
|||
|
nextToken()
|
|||
|
block.start = getStart()
|
|||
|
block.finish = getFinish()
|
|||
|
elseif checkToken('symbol', '-', 1) then
|
|||
|
block.mode = '-'
|
|||
|
nextToken()
|
|||
|
block.start = getStart()
|
|||
|
block.finish = getFinish()
|
|||
|
end
|
|||
|
|
|||
|
if checkToken('symbol', '?', 1) then
|
|||
|
block.optional = true
|
|||
|
nextToken()
|
|||
|
block.finish = getFinish()
|
|||
|
else
|
|||
|
block.extends = parseType(block)
|
|||
|
if block.extends then
|
|||
|
block.start = block.start or block.extends.start
|
|||
|
block.finish = block.extends.finish
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
if block.optional or block.extends then
|
|||
|
result.casts[#result.casts+1] = block
|
|||
|
end
|
|||
|
result.finish = block.finish
|
|||
|
|
|||
|
if checkToken('symbol', ',', 1) then
|
|||
|
nextToken()
|
|||
|
else
|
|||
|
break
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
return result
|
|||
|
end)
|
|||
|
: case 'operator'
|
|||
|
: call(function ()
|
|||
|
local result = {
|
|||
|
type = 'doc.operator',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
|
|||
|
local op = parseName('doc.operator.name', result)
|
|||
|
if not op then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_OPERATOR_NAME',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
return nil
|
|||
|
end
|
|||
|
result.op = op
|
|||
|
result.finish = op.finish
|
|||
|
|
|||
|
if checkToken('symbol', '(', 1) then
|
|||
|
nextToken()
|
|||
|
if checkToken('symbol', ')', 1) then
|
|||
|
nextToken()
|
|||
|
else
|
|||
|
local exp = parseType(result)
|
|||
|
if exp then
|
|||
|
result.exp = exp
|
|||
|
result.finish = exp.finish
|
|||
|
end
|
|||
|
nextSymbolOrError ')'
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
nextSymbolOrError ':'
|
|||
|
|
|||
|
local ret = parseType(result)
|
|||
|
if ret then
|
|||
|
result.extends = ret
|
|||
|
result.finish = ret.finish
|
|||
|
end
|
|||
|
|
|||
|
return result
|
|||
|
end)
|
|||
|
: case 'source'
|
|||
|
: call(function (doc)
|
|||
|
local fullSource = doc:sub(#'source' + 1)
|
|||
|
if not fullSource or fullSource == '' then
|
|||
|
return
|
|||
|
end
|
|||
|
fullSource = util.trim(fullSource)
|
|||
|
if fullSource == '' then
|
|||
|
return
|
|||
|
end
|
|||
|
local source, line, char = fullSource:match('^(.-):?(%d*):?(%d*)$')
|
|||
|
source = source or fullSource
|
|||
|
line = tonumber(line) or 1
|
|||
|
char = tonumber(char) or 0
|
|||
|
local result = {
|
|||
|
type = 'doc.source',
|
|||
|
start = getStart(),
|
|||
|
finish = getFinish(),
|
|||
|
path = source,
|
|||
|
line = line,
|
|||
|
char = char,
|
|||
|
}
|
|||
|
return result
|
|||
|
end)
|
|||
|
: case 'enum'
|
|||
|
: call(function ()
|
|||
|
local name = parseName('doc.enum.name')
|
|||
|
if not name then
|
|||
|
return nil
|
|||
|
end
|
|||
|
local result = {
|
|||
|
type = 'doc.enum',
|
|||
|
start = name.start,
|
|||
|
finish = name.finish,
|
|||
|
enum = name,
|
|||
|
}
|
|||
|
name.parent = result
|
|||
|
return result
|
|||
|
end)
|
|||
|
: case 'private'
|
|||
|
: call(function ()
|
|||
|
return {
|
|||
|
type = 'doc.private',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
end)
|
|||
|
: case 'protected'
|
|||
|
: call(function ()
|
|||
|
return {
|
|||
|
type = 'doc.protected',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
end)
|
|||
|
: case 'public'
|
|||
|
: call(function ()
|
|||
|
return {
|
|||
|
type = 'doc.public',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
end)
|
|||
|
: case 'package'
|
|||
|
: call(function ()
|
|||
|
return {
|
|||
|
type = 'doc.package',
|
|||
|
start = getFinish(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
end)
|
|||
|
|
|||
|
local function convertTokens(doc)
|
|||
|
local tp, text = nextToken()
|
|||
|
if not tp then
|
|||
|
return
|
|||
|
end
|
|||
|
if tp ~= 'name' then
|
|||
|
pushWarning {
|
|||
|
type = 'LUADOC_MISS_CATE_NAME',
|
|||
|
start = getStart(),
|
|||
|
finish = getFinish(),
|
|||
|
}
|
|||
|
return nil
|
|||
|
end
|
|||
|
return docSwitch(text, doc)
|
|||
|
end
|
|||
|
|
|||
|
local function trimTailComment(text)
|
|||
|
local comment = text
|
|||
|
if text:sub(1, 1) == '@' then
|
|||
|
comment = text:sub(2)
|
|||
|
end
|
|||
|
if text:sub(1, 1) == '#' then
|
|||
|
comment = text:sub(2)
|
|||
|
end
|
|||
|
if text:sub(1, 2) == '--' then
|
|||
|
comment = text:sub(3)
|
|||
|
end
|
|||
|
if comment:find '^%s*[\'"[]' then
|
|||
|
local state = compile(comment:gsub('^%s+', ''), 'String')
|
|||
|
if state and state.ast then
|
|||
|
comment = state.ast[1]
|
|||
|
end
|
|||
|
end
|
|||
|
return comment
|
|||
|
end
|
|||
|
|
|||
|
local function buildLuaDoc(comment)
|
|||
|
local text = comment.text
|
|||
|
local startPos = (comment.type == 'comment.short' and text:match '^%-%s*@()')
|
|||
|
or (comment.type == 'comment.long' and text:match '^@()')
|
|||
|
if not startPos then
|
|||
|
return {
|
|||
|
type = 'doc.comment',
|
|||
|
start = comment.start,
|
|||
|
finish = comment.finish,
|
|||
|
range = comment.finish,
|
|||
|
comment = comment,
|
|||
|
}
|
|||
|
end
|
|||
|
local startOffset = comment.start
|
|||
|
if comment.type == 'comment.long' then
|
|||
|
startOffset = startOffset + #comment.mark - 2
|
|||
|
end
|
|||
|
|
|||
|
local doc = text:sub(startPos)
|
|||
|
|
|||
|
parseTokens(doc, startOffset + startPos)
|
|||
|
local result, rests = convertTokens(doc)
|
|||
|
if result then
|
|||
|
result.range = comment.finish
|
|||
|
local finish = result.firstFinish or result.finish
|
|||
|
if rests then
|
|||
|
for _, rest in ipairs(rests) do
|
|||
|
rest.range = comment.finish
|
|||
|
finish = rest.firstFinish or result.finish
|
|||
|
end
|
|||
|
end
|
|||
|
local cstart = text:find('%S', finish - comment.start)
|
|||
|
if cstart and cstart < comment.finish then
|
|||
|
result.comment = {
|
|||
|
type = 'doc.tailcomment',
|
|||
|
start = cstart + comment.start,
|
|||
|
finish = comment.finish,
|
|||
|
parent = result,
|
|||
|
text = trimTailComment(text:sub(cstart)),
|
|||
|
}
|
|||
|
if rests then
|
|||
|
for _, rest in ipairs(rests) do
|
|||
|
rest.comment = result.comment
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
if result then
|
|||
|
return result, rests
|
|||
|
end
|
|||
|
|
|||
|
return {
|
|||
|
type = 'doc.comment',
|
|||
|
start = comment.start,
|
|||
|
finish = comment.finish,
|
|||
|
range = comment.finish,
|
|||
|
comment = comment,
|
|||
|
}
|
|||
|
end
|
|||
|
|
|||
|
local function isTailComment(text, doc)
|
|||
|
if not doc then
|
|||
|
return false
|
|||
|
end
|
|||
|
local left = doc.originalComment.start
|
|||
|
local row, col = guide.rowColOf(left)
|
|||
|
local lineStart = Lines[row] or 0
|
|||
|
local hasCodeBefore = text:sub(lineStart, lineStart + col):find '[%w_]'
|
|||
|
return hasCodeBefore
|
|||
|
end
|
|||
|
|
|||
|
local function isContinuedDoc(lastDoc, nextDoc)
|
|||
|
if not nextDoc then
|
|||
|
return false
|
|||
|
end
|
|||
|
if nextDoc.type == 'doc.diagnostic' then
|
|||
|
return true
|
|||
|
end
|
|||
|
if lastDoc.type == 'doc.type'
|
|||
|
or lastDoc.type == 'doc.module'
|
|||
|
or lastDoc.type == 'doc.enum' then
|
|||
|
if nextDoc.type ~= 'doc.comment' then
|
|||
|
return false
|
|||
|
end
|
|||
|
end
|
|||
|
if lastDoc.type == 'doc.class'
|
|||
|
or lastDoc.type == 'doc.field'
|
|||
|
or lastDoc.type == 'doc.operator' then
|
|||
|
if nextDoc.type ~= 'doc.field'
|
|||
|
and nextDoc.type ~= 'doc.operator'
|
|||
|
and nextDoc.type ~= 'doc.comment'
|
|||
|
and nextDoc.type ~= 'doc.overload'
|
|||
|
and nextDoc.type ~= 'doc.source' then
|
|||
|
return false
|
|||
|
end
|
|||
|
end
|
|||
|
if nextDoc.type == 'doc.cast' then
|
|||
|
return false
|
|||
|
end
|
|||
|
return true
|
|||
|
end
|
|||
|
|
|||
|
local function isNextLine(lastDoc, nextDoc)
|
|||
|
if not nextDoc then
|
|||
|
return false
|
|||
|
end
|
|||
|
local lastRow = guide.rowColOf(lastDoc.finish)
|
|||
|
local newRow = guide.rowColOf(nextDoc.start)
|
|||
|
return newRow - lastRow == 1
|
|||
|
end
|
|||
|
|
|||
|
local function bindGeneric(binded)
|
|||
|
local generics = {}
|
|||
|
for _, doc in ipairs(binded) do
|
|||
|
if doc.type == 'doc.generic' then
|
|||
|
for _, obj in ipairs(doc.generics) do
|
|||
|
local name = obj.generic[1]
|
|||
|
generics[name] = obj
|
|||
|
end
|
|||
|
end
|
|||
|
if doc.type == 'doc.class'
|
|||
|
or doc.type == 'doc.alias' then
|
|||
|
if doc.signs then
|
|||
|
for _, sign in ipairs(doc.signs) do
|
|||
|
local name = sign[1]
|
|||
|
generics[name] = sign
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
if doc.type == 'doc.param'
|
|||
|
or doc.type == 'doc.vararg'
|
|||
|
or doc.type == 'doc.return'
|
|||
|
or doc.type == 'doc.type'
|
|||
|
or doc.type == 'doc.class'
|
|||
|
or doc.type == 'doc.alias' then
|
|||
|
guide.eachSourceType(doc, 'doc.type.name', function (src)
|
|||
|
local name = src[1]
|
|||
|
if generics[name] then
|
|||
|
src.type = 'doc.generic.name'
|
|||
|
src.generic = generics[name]
|
|||
|
end
|
|||
|
end)
|
|||
|
guide.eachSourceType(doc, 'doc.type.code', function (src)
|
|||
|
local name = src[1]
|
|||
|
if generics[name] then
|
|||
|
src.type = 'doc.generic.name'
|
|||
|
src.literal = true
|
|||
|
end
|
|||
|
end)
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
local function bindDocWithSource(doc, source)
|
|||
|
if not source.bindDocs then
|
|||
|
source.bindDocs = {}
|
|||
|
end
|
|||
|
source.bindDocs[#source.bindDocs+1] = doc
|
|||
|
doc.bindSource = source
|
|||
|
end
|
|||
|
|
|||
|
local function bindDoc(source, binded)
|
|||
|
local isParam = source.type == 'self'
|
|||
|
or source.type == 'local'
|
|||
|
and (source.parent.type == 'funcargs'
|
|||
|
or ( source.parent.type == 'in'
|
|||
|
and source.finish <= source.parent.keys.finish
|
|||
|
)
|
|||
|
)
|
|||
|
local ok = false
|
|||
|
for _, doc in ipairs(binded) do
|
|||
|
if doc.bindSource then
|
|||
|
goto CONTINUE
|
|||
|
end
|
|||
|
if doc.type == 'doc.class'
|
|||
|
or doc.type == 'doc.deprecated'
|
|||
|
or doc.type == 'doc.version'
|
|||
|
or doc.type == 'doc.module'
|
|||
|
or doc.type == 'doc.source'
|
|||
|
or doc.type == 'doc.private'
|
|||
|
or doc.type == 'doc.protected'
|
|||
|
or doc.type == 'doc.public'
|
|||
|
or doc.type == 'doc.package'
|
|||
|
or doc.type == 'doc.see' then
|
|||
|
if source.type == 'function'
|
|||
|
or isParam then
|
|||
|
goto CONTINUE
|
|||
|
end
|
|||
|
bindDocWithSource(doc, source)
|
|||
|
ok = true
|
|||
|
elseif doc.type == 'doc.type' then
|
|||
|
if source.type == 'function'
|
|||
|
or isParam
|
|||
|
or source._bindedDocType then
|
|||
|
goto CONTINUE
|
|||
|
end
|
|||
|
source._bindedDocType = true
|
|||
|
bindDocWithSource(doc, source)
|
|||
|
ok = true
|
|||
|
elseif doc.type == 'doc.overload' then
|
|||
|
if not source.bindDocs then
|
|||
|
source.bindDocs = {}
|
|||
|
end
|
|||
|
source.bindDocs[#source.bindDocs+1] = doc
|
|||
|
if source.type == 'function' then
|
|||
|
bindDocWithSource(doc, source)
|
|||
|
end
|
|||
|
ok = true
|
|||
|
elseif doc.type == 'doc.param' then
|
|||
|
if isParam
|
|||
|
and doc.param[1] == source[1] then
|
|||
|
bindDocWithSource(doc, source)
|
|||
|
ok = true
|
|||
|
elseif source.type == '...'
|
|||
|
and doc.param[1] == '...' then
|
|||
|
bindDocWithSource(doc, source)
|
|||
|
ok = true
|
|||
|
elseif source.type == 'self'
|
|||
|
and doc.param[1] == 'self' then
|
|||
|
bindDocWithSource(doc, source)
|
|||
|
ok = true
|
|||
|
elseif source.type == 'function' then
|
|||
|
if not source.bindDocs then
|
|||
|
source.bindDocs = {}
|
|||
|
end
|
|||
|
source.bindDocs[#source.bindDocs + 1] = doc
|
|||
|
if source.args then
|
|||
|
for _, arg in ipairs(source.args) do
|
|||
|
if arg[1] == doc.param[1] then
|
|||
|
bindDocWithSource(doc, arg)
|
|||
|
break
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
elseif doc.type == 'doc.vararg' then
|
|||
|
if source.type == '...' then
|
|||
|
bindDocWithSource(doc, source)
|
|||
|
ok = true
|
|||
|
end
|
|||
|
elseif doc.type == 'doc.return'
|
|||
|
or doc.type == 'doc.generic'
|
|||
|
or doc.type == 'doc.async'
|
|||
|
or doc.type == 'doc.nodiscard' then
|
|||
|
if source.type == 'function' then
|
|||
|
bindDocWithSource(doc, source)
|
|||
|
ok = true
|
|||
|
end
|
|||
|
elseif doc.type == 'doc.enum' then
|
|||
|
if source.type == 'table' then
|
|||
|
bindDocWithSource(doc, source)
|
|||
|
ok = true
|
|||
|
end
|
|||
|
if source.value and source.value.type == 'table' then
|
|||
|
bindDocWithSource(doc, source.value)
|
|||
|
goto CONTINUE
|
|||
|
end
|
|||
|
elseif doc.type == 'doc.comment' then
|
|||
|
bindDocWithSource(doc, source)
|
|||
|
ok = true
|
|||
|
end
|
|||
|
::CONTINUE::
|
|||
|
end
|
|||
|
return ok
|
|||
|
end
|
|||
|
|
|||
|
local function bindDocsBetween(sources, binded, start, finish)
|
|||
|
-- 用二分法找到第一个
|
|||
|
local max = #sources
|
|||
|
local index
|
|||
|
local left = 1
|
|||
|
local right = max
|
|||
|
for _ = 1, 1000 do
|
|||
|
index = left + (right - left) // 2
|
|||
|
if index <= left then
|
|||
|
index = left
|
|||
|
break
|
|||
|
elseif index >= right then
|
|||
|
index = right
|
|||
|
break
|
|||
|
end
|
|||
|
local src = sources[index]
|
|||
|
if src.start < start then
|
|||
|
left = index + 1
|
|||
|
else
|
|||
|
right = index
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
local ok = false
|
|||
|
-- 从前往后进行绑定
|
|||
|
for i = index, max do
|
|||
|
local src = sources[i]
|
|||
|
if src and src.start >= start then
|
|||
|
if src.start >= finish then
|
|||
|
break
|
|||
|
end
|
|||
|
if src.start >= start then
|
|||
|
if src.type == 'local'
|
|||
|
or src.type == 'self'
|
|||
|
or src.type == 'setlocal'
|
|||
|
or src.type == 'setglobal'
|
|||
|
or src.type == 'tablefield'
|
|||
|
or src.type == 'tableindex'
|
|||
|
or src.type == 'setfield'
|
|||
|
or src.type == 'setindex'
|
|||
|
or src.type == 'setmethod'
|
|||
|
or src.type == 'function'
|
|||
|
or src.type == 'table'
|
|||
|
or src.type == '...' then
|
|||
|
if bindDoc(src, binded) then
|
|||
|
ok = true
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
return ok
|
|||
|
end
|
|||
|
|
|||
|
local function bindReturnIndex(binded)
|
|||
|
local returnIndex = 0
|
|||
|
for _, doc in ipairs(binded) do
|
|||
|
if doc.type == 'doc.return' then
|
|||
|
for _, rtn in ipairs(doc.returns) do
|
|||
|
returnIndex = returnIndex + 1
|
|||
|
rtn.returnIndex = returnIndex
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
local function bindCommentsToDoc(doc, comments)
|
|||
|
doc.bindComments = comments
|
|||
|
for _, comment in ipairs(comments) do
|
|||
|
comment.bindSource = doc
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
local function bindCommentsAndFields(binded)
|
|||
|
local class
|
|||
|
local comments = {}
|
|||
|
local source
|
|||
|
for _, doc in ipairs(binded) do
|
|||
|
if doc.type == 'doc.class' then
|
|||
|
-- 多个class连续写在一起,只有最后一个class可以绑定source
|
|||
|
if class then
|
|||
|
class.bindSource = nil
|
|||
|
end
|
|||
|
if source then
|
|||
|
doc.source = source
|
|||
|
source.bindSource = doc
|
|||
|
end
|
|||
|
class = doc
|
|||
|
bindCommentsToDoc(doc, comments)
|
|||
|
comments = {}
|
|||
|
elseif doc.type == 'doc.field' then
|
|||
|
if class then
|
|||
|
class.fields[#class.fields+1] = doc
|
|||
|
doc.class = class
|
|||
|
end
|
|||
|
if source then
|
|||
|
doc.source = source
|
|||
|
source.bindSource = doc
|
|||
|
end
|
|||
|
bindCommentsToDoc(doc, comments)
|
|||
|
comments = {}
|
|||
|
elseif doc.type == 'doc.operator' then
|
|||
|
if class then
|
|||
|
class.operators[#class.operators+1] = doc
|
|||
|
doc.class = class
|
|||
|
end
|
|||
|
bindCommentsToDoc(doc, comments)
|
|||
|
comments = {}
|
|||
|
elseif doc.type == 'doc.overload' then
|
|||
|
if class then
|
|||
|
class.calls[#class.calls+1] = doc
|
|||
|
doc.class = class
|
|||
|
end
|
|||
|
elseif doc.type == 'doc.alias'
|
|||
|
or doc.type == 'doc.enum' then
|
|||
|
bindCommentsToDoc(doc, comments)
|
|||
|
comments = {}
|
|||
|
elseif doc.type == 'doc.comment' then
|
|||
|
comments[#comments+1] = doc
|
|||
|
elseif doc.type == 'doc.source' then
|
|||
|
source = doc
|
|||
|
goto CONTINUE
|
|||
|
end
|
|||
|
source = nil
|
|||
|
::CONTINUE::
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
local function bindDocWithSources(sources, binded)
|
|||
|
if not binded then
|
|||
|
return
|
|||
|
end
|
|||
|
local lastDoc = binded[#binded]
|
|||
|
if not lastDoc then
|
|||
|
return
|
|||
|
end
|
|||
|
for _, doc in ipairs(binded) do
|
|||
|
doc.bindGroup = binded
|
|||
|
end
|
|||
|
bindGeneric(binded)
|
|||
|
bindCommentsAndFields(binded)
|
|||
|
bindReturnIndex(binded)
|
|||
|
local row = guide.rowColOf(lastDoc.finish)
|
|||
|
local suc = bindDocsBetween(sources, binded, guide.positionOf(row, 0), lastDoc.start)
|
|||
|
if not suc then
|
|||
|
bindDocsBetween(sources, binded, guide.positionOf(row + 1, 0), guide.positionOf(row + 2, 0))
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
local bindDocAccept = {
|
|||
|
'local' , 'setlocal' , 'setglobal',
|
|||
|
'setfield' , 'setmethod' , 'setindex' ,
|
|||
|
'tablefield', 'tableindex', 'self' ,
|
|||
|
'function' , 'table' , '...' ,
|
|||
|
}
|
|||
|
|
|||
|
local function bindDocs(state)
|
|||
|
local text = state.lua
|
|||
|
local sources = {}
|
|||
|
guide.eachSourceTypes(state.ast, bindDocAccept, function (src)
|
|||
|
sources[#sources+1] = src
|
|||
|
end)
|
|||
|
table.sort(sources, function (a, b)
|
|||
|
return a.start < b.start
|
|||
|
end)
|
|||
|
local binded
|
|||
|
for i, doc in ipairs(state.ast.docs) do
|
|||
|
if not binded then
|
|||
|
binded = {}
|
|||
|
state.ast.docs.groups[#state.ast.docs.groups+1] = binded
|
|||
|
end
|
|||
|
binded[#binded+1] = doc
|
|||
|
if isTailComment(text, doc) then
|
|||
|
bindDocWithSources(sources, binded)
|
|||
|
binded = nil
|
|||
|
else
|
|||
|
local nextDoc = state.ast.docs[i+1]
|
|||
|
if not isNextLine(doc, nextDoc) then
|
|||
|
bindDocWithSources(sources, binded)
|
|||
|
binded = nil
|
|||
|
end
|
|||
|
if not isContinuedDoc(doc, nextDoc)
|
|||
|
and not isTailComment(text, nextDoc) then
|
|||
|
bindDocWithSources(sources, binded)
|
|||
|
binded = nil
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
local function findTouch(state, doc)
|
|||
|
local text = state.lua
|
|||
|
local pos = guide.positionToOffset(state, doc.originalComment.start)
|
|||
|
for i = pos - 2, 1, -1 do
|
|||
|
local c = text:sub(i, i)
|
|||
|
if c == '\r'
|
|||
|
or c == '\n' then
|
|||
|
break
|
|||
|
elseif c ~= ' '
|
|||
|
and c ~= '\t' then
|
|||
|
doc.touch = guide.offsetToPosition(state, i)
|
|||
|
break
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
return function (state)
|
|||
|
local ast = state.ast
|
|||
|
local comments = state.comms
|
|||
|
table.sort(comments, function (a, b)
|
|||
|
return a.start < b.start
|
|||
|
end)
|
|||
|
ast.docs = {
|
|||
|
type = 'doc',
|
|||
|
parent = ast,
|
|||
|
groups = {},
|
|||
|
}
|
|||
|
|
|||
|
pushWarning = function (err)
|
|||
|
local errs = state.errs
|
|||
|
if err.finish < err.start then
|
|||
|
err.finish = err.start
|
|||
|
end
|
|||
|
local last = errs[#errs]
|
|||
|
if last then
|
|||
|
if last.start <= err.start and last.finish >= err.finish then
|
|||
|
return
|
|||
|
end
|
|||
|
end
|
|||
|
err.level = err.level or 'Warning'
|
|||
|
errs[#errs+1] = err
|
|||
|
return err
|
|||
|
end
|
|||
|
Lines = state.lines
|
|||
|
|
|||
|
local ci = 1
|
|||
|
NextComment = function (offset, peek)
|
|||
|
local comment = comments[ci + (offset or 0)]
|
|||
|
if not peek then
|
|||
|
ci = ci + 1 + (offset or 0)
|
|||
|
end
|
|||
|
return comment
|
|||
|
end
|
|||
|
|
|||
|
local function insertDoc(doc, comment)
|
|||
|
ast.docs[#ast.docs+1] = doc
|
|||
|
doc.parent = ast.docs
|
|||
|
if ast.start > doc.start then
|
|||
|
ast.start = doc.start
|
|||
|
end
|
|||
|
if ast.finish < doc.finish then
|
|||
|
ast.finish = doc.finish
|
|||
|
end
|
|||
|
doc.originalComment = comment
|
|||
|
if comment.type == 'comment.long' then
|
|||
|
findTouch(state, doc)
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
while true do
|
|||
|
local comment = NextComment()
|
|||
|
if not comment then
|
|||
|
break
|
|||
|
end
|
|||
|
local doc, rests = buildLuaDoc(comment)
|
|||
|
if doc then
|
|||
|
insertDoc(doc, comment)
|
|||
|
if rests then
|
|||
|
for _, rest in ipairs(rests) do
|
|||
|
insertDoc(rest, comment)
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
end
|
|||
|
|
|||
|
ast.docs.start = ast.start
|
|||
|
ast.docs.finish = ast.finish
|
|||
|
|
|||
|
if #ast.docs == 0 then
|
|||
|
return
|
|||
|
end
|
|||
|
|
|||
|
bindDocs(state)
|
|||
|
end
|