summaryrefslogtreecommitdiff
path: root/script-beta/parser/luadoc.lua
diff options
context:
space:
mode:
Diffstat (limited to 'script-beta/parser/luadoc.lua')
-rw-r--r--script-beta/parser/luadoc.lua684
1 files changed, 684 insertions, 0 deletions
diff --git a/script-beta/parser/luadoc.lua b/script-beta/parser/luadoc.lua
new file mode 100644
index 00000000..ebab1ee9
--- /dev/null
+++ b/script-beta/parser/luadoc.lua
@@ -0,0 +1,684 @@
+local m = require 'lpeglabel'
+local re = require 'parser.relabel'
+
+local TokenTypes, TokenStarts, TokenFinishs, TokenContents
+local Ci, Offset, pushError
+local parseType
+local Parser = re.compile([[
+Main <- (Token / Sp)*
+Sp <- %s+
+X16 <- [a-fA-F0-9]
+Word <- [a-zA-Z0-9_]
+Token <- Name / String / Symbol
+Name <- ({} {[a-zA-Z_] [a-zA-Z0-9_.]*} {})
+ -> Name
+String <- ({} StringDef {})
+ -> String
+StringDef <- '"'
+ {~(Esc / !'"' .)*~} -> 1
+ ('"'?)
+ / "'"
+ {~(Esc / !"'" .)*~} -> 1
+ ("'"?)
+ / ('[' {:eq: '='* :} '['
+ {(!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{' {Word*} '}') -> CharUtf8
+Symbol <- ({} {
+ ':'
+ / '|'
+ / ','
+ / '[]'
+ / '<'
+ / '>'
+ / '('
+ / ')'
+ } {})
+ -> Symbol
+]], {
+ s = m.S' \t',
+ ea = '\a',
+ eb = '\b',
+ ef = '\f',
+ en = '\n',
+ er = '\r',
+ et = '\t',
+ ev = '\v',
+ Char10 = function (char)
+ 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,
+ 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, content, finish)
+ Ci = Ci + 1
+ TokenTypes[Ci] = 'string'
+ 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,
+})
+
+local function parseTokens(text)
+ Ci = 0
+ TokenTypes = {}
+ TokenStarts = {}
+ TokenFinishs = {}
+ TokenContents = {}
+ Parser:match(text)
+ Ci = 0
+end
+
+local function peekToken()
+ return TokenTypes[Ci+1], TokenContents[Ci+1]
+end
+
+local function nextToken()
+ Ci = Ci + 1
+ if not TokenTypes[Ci] then
+ Ci = Ci - 1
+ return 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()
+ return TokenStarts[Ci] + Offset
+end
+
+local function getFinish()
+ return TokenFinishs[Ci] + Offset
+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 class = {
+ type = tp,
+ start = getStart(),
+ finish = getFinish(),
+ parent = parent,
+ [1] = nameText,
+ }
+ return class
+end
+
+local function parseClass(parent)
+ local result = {
+ type = 'doc.class',
+ parent = parent,
+ }
+ result.class = parseName('doc.class.name', result)
+ if not result.class then
+ pushError {
+ type = 'LUADOC_MISS_CLASS_NAME',
+ start = getFinish(),
+ finish = getFinish(),
+ }
+ return nil
+ end
+ result.start = getStart()
+ result.finish = getFinish()
+ if not peekToken() then
+ return result
+ end
+ nextToken()
+ if not checkToken('symbol', ':') then
+ pushError {
+ type = 'LUADOC_MISS_EXTENSION_SYMBOL',
+ start = getFinish(),
+ finish = getFinish(),
+ }
+ return result
+ end
+ result.extends = parseName('doc.extends.name', result)
+ if not result.extends then
+ pushError {
+ type = 'LUADOC_MISS_EXTENDS_NAME',
+ start = getFinish(),
+ finish = getFinish(),
+ }
+ return result
+ end
+ result.finish = getFinish()
+ return result
+end
+
+local function nextSymbolOrError(symbol)
+ if checkToken('symbol', symbol, 1) then
+ nextToken()
+ return true
+ end
+ pushError {
+ type = 'LUADOC_MISS_SYMBOL',
+ start = getFinish(),
+ finish = getFinish(),
+ info = {
+ symbol = symbol,
+ }
+ }
+ return false
+end
+
+local function parseTypeUnitTable()
+ local typeUnit = {
+ type = 'doc.type.table',
+ start = getStart(),
+ }
+ if not nextSymbolOrError('<') then
+ return nil
+ end
+ local key = parseType(typeUnit)
+ if not key or not nextSymbolOrError(',') then
+ return nil
+ end
+ local value = parseType(typeUnit)
+ if not value then
+ return nil
+ end
+ nextSymbolOrError('>')
+ typeUnit.key = key
+ typeUnit.value = value
+ typeUnit.finish = getFinish()
+ return typeUnit
+end
+
+local function parseTypeUnitFunction()
+ local typeUnit = {
+ type = 'doc.type.function',
+ 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.name', arg)
+ if not arg.name then
+ pushError {
+ type = 'LUADOC_MISS_ARG_NAME',
+ start = getFinish(),
+ finish = getFinish(),
+ }
+ break
+ end
+ if not arg.start then
+ arg.start = arg.name.start
+ end
+ arg.finish = getFinish()
+ if not nextSymbolOrError(':') then
+ break
+ end
+ arg.extends = parseType(arg)
+ if not arg.extends then
+ break
+ 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()
+ while true do
+ local rtn = parseType(arg)
+ if not rtn then
+ break
+ end
+ typeUnit.returns[#typeUnit.returns+1] = rtn
+ if checkToken('symbol', ',', 1) then
+ nextToken()
+ else
+ break
+ end
+ end
+ end
+ typeUnit.finish = getFinish()
+ return typeUnit
+end
+
+local function parseTypeUnit(parent, content)
+ local typeUnit
+ if content == 'table' then
+ typeUnit = parseTypeUnitTable()
+ elseif content == 'fun' then
+ typeUnit = parseTypeUnitFunction()
+ else
+ typeUnit = {
+ type = 'doc.type.name',
+ start = getStart(),
+ finish = getFinish(),
+ [1] = content,
+ }
+ end
+ if not typeUnit then
+ return nil
+ end
+ typeUnit.parent = parent
+ if checkToken('symbol', '[]', 1) then
+ nextToken()
+ typeUnit.array = true
+ typeUnit.finish = getFinish()
+ end
+ return typeUnit
+end
+
+function parseType(parent)
+ if not peekToken() then
+ pushError {
+ type = 'LUADOC_MISS_TYPE_NAME',
+ start = getFinish(),
+ finish = getFinish(),
+ }
+ return nil
+ end
+ local result = {
+ type = 'doc.type',
+ parent = parent,
+ types = {},
+ enums = {},
+ }
+ while true do
+ local tp, content = peekToken()
+ if not tp then
+ break
+ end
+ if tp == 'name' then
+ nextToken()
+ local typeUnit = parseTypeUnit(result, content)
+ if not typeUnit then
+ break
+ end
+ result.types[#result.types+1] = typeUnit
+ if not result.start then
+ result.start = typeUnit.start
+ end
+ elseif tp == 'string' then
+ nextToken()
+ local typeEnum = {
+ type = 'doc.type.enum',
+ start = getStart(),
+ finish = getFinish(),
+ parent = result,
+ [1] = content,
+ }
+ result.enums[#result.enums+1] = typeEnum
+ if not result.start then
+ result.start = typeEnum.start
+ end
+ end
+ if not checkToken('symbol', '|', 1) then
+ break
+ end
+ nextToken()
+ end
+ result.finish = getFinish()
+ if #result.types == 0 and #result.enums == 0 then
+ return nil
+ end
+ return result
+end
+
+local function parseAlias()
+ local result = {
+ type = 'doc.alias',
+ }
+ result.alias = parseName('doc.alias.name', result)
+ if not result.alias then
+ pushError {
+ type = 'LUADOC_MISS_ALIAS_NAME',
+ start = getFinish(),
+ finish = getFinish(),
+ }
+ return nil
+ end
+ result.start = getStart()
+ result.extends = parseType(result)
+ if not result.extends then
+ pushError {
+ type = 'LUADOC_MISS_ALIAS_EXTENDS',
+ start = getFinish(),
+ finish = getFinish(),
+ }
+ return nil
+ end
+ result.finish = getFinish()
+ return result
+end
+
+local function parseParam()
+ local result = {
+ type = 'doc.param',
+ }
+ result.param = parseName('doc.param.name', result)
+ if not result.param then
+ pushError {
+ type = 'LUADOC_MISS_PARAM_NAME',
+ start = getFinish(),
+ finish = getFinish(),
+ }
+ return nil
+ end
+ result.start = getStart()
+ result.extends = parseType(result)
+ if not result.extends then
+ pushError {
+ type = 'LUADOC_MISS_PARAM_EXTENDS',
+ start = getFinish(),
+ finish = getFinish(),
+ }
+ return nil
+ end
+ result.finish = getFinish()
+ return result
+end
+
+local function parseReturn()
+ local result = {
+ type = 'doc.return',
+ returns = {},
+ }
+ while true do
+ local docType = parseType(result)
+ if not docType then
+ break
+ end
+ if not result.start then
+ result.start = docType.start
+ 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
+
+local function parseField()
+ local result = {
+ type = 'doc.field',
+ }
+ try(function ()
+ local tp, value = nextToken()
+ if tp == 'name' then
+ if value == 'public'
+ or value == 'protected'
+ or value == 'private' then
+ result.visible = value
+ result.start = getStart()
+ return true
+ end
+ end
+ return false
+ end)
+ result.field = parseName('doc.field.name', result)
+ if not result.field then
+ pushError {
+ type = 'LUADOC_MISS_FIELD_NAME',
+ start = getFinish(),
+ finish = getFinish(),
+ }
+ return nil
+ end
+ if not result.start then
+ result.start = result.field.start
+ end
+ result.extends = parseType(result)
+ if not result.extends then
+ pushError {
+ type = 'LUADOC_MISS_FIELD_EXTENDS',
+ start = getFinish(),
+ finish = getFinish(),
+ }
+ return nil
+ end
+ result.finish = getFinish()
+ return result
+end
+
+local function parseGeneric()
+ 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
+ pushError {
+ 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 = parseName('doc.extends.name', object)
+ if not object.extends then
+ pushError {
+ type = 'LUADOC_MISS_EXTENDS_NAME',
+ start = getFinish(),
+ finish = getFinish(),
+ }
+ return nil
+ end
+ 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
+
+local function parseVararg()
+ local result = {
+ type = 'doc.vararg',
+ }
+ result.vararg = parseType(result)
+ if not result.vararg then
+ pushError {
+ type = 'LUADOC_MISS_VARARG_TYPE',
+ start = getFinish(),
+ finish = getFinish(),
+ }
+ return
+ end
+ result.start = result.vararg.start
+ result.finish = result.vararg.finish
+ return result
+end
+
+local function parseOverload()
+ local tp, name = peekToken()
+ if tp ~= 'name' or name ~= 'fun' then
+ pushError {
+ type = 'LUADOC_MISS_FUN_AFTER_OVERLOAD',
+ start = getFinish(),
+ finish = getFinish(),
+ }
+ return nil
+ end
+ nextToken()
+ local result = {
+ type = 'doc.overload',
+ }
+ result.overload = parseTypeUnitFunction()
+ 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
+
+local function convertTokens()
+ local tp, text = nextToken()
+ if not tp then
+ return
+ end
+ if tp ~= 'name' then
+ pushError {
+ type = 'LUADOC_MISS_CATE_NAME',
+ start = getStart(),
+ finish = getFinish(),
+ }
+ return nil
+ end
+ if text == 'class' then
+ return parseClass()
+ elseif text == 'type' then
+ return parseType()
+ elseif text == 'alias' then
+ return parseAlias()
+ elseif text == 'param' then
+ return parseParam()
+ elseif text == 'return' then
+ return parseReturn()
+ elseif text == 'field' then
+ return parseField()
+ elseif text == 'generic' then
+ return parseGeneric()
+ elseif text == 'vararg' then
+ return parseVararg()
+ elseif text == 'overload' then
+ return parseOverload()
+ end
+end
+
+local function buildLuaDoc(comment)
+ Offset = comment.start + 1
+ local text = comment.text
+ if text:sub(1, 2) ~= '-@' then
+ return
+ end
+ local finishPos = text:find('@', 3)
+ local doc, lastComment
+ if finishPos then
+ doc = text:sub(3, finishPos - 1)
+ lastComment = text:sub(finishPos)
+ else
+ doc = text:sub(3)
+ end
+ parseTokens(doc)
+ local result = convertTokens()
+ if result then
+ result.comment = lastComment
+ end
+ return result
+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 = {}
+
+ pushError = state.pushError
+
+ for _, comment in ipairs(comments) do
+ local doc = buildLuaDoc(comment)
+ if doc then
+ ast.docs[#ast.docs+1] = doc
+ end
+ end
+end