3916 lines
104 KiB
Lua
3916 lines
104 KiB
Lua
local tokens = require 'parser.tokens'
|
|
local guide = require 'parser.guide'
|
|
|
|
local sbyte = string.byte
|
|
local sfind = string.find
|
|
local smatch = string.match
|
|
local sgsub = string.gsub
|
|
local ssub = string.sub
|
|
local schar = string.char
|
|
local supper = string.upper
|
|
local uchar = utf8.char
|
|
local tconcat = table.concat
|
|
local tinsert = table.insert
|
|
local tointeger = math.tointeger
|
|
local tonumber = tonumber
|
|
local maxinteger = math.maxinteger
|
|
local assert = assert
|
|
|
|
_ENV = nil
|
|
|
|
---@alias parser.position integer
|
|
|
|
---@param str string
|
|
---@return table<integer, boolean>
|
|
local function stringToCharMap(str)
|
|
local map = {}
|
|
local pos = 1
|
|
while pos <= #str do
|
|
local byte = sbyte(str, pos, pos)
|
|
map[schar(byte)] = true
|
|
pos = pos + 1
|
|
if ssub(str, pos, pos) == '-'
|
|
and pos < #str then
|
|
pos = pos + 1
|
|
local byte2 = sbyte(str, pos, pos)
|
|
assert(byte < byte2)
|
|
for b = byte + 1, byte2 do
|
|
map[schar(b)] = true
|
|
end
|
|
pos = pos + 1
|
|
end
|
|
end
|
|
return map
|
|
end
|
|
|
|
local CharMapNumber = stringToCharMap '0-9'
|
|
local CharMapN16 = stringToCharMap 'xX'
|
|
local CharMapN2 = stringToCharMap 'bB'
|
|
local CharMapE10 = stringToCharMap 'eE'
|
|
local CharMapE16 = stringToCharMap 'pP'
|
|
local CharMapSign = stringToCharMap '+-'
|
|
local CharMapSB = stringToCharMap 'ao|~&=<>.*/%^+-'
|
|
local CharMapSU = stringToCharMap 'n#~!-'
|
|
local CharMapSimple = stringToCharMap '.:([\'"{'
|
|
local CharMapStrSH = stringToCharMap '\'"`'
|
|
local CharMapStrLH = stringToCharMap '['
|
|
local CharMapTSep = stringToCharMap ',;'
|
|
local CharMapWord = stringToCharMap '_a-zA-Z\x80-\xff'
|
|
|
|
local EscMap = {
|
|
['a'] = '\a',
|
|
['b'] = '\b',
|
|
['f'] = '\f',
|
|
['n'] = '\n',
|
|
['r'] = '\r',
|
|
['t'] = '\t',
|
|
['v'] = '\v',
|
|
['\\'] = '\\',
|
|
['\''] = '\'',
|
|
['\"'] = '\"',
|
|
}
|
|
|
|
local NLMap = {
|
|
['\n'] = true,
|
|
['\r'] = true,
|
|
['\r\n'] = true,
|
|
}
|
|
|
|
local LineMulti = 10000
|
|
|
|
-- goto 单独处理
|
|
local KeyWord = {
|
|
['and'] = true,
|
|
['break'] = true,
|
|
['do'] = true,
|
|
['else'] = true,
|
|
['elseif'] = true,
|
|
['end'] = true,
|
|
['false'] = true,
|
|
['for'] = true,
|
|
['function'] = true,
|
|
['if'] = true,
|
|
['in'] = true,
|
|
['local'] = true,
|
|
['nil'] = true,
|
|
['not'] = true,
|
|
['or'] = true,
|
|
['repeat'] = true,
|
|
['return'] = true,
|
|
['then'] = true,
|
|
['true'] = true,
|
|
['until'] = true,
|
|
['while'] = true,
|
|
}
|
|
|
|
local Specials = {
|
|
['_G'] = true,
|
|
['rawset'] = true,
|
|
['rawget'] = true,
|
|
['setmetatable'] = true,
|
|
['require'] = true,
|
|
['dofile'] = true,
|
|
['loadfile'] = true,
|
|
['pcall'] = true,
|
|
['xpcall'] = true,
|
|
['pairs'] = true,
|
|
['ipairs'] = true,
|
|
['assert'] = true,
|
|
['error'] = true,
|
|
['type'] = true,
|
|
}
|
|
|
|
local UnarySymbol = {
|
|
['not'] = 11,
|
|
['#'] = 11,
|
|
['~'] = 11,
|
|
['-'] = 11,
|
|
}
|
|
|
|
local BinarySymbol = {
|
|
['or'] = 1,
|
|
['and'] = 2,
|
|
['<='] = 3,
|
|
['>='] = 3,
|
|
['<'] = 3,
|
|
['>'] = 3,
|
|
['~='] = 3,
|
|
['=='] = 3,
|
|
['|'] = 4,
|
|
['~'] = 5,
|
|
['&'] = 6,
|
|
['<<'] = 7,
|
|
['>>'] = 7,
|
|
['..'] = 8,
|
|
['+'] = 9,
|
|
['-'] = 9,
|
|
['*'] = 10,
|
|
['//'] = 10,
|
|
['/'] = 10,
|
|
['%'] = 10,
|
|
['^'] = 12,
|
|
}
|
|
|
|
local BinaryAlias = {
|
|
['&&'] = 'and',
|
|
['||'] = 'or',
|
|
['!='] = '~=',
|
|
}
|
|
|
|
local BinaryActionAlias = {
|
|
['='] = '==',
|
|
}
|
|
|
|
local UnaryAlias = {
|
|
['!'] = 'not',
|
|
}
|
|
|
|
local SymbolForward = {
|
|
[01] = true,
|
|
[02] = true,
|
|
[03] = true,
|
|
[04] = true,
|
|
[05] = true,
|
|
[06] = true,
|
|
[07] = true,
|
|
[08] = false,
|
|
[09] = true,
|
|
[10] = true,
|
|
[11] = true,
|
|
[12] = false,
|
|
}
|
|
|
|
local GetToSetMap = {
|
|
['getglobal'] = 'setglobal',
|
|
['getlocal'] = 'setlocal',
|
|
['getfield'] = 'setfield',
|
|
['getindex'] = 'setindex',
|
|
['getmethod'] = 'setmethod',
|
|
}
|
|
|
|
local ChunkFinishMap = {
|
|
['end'] = true,
|
|
['else'] = true,
|
|
['elseif'] = true,
|
|
['in'] = true,
|
|
['then'] = true,
|
|
['until'] = true,
|
|
[';'] = true,
|
|
[']'] = true,
|
|
[')'] = true,
|
|
['}'] = true,
|
|
}
|
|
|
|
local ChunkStartMap = {
|
|
['do'] = true,
|
|
['else'] = true,
|
|
['elseif'] = true,
|
|
['for'] = true,
|
|
['function'] = true,
|
|
['if'] = true,
|
|
['local'] = true,
|
|
['repeat'] = true,
|
|
['return'] = true,
|
|
['then'] = true,
|
|
['until'] = true,
|
|
['while'] = true,
|
|
}
|
|
|
|
local ListFinishMap = {
|
|
['end'] = true,
|
|
['else'] = true,
|
|
['elseif'] = true,
|
|
['in'] = true,
|
|
['then'] = true,
|
|
['do'] = true,
|
|
['until'] = true,
|
|
['for'] = true,
|
|
['if'] = true,
|
|
['local'] = true,
|
|
['repeat'] = true,
|
|
['return'] = true,
|
|
['while'] = true,
|
|
}
|
|
|
|
local State, Lua, Line, LineOffset, Chunk, Tokens, Index, LastTokenFinish, Mode, LocalCount
|
|
|
|
local LocalLimit = 200
|
|
|
|
local parseExp, parseAction
|
|
|
|
local pushError
|
|
|
|
local function addSpecial(name, obj)
|
|
if not State.specials then
|
|
State.specials = {}
|
|
end
|
|
if not State.specials[name] then
|
|
State.specials[name] = {}
|
|
end
|
|
State.specials[name][#State.specials[name]+1] = obj
|
|
obj.special = name
|
|
end
|
|
|
|
---@param offset integer
|
|
---@param leftOrRight '"left"'|'"right"'
|
|
local function getPosition(offset, leftOrRight)
|
|
if not offset or offset > #Lua then
|
|
return LineMulti * Line + #Lua - LineOffset + 1
|
|
end
|
|
if leftOrRight == 'left' then
|
|
return LineMulti * Line + offset - LineOffset
|
|
else
|
|
return LineMulti * Line + offset - LineOffset + 1
|
|
end
|
|
end
|
|
|
|
---@return string? word
|
|
---@return parser.position? startPosition
|
|
---@return parser.position? finishPosition
|
|
local function peekWord()
|
|
local word = Tokens[Index + 1]
|
|
if not word then
|
|
return nil
|
|
end
|
|
if not CharMapWord[ssub(word, 1, 1)] then
|
|
return nil
|
|
end
|
|
local startPos = getPosition(Tokens[Index] , 'left')
|
|
local finishPos = getPosition(Tokens[Index] + #word - 1, 'right')
|
|
return word, startPos, finishPos
|
|
end
|
|
|
|
local function lastRightPosition()
|
|
if Index < 2 then
|
|
return 0
|
|
end
|
|
local token = Tokens[Index - 1]
|
|
if NLMap[token] then
|
|
return LastTokenFinish
|
|
elseif token then
|
|
return getPosition(Tokens[Index - 2] + #token - 1, 'right')
|
|
else
|
|
return getPosition(#Lua, 'right')
|
|
end
|
|
end
|
|
|
|
local function missSymbol(symbol, start, finish)
|
|
pushError {
|
|
type = 'MISS_SYMBOL',
|
|
start = start or lastRightPosition(),
|
|
finish = finish or start or lastRightPosition(),
|
|
info = {
|
|
symbol = symbol,
|
|
}
|
|
}
|
|
end
|
|
|
|
local function missExp()
|
|
pushError {
|
|
type = 'MISS_EXP',
|
|
start = lastRightPosition(),
|
|
finish = lastRightPosition(),
|
|
}
|
|
end
|
|
|
|
local function missName(pos)
|
|
pushError {
|
|
type = 'MISS_NAME',
|
|
start = pos or lastRightPosition(),
|
|
finish = pos or lastRightPosition(),
|
|
}
|
|
end
|
|
|
|
local function missEnd(relatedStart, relatedFinish)
|
|
pushError {
|
|
type = 'MISS_SYMBOL',
|
|
start = lastRightPosition(),
|
|
finish = lastRightPosition(),
|
|
info = {
|
|
symbol = 'end',
|
|
related = {
|
|
{
|
|
start = relatedStart,
|
|
finish = relatedFinish,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
pushError {
|
|
type = 'MISS_END',
|
|
start = relatedStart,
|
|
finish = relatedFinish,
|
|
}
|
|
end
|
|
|
|
local function unknownSymbol(start, finish, word)
|
|
local token = word or Tokens[Index + 1]
|
|
if not token then
|
|
return false
|
|
end
|
|
pushError {
|
|
type = 'UNKNOWN_SYMBOL',
|
|
start = start or getPosition(Tokens[Index], 'left'),
|
|
finish = finish or getPosition(Tokens[Index] + #token - 1, 'right'),
|
|
info = {
|
|
symbol = token,
|
|
}
|
|
}
|
|
return true
|
|
end
|
|
|
|
local function skipUnknownSymbol(stopSymbol)
|
|
if unknownSymbol() then
|
|
Index = Index + 2
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function skipNL()
|
|
local token = Tokens[Index + 1]
|
|
if NLMap[token] then
|
|
if Index >= 2 and not NLMap[Tokens[Index - 1]] then
|
|
LastTokenFinish = getPosition(Tokens[Index - 2] + #Tokens[Index - 1] - 1, 'right')
|
|
end
|
|
Line = Line + 1
|
|
LineOffset = Tokens[Index] + #token
|
|
Index = Index + 2
|
|
State.lines[Line] = LineOffset
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function getSavePoint()
|
|
local index = Index
|
|
local line = Line
|
|
local lineOffset = LineOffset
|
|
local errs = State.errs
|
|
local errCount = #errs
|
|
return function ()
|
|
Index = index
|
|
Line = line
|
|
LineOffset = lineOffset
|
|
for i = errCount + 1, #errs do
|
|
errs[i] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
local function fastForwardToken(offset)
|
|
while true do
|
|
local myOffset = Tokens[Index]
|
|
if not myOffset
|
|
or myOffset >= offset then
|
|
break
|
|
end
|
|
local token = Tokens[Index + 1]
|
|
if NLMap[token] then
|
|
Line = Line + 1
|
|
LineOffset = Tokens[Index] + #token
|
|
State.lines[Line] = LineOffset
|
|
end
|
|
Index = Index + 2
|
|
end
|
|
end
|
|
|
|
local function resolveLongString(finishMark)
|
|
skipNL()
|
|
local miss
|
|
local start = Tokens[Index]
|
|
local finishOffset = sfind(Lua, finishMark, start, true)
|
|
if not finishOffset then
|
|
finishOffset = #Lua + 1
|
|
miss = true
|
|
end
|
|
local stringResult = start and ssub(Lua, start, finishOffset - 1) or ''
|
|
local lastLN = stringResult:find '[\r\n][^\r\n]*$'
|
|
if lastLN then
|
|
local result = stringResult
|
|
: gsub('\r\n?', '\n')
|
|
stringResult = result
|
|
end
|
|
fastForwardToken(finishOffset + #finishMark)
|
|
if miss then
|
|
local pos = getPosition(finishOffset - 1, 'right')
|
|
pushError {
|
|
type = 'MISS_SYMBOL',
|
|
start = pos,
|
|
finish = pos,
|
|
info = {
|
|
symbol = finishMark,
|
|
},
|
|
fix = {
|
|
title = 'ADD_LSTRING_END',
|
|
{
|
|
start = pos,
|
|
finish = pos,
|
|
text = finishMark,
|
|
}
|
|
},
|
|
}
|
|
end
|
|
return stringResult, getPosition(finishOffset + #finishMark - 1, 'right')
|
|
end
|
|
|
|
local function parseLongString()
|
|
local start, finish, mark = sfind(Lua, '^(%[%=*%[)', Tokens[Index])
|
|
if not mark then
|
|
return nil
|
|
end
|
|
fastForwardToken(finish + 1)
|
|
local startPos = getPosition(start, 'left')
|
|
local finishMark = sgsub(mark, '%[', ']')
|
|
local stringResult, finishPos = resolveLongString(finishMark)
|
|
return {
|
|
type = 'string',
|
|
start = startPos,
|
|
finish = finishPos,
|
|
[1] = stringResult,
|
|
[2] = mark,
|
|
}
|
|
end
|
|
|
|
local function pushCommentHeadError(left)
|
|
if State.options.nonstandardSymbol['//'] then
|
|
return
|
|
end
|
|
pushError {
|
|
type = 'ERR_COMMENT_PREFIX',
|
|
start = left,
|
|
finish = left + 2,
|
|
fix = {
|
|
title = 'FIX_COMMENT_PREFIX',
|
|
{
|
|
start = left,
|
|
finish = left + 2,
|
|
text = '--',
|
|
},
|
|
}
|
|
}
|
|
end
|
|
|
|
local function pushLongCommentError(left, right)
|
|
if State.options.nonstandardSymbol['/**/'] then
|
|
return
|
|
end
|
|
pushError {
|
|
type = 'ERR_C_LONG_COMMENT',
|
|
start = left,
|
|
finish = right,
|
|
fix = {
|
|
title = 'FIX_C_LONG_COMMENT',
|
|
{
|
|
start = left,
|
|
finish = left + 2,
|
|
text = '--[[',
|
|
},
|
|
{
|
|
start = right - 2,
|
|
finish = right,
|
|
text = '--]]'
|
|
},
|
|
}
|
|
}
|
|
end
|
|
|
|
local function skipComment(isAction)
|
|
local token = Tokens[Index + 1]
|
|
if token == '--'
|
|
or (
|
|
token == '//'
|
|
and (
|
|
isAction
|
|
or State.options.nonstandardSymbol['//']
|
|
)
|
|
) then
|
|
local start = Tokens[Index]
|
|
local left = getPosition(start, 'left')
|
|
local chead = false
|
|
if token == '//' then
|
|
chead = true
|
|
pushCommentHeadError(left)
|
|
end
|
|
Index = Index + 2
|
|
local longComment = start + 2 == Tokens[Index] and parseLongString()
|
|
if longComment then
|
|
longComment.type = 'comment.long'
|
|
longComment.text = longComment[1]
|
|
longComment.mark = longComment[2]
|
|
longComment[1] = nil
|
|
longComment[2] = nil
|
|
State.comms[#State.comms+1] = longComment
|
|
return true
|
|
end
|
|
while true do
|
|
local nl = Tokens[Index + 1]
|
|
if not nl or NLMap[nl] then
|
|
break
|
|
end
|
|
Index = Index + 2
|
|
end
|
|
local right = Tokens[Index] and (Tokens[Index] - 1) or #Lua
|
|
State.comms[#State.comms+1] = {
|
|
type = chead and 'comment.cshort' or 'comment.short',
|
|
start = left,
|
|
finish = getPosition(right, 'right'),
|
|
text = ssub(Lua, start + 2, right),
|
|
}
|
|
return true
|
|
end
|
|
if token == '/*' then
|
|
local start = Tokens[Index]
|
|
local left = getPosition(start, 'left')
|
|
Index = Index + 2
|
|
local result, right = resolveLongString '*/'
|
|
pushLongCommentError(left, right)
|
|
State.comms[#State.comms+1] = {
|
|
type = 'comment.long',
|
|
start = left,
|
|
finish = right,
|
|
text = result,
|
|
}
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function skipSpace(isAction)
|
|
repeat until not skipNL()
|
|
and not skipComment(isAction)
|
|
end
|
|
|
|
local function expectAssign(isAction)
|
|
local token = Tokens[Index + 1]
|
|
if token == '=' then
|
|
Index = Index + 2
|
|
return true
|
|
end
|
|
if token == '==' then
|
|
local left = getPosition(Tokens[Index], 'left')
|
|
local right = getPosition(Tokens[Index] + #token - 1, 'right')
|
|
pushError {
|
|
type = 'ERR_ASSIGN_AS_EQ',
|
|
start = left,
|
|
finish = right,
|
|
fix = {
|
|
title = 'FIX_ASSIGN_AS_EQ',
|
|
{
|
|
start = left,
|
|
finish = right,
|
|
text = '=',
|
|
}
|
|
}
|
|
}
|
|
Index = Index + 2
|
|
return true
|
|
end
|
|
if isAction then
|
|
if token == '+='
|
|
or token == '-='
|
|
or token == '*='
|
|
or token == '/='
|
|
or token == '%='
|
|
or token == '^='
|
|
or token == '//='
|
|
or token == '|='
|
|
or token == '&='
|
|
or token == '>>='
|
|
or token == '<<=' then
|
|
if not State.options.nonstandardSymbol[token] then
|
|
unknownSymbol()
|
|
end
|
|
Index = Index + 2
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function parseLocalAttrs()
|
|
local attrs
|
|
while true do
|
|
skipSpace()
|
|
local token = Tokens[Index + 1]
|
|
if token ~= '<' then
|
|
break
|
|
end
|
|
if not attrs then
|
|
attrs = {
|
|
type = 'localattrs',
|
|
}
|
|
end
|
|
local attr = {
|
|
type = 'localattr',
|
|
parent = attrs,
|
|
start = getPosition(Tokens[Index], 'left'),
|
|
finish = getPosition(Tokens[Index], 'right'),
|
|
}
|
|
attrs[#attrs+1] = attr
|
|
Index = Index + 2
|
|
skipSpace()
|
|
local word, wstart, wfinish = peekWord()
|
|
if word then
|
|
attr[1] = word
|
|
attr.finish = wfinish
|
|
Index = Index + 2
|
|
if word ~= 'const'
|
|
and word ~= 'close' then
|
|
pushError {
|
|
type = 'UNKNOWN_ATTRIBUTE',
|
|
start = wstart,
|
|
finish = wfinish,
|
|
}
|
|
end
|
|
else
|
|
missName()
|
|
end
|
|
attr.finish = lastRightPosition()
|
|
skipSpace()
|
|
if Tokens[Index + 1] == '>' then
|
|
attr.finish = getPosition(Tokens[Index], 'right')
|
|
Index = Index + 2
|
|
elseif Tokens[Index + 1] == '>=' then
|
|
attr.finish = getPosition(Tokens[Index], 'right')
|
|
pushError {
|
|
type = 'MISS_SPACE_BETWEEN',
|
|
start = getPosition(Tokens[Index], 'left'),
|
|
finish = getPosition(Tokens[Index] + 1, 'right'),
|
|
}
|
|
Index = Index + 2
|
|
else
|
|
missSymbol '>'
|
|
end
|
|
if State.version ~= 'Lua 5.4' then
|
|
pushError {
|
|
type = 'UNSUPPORT_SYMBOL',
|
|
start = attr.start,
|
|
finish = attr.finish,
|
|
version = 'Lua 5.4',
|
|
info = {
|
|
version = State.version
|
|
}
|
|
}
|
|
end
|
|
end
|
|
return attrs
|
|
end
|
|
|
|
local function createLocal(obj, attrs)
|
|
obj.type = 'local'
|
|
obj.effect = obj.finish
|
|
|
|
if attrs then
|
|
obj.attrs = attrs
|
|
attrs.parent = obj
|
|
end
|
|
|
|
local chunk = Chunk[#Chunk]
|
|
if chunk then
|
|
local locals = chunk.locals
|
|
if not locals then
|
|
locals = {}
|
|
chunk.locals = locals
|
|
end
|
|
locals[#locals+1] = obj
|
|
LocalCount = LocalCount + 1
|
|
if LocalCount > LocalLimit then
|
|
pushError {
|
|
type = 'LOCAL_LIMIT',
|
|
start = obj.start,
|
|
finish = obj.finish,
|
|
}
|
|
end
|
|
end
|
|
return obj
|
|
end
|
|
|
|
local function pushChunk(chunk)
|
|
Chunk[#Chunk+1] = chunk
|
|
end
|
|
|
|
local function resolveLable(label, obj)
|
|
if not label.ref then
|
|
label.ref = {}
|
|
end
|
|
label.ref[#label.ref+1] = obj
|
|
obj.node = label
|
|
|
|
-- 如果有局部变量在 goto 与 label 之间声明,
|
|
-- 并在 label 之后使用,则算作语法错误
|
|
|
|
-- 如果 label 在 goto 之前声明,那么不会有中间声明的局部变量
|
|
if obj.start > label.start then
|
|
return
|
|
end
|
|
|
|
local block = guide.getBlock(obj)
|
|
local locals = block and block.locals
|
|
if not locals then
|
|
return
|
|
end
|
|
|
|
for i = 1, #locals do
|
|
local loc = locals[i]
|
|
-- 检查局部变量声明位置为 goto 与 label 之间
|
|
if loc.start < obj.start or loc.finish > label.finish then
|
|
goto CONTINUE
|
|
end
|
|
-- 检查局部变量的使用位置在 label 之后
|
|
local refs = loc.ref
|
|
if not refs then
|
|
goto CONTINUE
|
|
end
|
|
for j = 1, #refs do
|
|
local ref = refs[j]
|
|
if ref.finish > label.finish then
|
|
pushError {
|
|
type = 'JUMP_LOCAL_SCOPE',
|
|
start = obj.start,
|
|
finish = obj.finish,
|
|
info = {
|
|
loc = loc[1],
|
|
},
|
|
relative = {
|
|
{
|
|
start = label.start,
|
|
finish = label.finish,
|
|
},
|
|
{
|
|
start = loc.start,
|
|
finish = loc.finish,
|
|
}
|
|
},
|
|
}
|
|
return
|
|
end
|
|
end
|
|
::CONTINUE::
|
|
end
|
|
end
|
|
|
|
local function resolveGoTo(gotos)
|
|
for i = 1, #gotos do
|
|
local action = gotos[i]
|
|
local label = guide.getLabel(action, action[1])
|
|
if label then
|
|
resolveLable(label, action)
|
|
else
|
|
pushError {
|
|
type = 'NO_VISIBLE_LABEL',
|
|
start = action.start,
|
|
finish = action.finish,
|
|
info = {
|
|
label = action[1],
|
|
}
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
local function popChunk()
|
|
local chunk = Chunk[#Chunk]
|
|
if chunk.gotos then
|
|
resolveGoTo(chunk.gotos)
|
|
chunk.gotos = nil
|
|
end
|
|
local lastAction = chunk[#chunk]
|
|
if lastAction then
|
|
chunk.finish = lastAction.finish
|
|
end
|
|
Chunk[#Chunk] = nil
|
|
end
|
|
|
|
local function parseNil()
|
|
if Tokens[Index + 1] ~= 'nil' then
|
|
return nil
|
|
end
|
|
local offset = Tokens[Index]
|
|
Index = Index + 2
|
|
return {
|
|
type = 'nil',
|
|
start = getPosition(offset, 'left'),
|
|
finish = getPosition(offset + 2, 'right'),
|
|
}
|
|
end
|
|
|
|
local function parseBoolean()
|
|
local word = Tokens[Index+1]
|
|
if word ~= 'true'
|
|
and word ~= 'false' then
|
|
return nil
|
|
end
|
|
local start = getPosition(Tokens[Index], 'left')
|
|
local finish = getPosition(Tokens[Index] + #word - 1, 'right')
|
|
Index = Index + 2
|
|
return {
|
|
type = 'boolean',
|
|
start = start,
|
|
finish = finish,
|
|
[1] = word == 'true' and true or false,
|
|
}
|
|
end
|
|
|
|
local function parseStringUnicode()
|
|
local offset = Tokens[Index] + 1
|
|
if ssub(Lua, offset, offset) ~= '{' then
|
|
local pos = getPosition(offset, 'left')
|
|
missSymbol('{', pos)
|
|
return nil, offset
|
|
end
|
|
local leftPos = getPosition(offset, 'left')
|
|
local x16 = smatch(Lua, '^%w*', offset + 1)
|
|
local rightPos = getPosition(offset + #x16, 'right')
|
|
offset = offset + #x16 + 1
|
|
if ssub(Lua, offset, offset) == '}' then
|
|
offset = offset + 1
|
|
rightPos = rightPos + 1
|
|
else
|
|
missSymbol('}', rightPos)
|
|
end
|
|
offset = offset + 1
|
|
if #x16 == 0 then
|
|
pushError {
|
|
type = 'UTF8_SMALL',
|
|
start = leftPos,
|
|
finish = rightPos,
|
|
}
|
|
return '', offset
|
|
end
|
|
if State.version ~= 'Lua 5.3'
|
|
and State.version ~= 'Lua 5.4'
|
|
and State.version ~= 'LuaJIT'
|
|
then
|
|
pushError {
|
|
type = 'ERR_ESC',
|
|
start = leftPos - 2,
|
|
finish = rightPos,
|
|
version = {'Lua 5.3', 'Lua 5.4', 'LuaJIT'},
|
|
info = {
|
|
version = State.version,
|
|
}
|
|
}
|
|
return nil, offset
|
|
end
|
|
local byte = tonumber(x16, 16)
|
|
if not byte then
|
|
for i = 1, #x16 do
|
|
if not tonumber(ssub(x16, i, i), 16) then
|
|
pushError {
|
|
type = 'MUST_X16',
|
|
start = leftPos + i,
|
|
finish = leftPos + i + 1,
|
|
}
|
|
end
|
|
end
|
|
return nil, offset
|
|
end
|
|
if State.version == 'Lua 5.4' then
|
|
if byte < 0 or byte > 0x7FFFFFFF then
|
|
pushError {
|
|
type = 'UTF8_MAX',
|
|
start = leftPos,
|
|
finish = rightPos,
|
|
info = {
|
|
min = '00000000',
|
|
max = '7FFFFFFF',
|
|
}
|
|
}
|
|
return nil, offset
|
|
end
|
|
else
|
|
if byte < 0 or byte > 0x10FFFF then
|
|
pushError {
|
|
type = 'UTF8_MAX',
|
|
start = leftPos,
|
|
finish = rightPos,
|
|
version = byte <= 0x7FFFFFFF and 'Lua 5.4' or nil,
|
|
info = {
|
|
min = '000000',
|
|
max = '10FFFF',
|
|
}
|
|
}
|
|
end
|
|
end
|
|
if byte >= 0 and byte <= 0x10FFFF then
|
|
return uchar(byte), offset
|
|
end
|
|
return '', offset
|
|
end
|
|
|
|
local stringPool = {}
|
|
local function parseShortString()
|
|
local mark = Tokens[Index+1]
|
|
local startOffset = Tokens[Index]
|
|
local startPos = getPosition(startOffset, 'left')
|
|
Index = Index + 2
|
|
local stringIndex = 0
|
|
local currentOffset = startOffset + 1
|
|
local escs = {}
|
|
while true do
|
|
local token = Tokens[Index + 1]
|
|
if token == mark then
|
|
stringIndex = stringIndex + 1
|
|
stringPool[stringIndex] = ssub(Lua, currentOffset, Tokens[Index] - 1)
|
|
Index = Index + 2
|
|
break
|
|
end
|
|
if NLMap[token] then
|
|
stringIndex = stringIndex + 1
|
|
stringPool[stringIndex] = ssub(Lua, currentOffset, Tokens[Index] - 1)
|
|
missSymbol(mark)
|
|
break
|
|
end
|
|
if not token then
|
|
stringIndex = stringIndex + 1
|
|
stringPool[stringIndex] = ssub(Lua, currentOffset or -1)
|
|
missSymbol(mark)
|
|
break
|
|
end
|
|
if token == '\\' then
|
|
stringIndex = stringIndex + 1
|
|
stringPool[stringIndex] = ssub(Lua, currentOffset, Tokens[Index] - 1)
|
|
currentOffset = Tokens[Index]
|
|
Index = Index + 2
|
|
if not Tokens[Index] then
|
|
goto CONTINUE
|
|
end
|
|
local escLeft = getPosition(currentOffset, 'left')
|
|
-- has space?
|
|
if Tokens[Index] - currentOffset > 1 then
|
|
local right = getPosition(currentOffset + 1, 'right')
|
|
pushError {
|
|
type = 'ERR_ESC',
|
|
start = escLeft,
|
|
finish = right,
|
|
}
|
|
escs[#escs+1] = escLeft
|
|
escs[#escs+1] = right
|
|
escs[#escs+1] = 'err'
|
|
goto CONTINUE
|
|
end
|
|
local nextToken = ssub(Tokens[Index + 1], 1, 1)
|
|
if EscMap[nextToken] then
|
|
stringIndex = stringIndex + 1
|
|
stringPool[stringIndex] = EscMap[nextToken]
|
|
currentOffset = Tokens[Index] + #nextToken
|
|
Index = Index + 2
|
|
escs[#escs+1] = escLeft
|
|
escs[#escs+1] = escLeft + 2
|
|
escs[#escs+1] = 'normal'
|
|
goto CONTINUE
|
|
end
|
|
if nextToken == mark then
|
|
stringIndex = stringIndex + 1
|
|
stringPool[stringIndex] = mark
|
|
currentOffset = Tokens[Index] + #nextToken
|
|
Index = Index + 2
|
|
escs[#escs+1] = escLeft
|
|
escs[#escs+1] = escLeft + 2
|
|
escs[#escs+1] = 'normal'
|
|
goto CONTINUE
|
|
end
|
|
if nextToken == 'z' then
|
|
Index = Index + 2
|
|
repeat until not skipNL()
|
|
currentOffset = Tokens[Index]
|
|
escs[#escs+1] = escLeft
|
|
escs[#escs+1] = escLeft + 2
|
|
escs[#escs+1] = 'normal'
|
|
goto CONTINUE
|
|
end
|
|
if CharMapNumber[nextToken] then
|
|
local numbers = smatch(Tokens[Index + 1], '^%d+')
|
|
if #numbers > 3 then
|
|
numbers = ssub(numbers, 1, 3)
|
|
end
|
|
currentOffset = Tokens[Index] + #numbers
|
|
fastForwardToken(currentOffset)
|
|
local right = getPosition(currentOffset - 1, 'right')
|
|
local byte = tointeger(numbers)
|
|
if byte and byte <= 255 then
|
|
stringIndex = stringIndex + 1
|
|
stringPool[stringIndex] = schar(byte)
|
|
else
|
|
pushError {
|
|
type = 'ERR_ESC',
|
|
start = escLeft,
|
|
finish = right,
|
|
}
|
|
end
|
|
escs[#escs+1] = escLeft
|
|
escs[#escs+1] = right
|
|
escs[#escs+1] = 'byte'
|
|
goto CONTINUE
|
|
end
|
|
if nextToken == 'x' then
|
|
local left = getPosition(Tokens[Index] - 1, 'left')
|
|
local x16 = ssub(Tokens[Index + 1], 2, 3)
|
|
local byte = tonumber(x16, 16)
|
|
if byte then
|
|
currentOffset = Tokens[Index] + 3
|
|
stringIndex = stringIndex + 1
|
|
stringPool[stringIndex] = schar(byte)
|
|
else
|
|
currentOffset = Tokens[Index] + 1
|
|
pushError {
|
|
type = 'MISS_ESC_X',
|
|
start = getPosition(currentOffset, 'left'),
|
|
finish = getPosition(currentOffset + 1, 'right'),
|
|
}
|
|
end
|
|
local right = getPosition(currentOffset + 1, 'right')
|
|
escs[#escs+1] = escLeft
|
|
escs[#escs+1] = right
|
|
escs[#escs+1] = 'byte'
|
|
if State.version == 'Lua 5.1' then
|
|
pushError {
|
|
type = 'ERR_ESC',
|
|
start = left,
|
|
finish = left + 4,
|
|
version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'},
|
|
info = {
|
|
version = State.version,
|
|
}
|
|
}
|
|
end
|
|
Index = Index + 2
|
|
goto CONTINUE
|
|
end
|
|
if nextToken == 'u' then
|
|
local str, newOffset = parseStringUnicode()
|
|
if str then
|
|
stringIndex = stringIndex + 1
|
|
stringPool[stringIndex] = str
|
|
end
|
|
currentOffset = newOffset
|
|
fastForwardToken(currentOffset - 1)
|
|
local right = getPosition(currentOffset + 1, 'right')
|
|
escs[#escs+1] = escLeft
|
|
escs[#escs+1] = right
|
|
escs[#escs+1] = 'unicode'
|
|
goto CONTINUE
|
|
end
|
|
if NLMap[nextToken] then
|
|
stringIndex = stringIndex + 1
|
|
stringPool[stringIndex] = '\n'
|
|
currentOffset = Tokens[Index] + #nextToken
|
|
skipNL()
|
|
local right = getPosition(currentOffset + 1, 'right')
|
|
escs[#escs+1] = escLeft
|
|
escs[#escs+1] = escLeft + 1
|
|
escs[#escs+1] = 'normal'
|
|
goto CONTINUE
|
|
end
|
|
local right = getPosition(currentOffset + 1, 'right')
|
|
pushError {
|
|
type = 'ERR_ESC',
|
|
start = escLeft,
|
|
finish = right,
|
|
}
|
|
escs[#escs+1] = escLeft
|
|
escs[#escs+1] = right
|
|
escs[#escs+1] = 'err'
|
|
end
|
|
Index = Index + 2
|
|
::CONTINUE::
|
|
end
|
|
local stringResult = tconcat(stringPool, '', 1, stringIndex)
|
|
local str = {
|
|
type = 'string',
|
|
start = startPos,
|
|
finish = lastRightPosition(),
|
|
escs = #escs > 0 and escs or nil,
|
|
[1] = stringResult,
|
|
[2] = mark,
|
|
}
|
|
if mark == '`' then
|
|
if not State.options.nonstandardSymbol[mark] then
|
|
pushError {
|
|
type = 'ERR_NONSTANDARD_SYMBOL',
|
|
start = startPos,
|
|
finish = str.finish,
|
|
info = {
|
|
symbol = '"',
|
|
},
|
|
fix = {
|
|
title = 'FIX_NONSTANDARD_SYMBOL',
|
|
symbol = '"',
|
|
{
|
|
start = startPos,
|
|
finish = startPos + 1,
|
|
text = '"',
|
|
},
|
|
{
|
|
start = str.finish - 1,
|
|
finish = str.finish,
|
|
text = '"',
|
|
},
|
|
}
|
|
}
|
|
end
|
|
end
|
|
return str
|
|
end
|
|
|
|
local function parseString()
|
|
local c = Tokens[Index + 1]
|
|
if CharMapStrSH[c] then
|
|
return parseShortString()
|
|
end
|
|
if CharMapStrLH[c] then
|
|
return parseLongString()
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local function parseNumber10(start)
|
|
local integer = true
|
|
local integerPart = smatch(Lua, '^%d*', start)
|
|
local offset = start + #integerPart
|
|
-- float part
|
|
if ssub(Lua, offset, offset) == '.' then
|
|
local floatPart = smatch(Lua, '^%d*', offset + 1)
|
|
integer = false
|
|
offset = offset + #floatPart + 1
|
|
end
|
|
-- exp part
|
|
local echar = ssub(Lua, offset, offset)
|
|
if CharMapE10[echar] then
|
|
integer = false
|
|
offset = offset + 1
|
|
local nextChar = ssub(Lua, offset, offset)
|
|
if CharMapSign[nextChar] then
|
|
offset = offset + 1
|
|
end
|
|
local exp = smatch(Lua, '^%d*', offset)
|
|
offset = offset + #exp
|
|
if #exp == 0 then
|
|
pushError {
|
|
type = 'MISS_EXPONENT',
|
|
start = getPosition(offset - 1, 'right'),
|
|
finish = getPosition(offset - 1, 'right'),
|
|
}
|
|
end
|
|
end
|
|
return tonumber(ssub(Lua, start, offset - 1)), offset, integer
|
|
end
|
|
|
|
local function parseNumber16(start)
|
|
local integerPart = smatch(Lua, '^[%da-fA-F]*', start)
|
|
local offset = start + #integerPart
|
|
local integer = true
|
|
-- float part
|
|
if ssub(Lua, offset, offset) == '.' then
|
|
local floatPart = smatch(Lua, '^[%da-fA-F]*', offset + 1)
|
|
integer = false
|
|
offset = offset + #floatPart + 1
|
|
if #integerPart == 0 and #floatPart == 0 then
|
|
pushError {
|
|
type = 'MUST_X16',
|
|
start = getPosition(offset - 1, 'right'),
|
|
finish = getPosition(offset - 1, 'right'),
|
|
}
|
|
end
|
|
else
|
|
if #integerPart == 0 then
|
|
pushError {
|
|
type = 'MUST_X16',
|
|
start = getPosition(offset - 1, 'right'),
|
|
finish = getPosition(offset - 1, 'right'),
|
|
}
|
|
return 0, offset
|
|
end
|
|
end
|
|
-- exp part
|
|
local echar = ssub(Lua, offset, offset)
|
|
if CharMapE16[echar] then
|
|
integer = false
|
|
offset = offset + 1
|
|
local nextChar = ssub(Lua, offset, offset)
|
|
if CharMapSign[nextChar] then
|
|
offset = offset + 1
|
|
end
|
|
local exp = smatch(Lua, '^%d*', offset)
|
|
offset = offset + #exp
|
|
end
|
|
local n = tonumber(ssub(Lua, start - 2, offset - 1))
|
|
return n, offset, integer
|
|
end
|
|
|
|
local function parseNumber2(start)
|
|
local bins = smatch(Lua, '^[01]*', start)
|
|
local offset = start + #bins
|
|
if State.version ~= 'LuaJIT' then
|
|
pushError {
|
|
type = 'UNSUPPORT_SYMBOL',
|
|
start = getPosition(start - 2, 'left'),
|
|
finish = getPosition(offset - 1, 'right'),
|
|
version = 'LuaJIT',
|
|
info = {
|
|
version = 'Lua 5.4',
|
|
}
|
|
}
|
|
end
|
|
return tonumber(bins, 2), offset
|
|
end
|
|
|
|
local function dropNumberTail(offset, integer)
|
|
local _, finish, word = sfind(Lua, '^([%.%w_\x80-\xff]+)', offset)
|
|
if not finish then
|
|
return offset
|
|
end
|
|
if integer then
|
|
if supper(ssub(word, 1, 2)) == 'LL' then
|
|
if State.version ~= 'LuaJIT' then
|
|
pushError {
|
|
type = 'UNSUPPORT_SYMBOL',
|
|
start = getPosition(offset, 'left'),
|
|
finish = getPosition(offset + 1, 'right'),
|
|
version = 'LuaJIT',
|
|
info = {
|
|
version = State.version,
|
|
}
|
|
}
|
|
end
|
|
offset = offset + 2
|
|
word = ssub(word, offset)
|
|
elseif supper(ssub(word, 1, 3)) == 'ULL' then
|
|
if State.version ~= 'LuaJIT' then
|
|
pushError {
|
|
type = 'UNSUPPORT_SYMBOL',
|
|
start = getPosition(offset, 'left'),
|
|
finish = getPosition(offset + 2, 'right'),
|
|
version = 'LuaJIT',
|
|
info = {
|
|
version = State.version,
|
|
}
|
|
}
|
|
end
|
|
offset = offset + 3
|
|
word = ssub(word, offset)
|
|
end
|
|
end
|
|
if supper(ssub(word, 1, 1)) == 'I' then
|
|
if State.version ~= 'LuaJIT' then
|
|
pushError {
|
|
type = 'UNSUPPORT_SYMBOL',
|
|
start = getPosition(offset, 'left'),
|
|
finish = getPosition(offset, 'right'),
|
|
version = 'LuaJIT',
|
|
info = {
|
|
version = State.version,
|
|
}
|
|
}
|
|
end
|
|
offset = offset + 1
|
|
word = ssub(word, offset)
|
|
end
|
|
if #word > 0 then
|
|
pushError {
|
|
type = 'MALFORMED_NUMBER',
|
|
start = getPosition(offset, 'left'),
|
|
finish = getPosition(finish, 'right'),
|
|
}
|
|
end
|
|
return finish + 1
|
|
end
|
|
|
|
local function parseNumber()
|
|
local offset = Tokens[Index]
|
|
if not offset then
|
|
return nil
|
|
end
|
|
local startPos = getPosition(offset, 'left')
|
|
local neg
|
|
if ssub(Lua, offset, offset) == '-' then
|
|
neg = true
|
|
offset = offset + 1
|
|
end
|
|
local number, integer
|
|
local firstChar = ssub(Lua, offset, offset)
|
|
if firstChar == '.' then
|
|
number, offset = parseNumber10(offset)
|
|
integer = false
|
|
elseif firstChar == '0' then
|
|
local nextChar = ssub(Lua, offset + 1, offset + 1)
|
|
if CharMapN16[nextChar] then
|
|
number, offset, integer = parseNumber16(offset + 2)
|
|
elseif CharMapN2[nextChar] then
|
|
number, offset = parseNumber2(offset + 2)
|
|
integer = true
|
|
else
|
|
number, offset, integer = parseNumber10(offset)
|
|
end
|
|
elseif CharMapNumber[firstChar] then
|
|
number, offset, integer = parseNumber10(offset)
|
|
else
|
|
return nil
|
|
end
|
|
if not number then
|
|
number = 0
|
|
end
|
|
if neg then
|
|
number = - number
|
|
end
|
|
local result = {
|
|
type = integer and 'integer' or 'number',
|
|
start = startPos,
|
|
finish = getPosition(offset - 1, 'right'),
|
|
[1] = number,
|
|
}
|
|
offset = dropNumberTail(offset, integer)
|
|
fastForwardToken(offset)
|
|
return result
|
|
end
|
|
|
|
local function isKeyWord(word)
|
|
if KeyWord[word] then
|
|
return true
|
|
end
|
|
if word == 'goto' then
|
|
return State.version ~= 'Lua 5.1'
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function parseName(asAction)
|
|
local word = peekWord()
|
|
if not word then
|
|
return nil
|
|
end
|
|
if ChunkFinishMap[word] then
|
|
return nil
|
|
end
|
|
if asAction and ChunkStartMap[word] then
|
|
return nil
|
|
end
|
|
local startPos = getPosition(Tokens[Index], 'left')
|
|
local finishPos = getPosition(Tokens[Index] + #word - 1, 'right')
|
|
Index = Index + 2
|
|
if not State.options.unicodeName and word:find '[\x80-\xff]' then
|
|
pushError {
|
|
type = 'UNICODE_NAME',
|
|
start = startPos,
|
|
finish = finishPos,
|
|
}
|
|
end
|
|
if isKeyWord(word) then
|
|
pushError {
|
|
type = 'KEYWORD',
|
|
start = startPos,
|
|
finish = finishPos,
|
|
}
|
|
end
|
|
return {
|
|
type = 'name',
|
|
start = startPos,
|
|
finish = finishPos,
|
|
[1] = word,
|
|
}
|
|
end
|
|
|
|
local function parseNameOrList(parent)
|
|
local first = parseName()
|
|
if not first then
|
|
return nil
|
|
end
|
|
skipSpace()
|
|
local list
|
|
while true do
|
|
if Tokens[Index + 1] ~= ',' then
|
|
break
|
|
end
|
|
Index = Index + 2
|
|
skipSpace()
|
|
local name = parseName(true)
|
|
if not name then
|
|
missName()
|
|
break
|
|
end
|
|
if not list then
|
|
list = {
|
|
type = 'list',
|
|
start = first.start,
|
|
finish = first.finish,
|
|
parent = parent,
|
|
[1] = first
|
|
}
|
|
end
|
|
list[#list+1] = name
|
|
list.finish = name.finish
|
|
end
|
|
return list or first
|
|
end
|
|
|
|
local function parseExpList(mini)
|
|
local list
|
|
local wantSep = false
|
|
while true do
|
|
skipSpace()
|
|
local token = Tokens[Index + 1]
|
|
if not token then
|
|
break
|
|
end
|
|
if ListFinishMap[token] then
|
|
break
|
|
end
|
|
if token == ',' then
|
|
local sepPos = getPosition(Tokens[Index], 'right')
|
|
if not wantSep then
|
|
pushError {
|
|
type = 'UNEXPECT_SYMBOL',
|
|
start = getPosition(Tokens[Index], 'left'),
|
|
finish = sepPos,
|
|
info = {
|
|
symbol = ',',
|
|
}
|
|
}
|
|
end
|
|
wantSep = false
|
|
Index = Index + 2
|
|
goto CONTINUE
|
|
else
|
|
if mini then
|
|
if wantSep then
|
|
break
|
|
end
|
|
local nextToken = peekWord()
|
|
if isKeyWord(nextToken)
|
|
and nextToken ~= 'function'
|
|
and nextToken ~= 'true'
|
|
and nextToken ~= 'false'
|
|
and nextToken ~= 'nil'
|
|
and nextToken ~= 'not' then
|
|
break
|
|
end
|
|
end
|
|
local exp = parseExp()
|
|
if not exp then
|
|
break
|
|
end
|
|
if wantSep then
|
|
missSymbol(',', list[#list].finish, exp.start)
|
|
end
|
|
wantSep = true
|
|
if not list then
|
|
list = {
|
|
type = 'list',
|
|
start = exp.start,
|
|
}
|
|
end
|
|
list[#list+1] = exp
|
|
list.finish = exp.finish
|
|
exp.parent = list
|
|
end
|
|
::CONTINUE::
|
|
end
|
|
if not list then
|
|
return nil
|
|
end
|
|
if not wantSep then
|
|
missExp()
|
|
end
|
|
return list
|
|
end
|
|
|
|
local function parseIndex()
|
|
local start = getPosition(Tokens[Index], 'left')
|
|
Index = Index + 2
|
|
skipSpace()
|
|
local exp = parseExp()
|
|
local index = {
|
|
type = 'index',
|
|
start = start,
|
|
finish = exp and exp.finish or (start + 1),
|
|
index = exp
|
|
}
|
|
if exp then
|
|
exp.parent = index
|
|
else
|
|
missExp()
|
|
end
|
|
skipSpace()
|
|
if Tokens[Index + 1] == ']' then
|
|
index.finish = getPosition(Tokens[Index], 'right')
|
|
Index = Index + 2
|
|
else
|
|
missSymbol ']'
|
|
end
|
|
return index
|
|
end
|
|
|
|
local function parseTable()
|
|
local tbl = {
|
|
type = 'table',
|
|
start = getPosition(Tokens[Index], 'left'),
|
|
finish = getPosition(Tokens[Index], 'right'),
|
|
}
|
|
Index = Index + 2
|
|
local index = 0
|
|
local tindex = 0
|
|
local wantSep = false
|
|
while true do
|
|
skipSpace(true)
|
|
local token = Tokens[Index + 1]
|
|
if token == '}' then
|
|
Index = Index + 2
|
|
break
|
|
end
|
|
if CharMapTSep[token] then
|
|
if not wantSep then
|
|
missExp()
|
|
end
|
|
wantSep = false
|
|
Index = Index + 2
|
|
goto CONTINUE
|
|
end
|
|
local lastRight = lastRightPosition()
|
|
|
|
if peekWord() then
|
|
local savePoint = getSavePoint()
|
|
local name = parseName()
|
|
if name then
|
|
skipSpace()
|
|
if Tokens[Index + 1] == '=' then
|
|
Index = Index + 2
|
|
if wantSep then
|
|
pushError {
|
|
type = 'MISS_SEP_IN_TABLE',
|
|
start = lastRight,
|
|
finish = getPosition(Tokens[Index], 'left'),
|
|
}
|
|
end
|
|
wantSep = true
|
|
skipSpace()
|
|
local fvalue = parseExp()
|
|
local tfield = {
|
|
type = 'tablefield',
|
|
start = name.start,
|
|
finish = name.finish,
|
|
range = fvalue and fvalue.finish,
|
|
node = tbl,
|
|
parent = tbl,
|
|
field = name,
|
|
value = fvalue,
|
|
}
|
|
name.type = 'field'
|
|
name.parent = tfield
|
|
if fvalue then
|
|
fvalue.parent = tfield
|
|
else
|
|
missExp()
|
|
end
|
|
index = index + 1
|
|
tbl[index] = tfield
|
|
goto CONTINUE
|
|
end
|
|
end
|
|
savePoint()
|
|
end
|
|
|
|
local exp = parseExp(true)
|
|
if exp then
|
|
if wantSep then
|
|
pushError {
|
|
type = 'MISS_SEP_IN_TABLE',
|
|
start = lastRight,
|
|
finish = exp.start,
|
|
}
|
|
end
|
|
wantSep = true
|
|
if exp.type == 'varargs' then
|
|
index = index + 1
|
|
tbl[index] = exp
|
|
exp.parent = tbl
|
|
goto CONTINUE
|
|
end
|
|
index = index + 1
|
|
tindex = tindex + 1
|
|
local texp = {
|
|
type = 'tableexp',
|
|
start = exp.start,
|
|
finish = exp.finish,
|
|
tindex = tindex,
|
|
parent = tbl,
|
|
value = exp,
|
|
}
|
|
exp.parent = texp
|
|
tbl[index] = texp
|
|
goto CONTINUE
|
|
end
|
|
|
|
if token == '[' then
|
|
if wantSep then
|
|
pushError {
|
|
type = 'MISS_SEP_IN_TABLE',
|
|
start = lastRight,
|
|
finish = getPosition(Tokens[Index], 'left'),
|
|
}
|
|
end
|
|
wantSep = true
|
|
local tindex = parseIndex()
|
|
skipSpace()
|
|
tindex.type = 'tableindex'
|
|
tindex.node = tbl
|
|
tindex.parent = tbl
|
|
index = index + 1
|
|
tbl[index] = tindex
|
|
if expectAssign() then
|
|
skipSpace()
|
|
local ivalue = parseExp()
|
|
if ivalue then
|
|
ivalue.parent = tindex
|
|
tindex.range = ivalue.finish
|
|
tindex.value = ivalue
|
|
else
|
|
missExp()
|
|
end
|
|
else
|
|
missSymbol '='
|
|
end
|
|
goto CONTINUE
|
|
end
|
|
|
|
missSymbol '}'
|
|
break
|
|
::CONTINUE::
|
|
end
|
|
tbl.finish = lastRightPosition()
|
|
return tbl
|
|
end
|
|
|
|
local function addDummySelf(node, call)
|
|
if node.type ~= 'getmethod' then
|
|
return
|
|
end
|
|
-- dummy param `self`
|
|
if not call.args then
|
|
call.args = {
|
|
type = 'callargs',
|
|
start = call.start,
|
|
finish = call.finish,
|
|
parent = call,
|
|
}
|
|
end
|
|
local self = {
|
|
type = 'self',
|
|
start = node.colon.start,
|
|
finish = node.colon.finish,
|
|
parent = call.args,
|
|
[1] = 'self',
|
|
}
|
|
tinsert(call.args, 1, self)
|
|
end
|
|
|
|
local function checkAmbiguityCall(call, parenPos)
|
|
if State.version ~= 'Lua 5.1' then
|
|
return
|
|
end
|
|
local node = call.node
|
|
local nodeRow = guide.rowColOf(node.finish)
|
|
local callRow = guide.rowColOf(parenPos)
|
|
if nodeRow == callRow then
|
|
return
|
|
end
|
|
pushError {
|
|
type = 'AMBIGUOUS_SYNTAX',
|
|
start = parenPos,
|
|
finish = call.finish,
|
|
}
|
|
end
|
|
|
|
local function bindSpecial(source, name)
|
|
if Specials[name] then
|
|
addSpecial(name, source)
|
|
else
|
|
local ospeicals = State.options.special
|
|
if ospeicals and ospeicals[name] then
|
|
addSpecial(ospeicals[name], source)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function parseSimple(node, funcName)
|
|
local currentName
|
|
if node.type == 'getglobal'
|
|
or node.type == 'getlocal' then
|
|
currentName = node[1]
|
|
end
|
|
local lastMethod
|
|
while true do
|
|
if lastMethod and node.node == lastMethod then
|
|
if node.type ~= 'call' then
|
|
missSymbol('(', node.node.finish, node.node.finish)
|
|
end
|
|
lastMethod = nil
|
|
end
|
|
skipSpace()
|
|
local token = Tokens[Index + 1]
|
|
if token == '.' then
|
|
local dot = {
|
|
type = token,
|
|
start = getPosition(Tokens[Index], 'left'),
|
|
finish = getPosition(Tokens[Index], 'right'),
|
|
}
|
|
Index = Index + 2
|
|
skipSpace()
|
|
local field = parseName(true)
|
|
local getfield = {
|
|
type = 'getfield',
|
|
start = node.start,
|
|
finish = lastRightPosition(),
|
|
node = node,
|
|
dot = dot,
|
|
field = field
|
|
}
|
|
if field then
|
|
field.parent = getfield
|
|
field.type = 'field'
|
|
if currentName then
|
|
if node.type == 'getlocal'
|
|
or node.type == 'getglobal'
|
|
or node.type == 'getfield' then
|
|
currentName = currentName .. '.' .. field[1]
|
|
bindSpecial(getfield, currentName)
|
|
else
|
|
currentName = nil
|
|
end
|
|
end
|
|
else
|
|
pushError {
|
|
type = 'MISS_FIELD',
|
|
start = lastRightPosition(),
|
|
finish = lastRightPosition(),
|
|
}
|
|
end
|
|
node.parent = getfield
|
|
node.next = getfield
|
|
node = getfield
|
|
elseif token == ':' then
|
|
local colon = {
|
|
type = token,
|
|
start = getPosition(Tokens[Index], 'left'),
|
|
finish = getPosition(Tokens[Index], 'right'),
|
|
}
|
|
Index = Index + 2
|
|
skipSpace()
|
|
local method = parseName(true)
|
|
local getmethod = {
|
|
type = 'getmethod',
|
|
start = node.start,
|
|
finish = lastRightPosition(),
|
|
node = node,
|
|
colon = colon,
|
|
method = method
|
|
}
|
|
if method then
|
|
method.parent = getmethod
|
|
method.type = 'method'
|
|
else
|
|
pushError {
|
|
type = 'MISS_METHOD',
|
|
start = lastRightPosition(),
|
|
finish = lastRightPosition(),
|
|
}
|
|
end
|
|
node.parent = getmethod
|
|
node.next = getmethod
|
|
node = getmethod
|
|
if lastMethod then
|
|
missSymbol('(', node.node.finish, node.node.finish)
|
|
end
|
|
lastMethod = getmethod
|
|
elseif token == '(' then
|
|
if funcName then
|
|
break
|
|
end
|
|
local startPos = getPosition(Tokens[Index], 'left')
|
|
local call = {
|
|
type = 'call',
|
|
start = node.start,
|
|
node = node,
|
|
}
|
|
Index = Index + 2
|
|
local args = parseExpList()
|
|
if Tokens[Index + 1] == ')' then
|
|
call.finish = getPosition(Tokens[Index], 'right')
|
|
Index = Index + 2
|
|
else
|
|
call.finish = lastRightPosition()
|
|
missSymbol ')'
|
|
end
|
|
if args then
|
|
args.type = 'callargs'
|
|
args.start = startPos
|
|
args.finish = call.finish
|
|
args.parent = call
|
|
call.args = args
|
|
end
|
|
addDummySelf(node, call)
|
|
checkAmbiguityCall(call, startPos)
|
|
node.parent = call
|
|
node = call
|
|
elseif token == '{' then
|
|
if funcName then
|
|
break
|
|
end
|
|
local tbl = parseTable()
|
|
local call = {
|
|
type = 'call',
|
|
start = node.start,
|
|
finish = tbl.finish,
|
|
node = node,
|
|
}
|
|
local args = {
|
|
type = 'callargs',
|
|
start = tbl.start,
|
|
finish = tbl.finish,
|
|
parent = call,
|
|
[1] = tbl,
|
|
}
|
|
call.args = args
|
|
addDummySelf(node, call)
|
|
tbl.parent = args
|
|
node.parent = call
|
|
node = call
|
|
elseif CharMapStrSH[token] then
|
|
if funcName then
|
|
break
|
|
end
|
|
local str = parseShortString()
|
|
local call = {
|
|
type = 'call',
|
|
start = node.start,
|
|
finish = str.finish,
|
|
node = node,
|
|
}
|
|
local args = {
|
|
type = 'callargs',
|
|
start = str.start,
|
|
finish = str.finish,
|
|
parent = call,
|
|
[1] = str,
|
|
}
|
|
call.args = args
|
|
addDummySelf(node, call)
|
|
str.parent = args
|
|
node.parent = call
|
|
node = call
|
|
elseif CharMapStrLH[token] then
|
|
local str = parseLongString()
|
|
if str then
|
|
if funcName then
|
|
break
|
|
end
|
|
local call = {
|
|
type = 'call',
|
|
start = node.start,
|
|
finish = str.finish,
|
|
node = node,
|
|
}
|
|
local args = {
|
|
type = 'callargs',
|
|
start = str.start,
|
|
finish = str.finish,
|
|
parent = call,
|
|
[1] = str,
|
|
}
|
|
call.args = args
|
|
addDummySelf(node, call)
|
|
str.parent = args
|
|
node.parent = call
|
|
node = call
|
|
else
|
|
local index = parseIndex()
|
|
local bstart = index.start
|
|
index.type = 'getindex'
|
|
index.start = node.start
|
|
index.node = node
|
|
node.next = index
|
|
node.parent = index
|
|
node = index
|
|
if funcName then
|
|
pushError {
|
|
type = 'INDEX_IN_FUNC_NAME',
|
|
start = bstart,
|
|
finish = index.finish,
|
|
}
|
|
end
|
|
end
|
|
else
|
|
break
|
|
end
|
|
end
|
|
if node.type == 'call'
|
|
and node.node == lastMethod then
|
|
lastMethod = nil
|
|
end
|
|
if node == lastMethod then
|
|
if funcName then
|
|
lastMethod = nil
|
|
end
|
|
end
|
|
if lastMethod then
|
|
missSymbol('(', lastMethod.finish)
|
|
end
|
|
return node
|
|
end
|
|
|
|
local function parseVarargs()
|
|
local varargs = {
|
|
type = 'varargs',
|
|
start = getPosition(Tokens[Index], 'left'),
|
|
finish = getPosition(Tokens[Index] + 2, 'right'),
|
|
}
|
|
Index = Index + 2
|
|
for i = #Chunk, 1, -1 do
|
|
local chunk = Chunk[i]
|
|
if chunk.vararg then
|
|
if not chunk.vararg.ref then
|
|
chunk.vararg.ref = {}
|
|
end
|
|
chunk.vararg.ref[#chunk.vararg.ref+1] = varargs
|
|
varargs.node = chunk.vararg
|
|
break
|
|
end
|
|
if chunk.type == 'main' then
|
|
break
|
|
end
|
|
if chunk.type == 'function' then
|
|
pushError {
|
|
type = 'UNEXPECT_DOTS',
|
|
start = varargs.start,
|
|
finish = varargs.finish,
|
|
}
|
|
break
|
|
end
|
|
end
|
|
return varargs
|
|
end
|
|
|
|
local function parseParen()
|
|
local pl = Tokens[Index]
|
|
local paren = {
|
|
type = 'paren',
|
|
start = getPosition(pl, 'left'),
|
|
finish = getPosition(pl, 'right')
|
|
}
|
|
Index = Index + 2
|
|
skipSpace()
|
|
local exp = parseExp()
|
|
if exp then
|
|
paren.exp = exp
|
|
paren.finish = exp.finish
|
|
exp.parent = paren
|
|
else
|
|
missExp()
|
|
end
|
|
skipSpace()
|
|
if Tokens[Index + 1] == ')' then
|
|
paren.finish = getPosition(Tokens[Index], 'right')
|
|
Index = Index + 2
|
|
else
|
|
missSymbol ')'
|
|
end
|
|
return paren
|
|
end
|
|
|
|
local function getLocal(name, pos)
|
|
for i = #Chunk, 1, -1 do
|
|
local chunk = Chunk[i]
|
|
local locals = chunk.locals
|
|
if locals then
|
|
local res
|
|
for n = 1, #locals do
|
|
local loc = locals[n]
|
|
if loc.effect > pos then
|
|
break
|
|
end
|
|
if loc[1] == name then
|
|
if not res or res.effect < loc.effect then
|
|
res = loc
|
|
end
|
|
end
|
|
end
|
|
if res then
|
|
return res
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function resolveName(node)
|
|
if not node then
|
|
return nil
|
|
end
|
|
local loc = getLocal(node[1], node.start)
|
|
if loc then
|
|
node.type = 'getlocal'
|
|
node.node = loc
|
|
if not loc.ref then
|
|
loc.ref = {}
|
|
end
|
|
loc.ref[#loc.ref+1] = node
|
|
if loc.special then
|
|
addSpecial(loc.special, node)
|
|
end
|
|
else
|
|
node.type = 'getglobal'
|
|
local env = getLocal(State.ENVMode, node.start)
|
|
if env then
|
|
node.node = env
|
|
if not env.ref then
|
|
env.ref = {}
|
|
end
|
|
env.ref[#env.ref+1] = node
|
|
end
|
|
end
|
|
local name = node[1]
|
|
bindSpecial(node, name)
|
|
return node
|
|
end
|
|
|
|
local function isChunkFinishToken(token)
|
|
local currentChunk = Chunk[#Chunk]
|
|
if not currentChunk then
|
|
return false
|
|
end
|
|
local tp = currentChunk.type
|
|
if tp == 'main' then
|
|
return false
|
|
end
|
|
if tp == 'for'
|
|
or tp == 'in'
|
|
or tp == 'loop'
|
|
or tp == 'function' then
|
|
return token == 'end'
|
|
end
|
|
if tp == 'if'
|
|
or tp == 'ifblock'
|
|
or tp == 'elseifblock'
|
|
or tp == 'elseblock' then
|
|
return token == 'then'
|
|
or token == 'end'
|
|
or token == 'else'
|
|
or token == 'elseif'
|
|
end
|
|
if tp == 'repeat' then
|
|
return token == 'until'
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function parseActions()
|
|
local rtn, last
|
|
while true do
|
|
skipSpace(true)
|
|
local token = Tokens[Index + 1]
|
|
if token == ';' then
|
|
Index = Index + 2
|
|
goto CONTINUE
|
|
end
|
|
if ChunkFinishMap[token]
|
|
and isChunkFinishToken(token) then
|
|
break
|
|
end
|
|
local action, failed = parseAction()
|
|
if failed then
|
|
if not skipUnknownSymbol() then
|
|
break
|
|
end
|
|
end
|
|
if action then
|
|
if not rtn and action.type == 'return' then
|
|
rtn = action
|
|
end
|
|
last = action
|
|
end
|
|
::CONTINUE::
|
|
end
|
|
if rtn and rtn ~= last then
|
|
pushError {
|
|
type = 'ACTION_AFTER_RETURN',
|
|
start = rtn.start,
|
|
finish = rtn.finish,
|
|
}
|
|
end
|
|
end
|
|
|
|
local function parseParams(params)
|
|
local lastSep
|
|
local hasDots
|
|
while true do
|
|
skipSpace()
|
|
local token = Tokens[Index + 1]
|
|
if not token or token == ')' then
|
|
if lastSep then
|
|
missName()
|
|
end
|
|
break
|
|
end
|
|
if token == ',' then
|
|
if lastSep or lastSep == nil then
|
|
missName()
|
|
else
|
|
lastSep = true
|
|
end
|
|
Index = Index + 2
|
|
goto CONTINUE
|
|
end
|
|
if token == '...' then
|
|
if lastSep == false then
|
|
missSymbol ','
|
|
end
|
|
lastSep = false
|
|
if not params then
|
|
params = {}
|
|
end
|
|
local vararg = {
|
|
type = '...',
|
|
start = getPosition(Tokens[Index], 'left'),
|
|
finish = getPosition(Tokens[Index] + 2, 'right'),
|
|
parent = params,
|
|
[1] = '...',
|
|
}
|
|
local chunk = Chunk[#Chunk]
|
|
chunk.vararg = vararg
|
|
params[#params+1] = vararg
|
|
if hasDots then
|
|
pushError {
|
|
type = 'ARGS_AFTER_DOTS',
|
|
start = getPosition(Tokens[Index], 'left'),
|
|
finish = getPosition(Tokens[Index] + 2, 'right'),
|
|
}
|
|
end
|
|
hasDots = true
|
|
Index = Index + 2
|
|
goto CONTINUE
|
|
end
|
|
if CharMapWord[ssub(token, 1, 1)] then
|
|
if lastSep == false then
|
|
missSymbol ','
|
|
end
|
|
lastSep = false
|
|
if not params then
|
|
params = {}
|
|
end
|
|
params[#params+1] = createLocal {
|
|
start = getPosition(Tokens[Index], 'left'),
|
|
finish = getPosition(Tokens[Index] + #token - 1, 'right'),
|
|
parent = params,
|
|
[1] = token,
|
|
}
|
|
if hasDots then
|
|
pushError {
|
|
type = 'ARGS_AFTER_DOTS',
|
|
start = getPosition(Tokens[Index], 'left'),
|
|
finish = getPosition(Tokens[Index] + #token - 1, 'right'),
|
|
}
|
|
end
|
|
if isKeyWord(token) then
|
|
pushError {
|
|
type = 'KEYWORD',
|
|
start = getPosition(Tokens[Index], 'left'),
|
|
finish = getPosition(Tokens[Index] + #token - 1, 'right'),
|
|
}
|
|
end
|
|
Index = Index + 2
|
|
goto CONTINUE
|
|
end
|
|
skipUnknownSymbol '%,%)%.'
|
|
::CONTINUE::
|
|
end
|
|
return params
|
|
end
|
|
|
|
local function parseFunction(isLocal, isAction)
|
|
local funcLeft = getPosition(Tokens[Index], 'left')
|
|
local funcRight = getPosition(Tokens[Index] + 7, 'right')
|
|
local func = {
|
|
type = 'function',
|
|
start = funcLeft,
|
|
finish = funcRight,
|
|
keyword = {
|
|
[1] = funcLeft,
|
|
[2] = funcRight,
|
|
},
|
|
}
|
|
Index = Index + 2
|
|
local LastLocalCount = LocalCount
|
|
LocalCount = 0
|
|
skipSpace(true)
|
|
local hasLeftParen = Tokens[Index + 1] == '('
|
|
if not hasLeftParen then
|
|
local name = parseName()
|
|
if name then
|
|
local simple = parseSimple(name, true)
|
|
if isLocal then
|
|
if simple == name then
|
|
createLocal(name)
|
|
else
|
|
resolveName(name)
|
|
pushError {
|
|
type = 'UNEXPECT_LFUNC_NAME',
|
|
start = simple.start,
|
|
finish = simple.finish,
|
|
}
|
|
end
|
|
else
|
|
resolveName(name)
|
|
end
|
|
func.name = simple
|
|
func.finish = simple.finish
|
|
if not isAction then
|
|
simple.parent = func
|
|
pushError {
|
|
type = 'UNEXPECT_EFUNC_NAME',
|
|
start = simple.start,
|
|
finish = simple.finish,
|
|
}
|
|
end
|
|
skipSpace(true)
|
|
hasLeftParen = Tokens[Index + 1] == '('
|
|
end
|
|
end
|
|
pushChunk(func)
|
|
local params
|
|
if func.name and func.name.type == 'getmethod' then
|
|
if func.name.type == 'getmethod' then
|
|
params = {}
|
|
params[1] = createLocal {
|
|
start = funcRight,
|
|
finish = funcRight,
|
|
parent = params,
|
|
[1] = 'self',
|
|
}
|
|
params[1].type = 'self'
|
|
end
|
|
end
|
|
if hasLeftParen then
|
|
params = params or {}
|
|
local parenLeft = getPosition(Tokens[Index], 'left')
|
|
Index = Index + 2
|
|
params = parseParams(params)
|
|
params.type = 'funcargs'
|
|
params.start = parenLeft
|
|
params.finish = lastRightPosition()
|
|
params.parent = func
|
|
func.args = params
|
|
skipSpace(true)
|
|
if Tokens[Index + 1] == ')' then
|
|
local parenRight = getPosition(Tokens[Index], 'right')
|
|
func.finish = parenRight
|
|
if params then
|
|
params.finish = parenRight
|
|
end
|
|
Index = Index + 2
|
|
skipSpace(true)
|
|
else
|
|
func.finish = lastRightPosition()
|
|
if params then
|
|
params.finish = func.finish
|
|
end
|
|
missSymbol ')'
|
|
end
|
|
else
|
|
missSymbol '('
|
|
end
|
|
parseActions()
|
|
popChunk()
|
|
if Tokens[Index + 1] == 'end' then
|
|
local endLeft = getPosition(Tokens[Index], 'left')
|
|
local endRight = getPosition(Tokens[Index] + 2, 'right')
|
|
func.keyword[3] = endLeft
|
|
func.keyword[4] = endRight
|
|
func.finish = endRight
|
|
Index = Index + 2
|
|
else
|
|
func.finish = lastRightPosition()
|
|
missEnd(funcLeft, funcRight)
|
|
end
|
|
LocalCount = LastLocalCount
|
|
return func
|
|
end
|
|
|
|
local function pushErrorNeedParen(source)
|
|
pushError {
|
|
type = 'NEED_PAREN',
|
|
start = source.start,
|
|
finish = source.finish,
|
|
fix = {
|
|
title = 'FIX_ADD_PAREN',
|
|
{
|
|
start = source.start,
|
|
finish = source.start,
|
|
text = '(',
|
|
},
|
|
{
|
|
start = source.finish,
|
|
finish = source.finish,
|
|
text = ')',
|
|
}
|
|
}
|
|
}
|
|
end
|
|
|
|
local function parseExpUnit()
|
|
local token = Tokens[Index + 1]
|
|
if token == '(' then
|
|
local paren = parseParen()
|
|
return parseSimple(paren, false)
|
|
end
|
|
|
|
if token == '...' then
|
|
local varargs = parseVarargs()
|
|
return varargs
|
|
end
|
|
|
|
if token == '{' then
|
|
local table = parseTable()
|
|
if not table then
|
|
return nil
|
|
end
|
|
local exp = parseSimple(table, false)
|
|
if exp ~= table then
|
|
pushErrorNeedParen(table)
|
|
end
|
|
return exp
|
|
end
|
|
|
|
if CharMapStrSH[token] then
|
|
local string = parseShortString()
|
|
if not string then
|
|
return nil
|
|
end
|
|
local exp = parseSimple(string, false)
|
|
if exp ~= string then
|
|
pushErrorNeedParen(string)
|
|
end
|
|
return exp
|
|
end
|
|
|
|
if CharMapStrLH[token] then
|
|
local string = parseLongString()
|
|
if not string then
|
|
return nil
|
|
end
|
|
local exp = parseSimple(string, false)
|
|
if exp ~= string then
|
|
pushErrorNeedParen(string)
|
|
end
|
|
return exp
|
|
end
|
|
|
|
local number = parseNumber()
|
|
if number then
|
|
return number
|
|
end
|
|
|
|
if ChunkFinishMap[token] then
|
|
return nil
|
|
end
|
|
|
|
if token == 'nil' then
|
|
return parseNil()
|
|
end
|
|
|
|
if token == 'true'
|
|
or token == 'false' then
|
|
return parseBoolean()
|
|
end
|
|
|
|
if token == 'function' then
|
|
return parseFunction()
|
|
end
|
|
|
|
local node = parseName()
|
|
if node then
|
|
return parseSimple(resolveName(node), false)
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
local function parseUnaryOP()
|
|
local token = Tokens[Index + 1]
|
|
local symbol = UnarySymbol[token] and token or UnaryAlias[token]
|
|
if not symbol then
|
|
return nil
|
|
end
|
|
local myLevel = UnarySymbol[symbol]
|
|
local op = {
|
|
type = symbol,
|
|
start = getPosition(Tokens[Index], 'left'),
|
|
finish = getPosition(Tokens[Index] + #symbol - 1, 'right'),
|
|
}
|
|
Index = Index + 2
|
|
return op, myLevel
|
|
end
|
|
|
|
---@param level integer # op level must greater than this level
|
|
local function parseBinaryOP(asAction, level)
|
|
local token = Tokens[Index + 1]
|
|
local symbol = (BinarySymbol[token] and token)
|
|
or BinaryAlias[token]
|
|
or (not asAction and BinaryActionAlias[token])
|
|
if not symbol then
|
|
return nil
|
|
end
|
|
if symbol == '//' and State.options.nonstandardSymbol['//'] then
|
|
return nil
|
|
end
|
|
local myLevel = BinarySymbol[symbol]
|
|
if level and myLevel < level then
|
|
return nil
|
|
end
|
|
local op = {
|
|
type = symbol,
|
|
start = getPosition(Tokens[Index], 'left'),
|
|
finish = getPosition(Tokens[Index] + #token - 1, 'right'),
|
|
}
|
|
if not asAction then
|
|
if token == '=' then
|
|
pushError {
|
|
type = 'ERR_EQ_AS_ASSIGN',
|
|
start = op.start,
|
|
finish = op.finish,
|
|
fix = {
|
|
title = 'FIX_EQ_AS_ASSIGN',
|
|
{
|
|
start = op.start,
|
|
finish = op.finish,
|
|
text = '==',
|
|
}
|
|
}
|
|
}
|
|
end
|
|
end
|
|
if BinaryAlias[token] then
|
|
if not State.options.nonstandardSymbol[token] then
|
|
pushError {
|
|
type = 'ERR_NONSTANDARD_SYMBOL',
|
|
start = op.start,
|
|
finish = op.finish,
|
|
info = {
|
|
symbol = symbol,
|
|
},
|
|
fix = {
|
|
title = 'FIX_NONSTANDARD_SYMBOL',
|
|
symbol = symbol,
|
|
{
|
|
start = op.start,
|
|
finish = op.finish,
|
|
text = symbol,
|
|
},
|
|
}
|
|
}
|
|
end
|
|
end
|
|
if token == '//'
|
|
or token == '<<'
|
|
or token == '>>' then
|
|
if State.version ~= 'Lua 5.3'
|
|
and State.version ~= 'Lua 5.4' then
|
|
pushError {
|
|
type = 'UNSUPPORT_SYMBOL',
|
|
version = {'Lua 5.3', 'Lua 5.4'},
|
|
start = op.start,
|
|
finish = op.finish,
|
|
info = {
|
|
version = State.version,
|
|
}
|
|
}
|
|
end
|
|
end
|
|
Index = Index + 2
|
|
return op, myLevel
|
|
end
|
|
|
|
function parseExp(asAction, level)
|
|
local exp
|
|
local uop, uopLevel = parseUnaryOP()
|
|
if uop then
|
|
skipSpace()
|
|
local child = parseExp(asAction, uopLevel)
|
|
-- 预计算负数
|
|
if uop.type == '-'
|
|
and child
|
|
and (child.type == 'number' or child.type == 'integer') then
|
|
child.start = uop.start
|
|
child[1] = - child[1]
|
|
exp = child
|
|
else
|
|
exp = {
|
|
type = 'unary',
|
|
op = uop,
|
|
start = uop.start,
|
|
finish = child and child.finish or uop.finish,
|
|
[1] = child,
|
|
}
|
|
if child then
|
|
child.parent = exp
|
|
else
|
|
missExp()
|
|
end
|
|
end
|
|
else
|
|
exp = parseExpUnit()
|
|
if not exp then
|
|
return nil
|
|
end
|
|
end
|
|
|
|
while true do
|
|
skipSpace()
|
|
local bop, bopLevel = parseBinaryOP(asAction, level)
|
|
if not bop then
|
|
break
|
|
end
|
|
|
|
::AGAIN::
|
|
skipSpace()
|
|
local isForward = SymbolForward[bopLevel]
|
|
local child = parseExp(asAction, isForward and (bopLevel + 0.5) or bopLevel)
|
|
if not child then
|
|
if skipUnknownSymbol() then
|
|
goto AGAIN
|
|
else
|
|
missExp()
|
|
end
|
|
end
|
|
local bin = {
|
|
type = 'binary',
|
|
start = exp.start,
|
|
finish = child and child.finish or bop.finish,
|
|
op = bop,
|
|
[1] = exp,
|
|
[2] = child
|
|
}
|
|
exp.parent = bin
|
|
if child then
|
|
child.parent = bin
|
|
end
|
|
exp = bin
|
|
end
|
|
|
|
return exp
|
|
end
|
|
|
|
local function skipSeps()
|
|
while true do
|
|
skipSpace()
|
|
if Tokens[Index + 1] == ',' then
|
|
missExp()
|
|
Index = Index + 2
|
|
else
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
---@return parser.object? first
|
|
---@return parser.object? second
|
|
---@return parser.object[]? rest
|
|
local function parseSetValues()
|
|
skipSpace()
|
|
local first = parseExp()
|
|
if not first then
|
|
return nil
|
|
end
|
|
skipSpace()
|
|
if Tokens[Index + 1] ~= ',' then
|
|
return first
|
|
end
|
|
Index = Index + 2
|
|
skipSeps()
|
|
local second = parseExp()
|
|
if not second then
|
|
missExp()
|
|
return first
|
|
end
|
|
skipSpace()
|
|
if Tokens[Index + 1] ~= ',' then
|
|
return first, second
|
|
end
|
|
Index = Index + 2
|
|
skipSeps()
|
|
local third = parseExp()
|
|
if not third then
|
|
missExp()
|
|
return first, second
|
|
end
|
|
|
|
local rest = { third }
|
|
while true do
|
|
skipSpace()
|
|
if Tokens[Index + 1] ~= ',' then
|
|
return first, second, rest
|
|
end
|
|
Index = Index + 2
|
|
skipSeps()
|
|
local exp = parseExp()
|
|
if not exp then
|
|
missExp()
|
|
return first, second, rest
|
|
end
|
|
rest[#rest+1] = exp
|
|
end
|
|
end
|
|
|
|
local function pushActionIntoCurrentChunk(action)
|
|
local chunk = Chunk[#Chunk]
|
|
if chunk then
|
|
chunk[#chunk+1] = action
|
|
action.parent = chunk
|
|
end
|
|
end
|
|
|
|
---@return parser.object? second
|
|
---@return parser.object[]? rest
|
|
local function parseVarTails(parser, isLocal)
|
|
if Tokens[Index + 1] ~= ',' then
|
|
return nil
|
|
end
|
|
Index = Index + 2
|
|
skipSpace()
|
|
local second = parser(true)
|
|
if not second then
|
|
missName()
|
|
return nil
|
|
end
|
|
if isLocal then
|
|
createLocal(second, parseLocalAttrs())
|
|
end
|
|
skipSpace()
|
|
if Tokens[Index + 1] ~= ',' then
|
|
return second
|
|
end
|
|
Index = Index + 2
|
|
skipSeps()
|
|
local third = parser(true)
|
|
if not third then
|
|
missName()
|
|
return second
|
|
end
|
|
if isLocal then
|
|
createLocal(third, parseLocalAttrs())
|
|
end
|
|
local rest = { third }
|
|
while true do
|
|
skipSpace()
|
|
if Tokens[Index + 1] ~= ',' then
|
|
return second, rest
|
|
end
|
|
Index = Index + 2
|
|
skipSeps()
|
|
local name = parser(true)
|
|
if not name then
|
|
missName()
|
|
return second, rest
|
|
end
|
|
if isLocal then
|
|
createLocal(name, parseLocalAttrs())
|
|
end
|
|
rest[#rest+1] = name
|
|
end
|
|
end
|
|
|
|
local function bindValue(n, v, index, lastValue, isLocal, isSet)
|
|
if isLocal then
|
|
if v and v.special then
|
|
addSpecial(v.special, n)
|
|
end
|
|
elseif isSet then
|
|
n.type = GetToSetMap[n.type] or n.type
|
|
if n.type == 'setlocal' then
|
|
local loc = n.node
|
|
if loc.attrs then
|
|
pushError {
|
|
type = 'SET_CONST',
|
|
start = n.start,
|
|
finish = n.finish,
|
|
}
|
|
end
|
|
end
|
|
end
|
|
if not v and lastValue then
|
|
if lastValue.type == 'call'
|
|
or lastValue.type == 'varargs' then
|
|
v = lastValue
|
|
if not v.extParent then
|
|
v.extParent = {}
|
|
end
|
|
end
|
|
end
|
|
if v then
|
|
if v.type == 'call'
|
|
or v.type == 'varargs' then
|
|
local select = {
|
|
type = 'select',
|
|
sindex = index,
|
|
start = v.start,
|
|
finish = v.finish,
|
|
vararg = v
|
|
}
|
|
if v.parent then
|
|
v.extParent[#v.extParent+1] = select
|
|
else
|
|
v.parent = select
|
|
end
|
|
v = select
|
|
end
|
|
n.value = v
|
|
n.range = v.finish
|
|
v.parent = n
|
|
end
|
|
end
|
|
|
|
local function parseMultiVars(n1, parser, isLocal)
|
|
local n2, nrest = parseVarTails(parser, isLocal)
|
|
skipSpace()
|
|
local v1, v2, vrest
|
|
local isSet
|
|
local max = 1
|
|
if expectAssign(not isLocal) then
|
|
v1, v2, vrest = parseSetValues()
|
|
isSet = true
|
|
if not v1 then
|
|
missExp()
|
|
end
|
|
end
|
|
local index = 1
|
|
bindValue(n1, v1, index, nil, isLocal, isSet)
|
|
local lastValue = v1
|
|
local lastVar = n1
|
|
if n2 then
|
|
max = 2
|
|
if not v2 then
|
|
index = 2
|
|
end
|
|
bindValue(n2, v2, index, lastValue, isLocal, isSet)
|
|
lastValue = v2 or lastValue
|
|
lastVar = n2
|
|
pushActionIntoCurrentChunk(n2)
|
|
end
|
|
if nrest then
|
|
for i = 1, #nrest do
|
|
local n = nrest[i]
|
|
local v = vrest and vrest[i]
|
|
max = i + 2
|
|
if not v then
|
|
index = index + 1
|
|
end
|
|
bindValue(n, v, index, lastValue, isLocal, isSet)
|
|
lastValue = v or lastValue
|
|
lastVar = n
|
|
pushActionIntoCurrentChunk(n)
|
|
end
|
|
end
|
|
|
|
if isLocal then
|
|
local effect = lastValue and lastValue.finish or lastVar.finish
|
|
n1.effect = effect
|
|
if n2 then
|
|
n2.effect = effect
|
|
end
|
|
if nrest then
|
|
for i = 1, #nrest do
|
|
nrest[i].effect = effect
|
|
end
|
|
end
|
|
end
|
|
|
|
if v2 and not n2 then
|
|
v2.redundant = {
|
|
max = max,
|
|
passed = 2,
|
|
}
|
|
pushActionIntoCurrentChunk(v2)
|
|
end
|
|
if vrest then
|
|
for i = 1, #vrest do
|
|
local v = vrest[i]
|
|
if not nrest or not nrest[i] then
|
|
v.redundant = {
|
|
max = max,
|
|
passed = i + 2,
|
|
}
|
|
pushActionIntoCurrentChunk(v)
|
|
end
|
|
end
|
|
end
|
|
|
|
return n1, isSet
|
|
end
|
|
|
|
local function compileExpAsAction(exp)
|
|
pushActionIntoCurrentChunk(exp)
|
|
if GetToSetMap[exp.type] then
|
|
skipSpace()
|
|
local isLocal
|
|
if exp.type == 'getlocal' and exp[1] == State.ENVMode then
|
|
exp.special = nil
|
|
local loc = createLocal(exp, parseLocalAttrs())
|
|
loc.locPos = exp.start
|
|
loc.effect = maxinteger
|
|
isLocal = true
|
|
skipSpace()
|
|
end
|
|
local action, isSet = parseMultiVars(exp, parseExp, isLocal)
|
|
if isSet
|
|
or action.type == 'getmethod' then
|
|
return action
|
|
end
|
|
end
|
|
|
|
if exp.type == 'call' then
|
|
if exp.node.special == 'error' then
|
|
for i = #Chunk, 1, -1 do
|
|
local block = Chunk[i]
|
|
if block.type == 'ifblock'
|
|
or block.type == 'elseifblock'
|
|
or block.type == 'elseblock'
|
|
or block.type == 'function' then
|
|
block.hasError = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
return exp
|
|
end
|
|
|
|
if exp.type == 'binary' then
|
|
if GetToSetMap[exp[1].type] then
|
|
local op = exp.op
|
|
if op.type == '==' then
|
|
pushError {
|
|
type = 'ERR_ASSIGN_AS_EQ',
|
|
start = op.start,
|
|
finish = op.finish,
|
|
fix = {
|
|
title = 'FIX_ASSIGN_AS_EQ',
|
|
{
|
|
start = op.start,
|
|
finish = op.finish,
|
|
text = '=',
|
|
}
|
|
}
|
|
}
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
pushError {
|
|
type = 'EXP_IN_ACTION',
|
|
start = exp.start,
|
|
finish = exp.finish,
|
|
}
|
|
|
|
return exp
|
|
end
|
|
|
|
local function parseLocal()
|
|
local locPos = getPosition(Tokens[Index], 'left')
|
|
Index = Index + 2
|
|
skipSpace()
|
|
local word = peekWord()
|
|
if not word then
|
|
missName()
|
|
return nil
|
|
end
|
|
|
|
if word == 'function' then
|
|
local func = parseFunction(true, true)
|
|
local name = func.name
|
|
if name then
|
|
func.name = nil
|
|
name.value = func
|
|
name.vstart = func.start
|
|
name.range = func.finish
|
|
name.locPos = locPos
|
|
func.parent = name
|
|
pushActionIntoCurrentChunk(name)
|
|
return name
|
|
else
|
|
missName(func.keyword[2])
|
|
pushActionIntoCurrentChunk(func)
|
|
return func
|
|
end
|
|
end
|
|
|
|
local name = parseName(true)
|
|
if not name then
|
|
missName()
|
|
return nil
|
|
end
|
|
local loc = createLocal(name, parseLocalAttrs())
|
|
loc.locPos = locPos
|
|
loc.effect = maxinteger
|
|
pushActionIntoCurrentChunk(loc)
|
|
skipSpace()
|
|
parseMultiVars(loc, parseName, true)
|
|
|
|
return loc
|
|
end
|
|
|
|
local function parseDo()
|
|
local doLeft = getPosition(Tokens[Index], 'left')
|
|
local doRight = getPosition(Tokens[Index] + 1, 'right')
|
|
local obj = {
|
|
type = 'do',
|
|
start = doLeft,
|
|
finish = doRight,
|
|
keyword = {
|
|
[1] = doLeft,
|
|
[2] = doRight,
|
|
},
|
|
}
|
|
Index = Index + 2
|
|
pushActionIntoCurrentChunk(obj)
|
|
pushChunk(obj)
|
|
parseActions()
|
|
popChunk()
|
|
if Tokens[Index + 1] == 'end' then
|
|
obj.finish = getPosition(Tokens[Index] + 2, 'right')
|
|
obj.keyword[3] = getPosition(Tokens[Index], 'left')
|
|
obj.keyword[4] = getPosition(Tokens[Index] + 2, 'right')
|
|
Index = Index + 2
|
|
else
|
|
missEnd(doLeft, doRight)
|
|
end
|
|
if obj.locals then
|
|
LocalCount = LocalCount - #obj.locals
|
|
end
|
|
|
|
return obj
|
|
end
|
|
|
|
local function parseReturn()
|
|
local returnLeft = getPosition(Tokens[Index], 'left')
|
|
local returnRight = getPosition(Tokens[Index] + 5, 'right')
|
|
Index = Index + 2
|
|
skipSpace()
|
|
local rtn = parseExpList(true)
|
|
if rtn then
|
|
rtn.type = 'return'
|
|
rtn.start = returnLeft
|
|
else
|
|
rtn = {
|
|
type = 'return',
|
|
start = returnLeft,
|
|
finish = returnRight,
|
|
}
|
|
end
|
|
pushActionIntoCurrentChunk(rtn)
|
|
for i = #Chunk, 1, -1 do
|
|
local block = Chunk[i]
|
|
if block.type == 'function'
|
|
or block.type == 'main' then
|
|
if not block.returns then
|
|
block.returns = {}
|
|
end
|
|
block.returns[#block.returns+1] = rtn
|
|
break
|
|
end
|
|
end
|
|
for i = #Chunk, 1, -1 do
|
|
local block = Chunk[i]
|
|
if block.type == 'ifblock'
|
|
or block.type == 'elseifblock'
|
|
or block.type == 'elseblock'
|
|
or block.type == 'function' then
|
|
block.hasReturn = true
|
|
break
|
|
end
|
|
end
|
|
|
|
return rtn
|
|
end
|
|
|
|
local function parseLabel()
|
|
local left = getPosition(Tokens[Index], 'left')
|
|
Index = Index + 2
|
|
skipSpace()
|
|
local label = parseName()
|
|
skipSpace()
|
|
|
|
if not label then
|
|
missName()
|
|
end
|
|
|
|
if Tokens[Index + 1] == '::' then
|
|
Index = Index + 2
|
|
else
|
|
if label then
|
|
missSymbol '::'
|
|
end
|
|
end
|
|
|
|
if not label then
|
|
return nil
|
|
end
|
|
|
|
label.type = 'label'
|
|
pushActionIntoCurrentChunk(label)
|
|
|
|
local block = guide.getBlock(label)
|
|
if block then
|
|
if not block.labels then
|
|
block.labels = {}
|
|
end
|
|
local name = label[1]
|
|
local olabel = guide.getLabel(block, name)
|
|
if olabel then
|
|
if State.version == 'Lua 5.4'
|
|
or block == guide.getBlock(olabel) then
|
|
pushError {
|
|
type = 'REDEFINED_LABEL',
|
|
start = label.start,
|
|
finish = label.finish,
|
|
relative = {
|
|
{
|
|
olabel.start,
|
|
olabel.finish,
|
|
}
|
|
}
|
|
}
|
|
end
|
|
end
|
|
block.labels[name] = label
|
|
end
|
|
|
|
if State.version == 'Lua 5.1' then
|
|
pushError {
|
|
type = 'UNSUPPORT_SYMBOL',
|
|
start = left,
|
|
finish = lastRightPosition(),
|
|
version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'},
|
|
info = {
|
|
version = State.version,
|
|
}
|
|
}
|
|
return
|
|
end
|
|
return label
|
|
end
|
|
|
|
local function parseGoTo()
|
|
local start = getPosition(Tokens[Index], 'left')
|
|
Index = Index + 2
|
|
skipSpace()
|
|
|
|
local action = parseName()
|
|
if not action then
|
|
missName()
|
|
return nil
|
|
end
|
|
|
|
action.type = 'goto'
|
|
action.keyStart = start
|
|
|
|
for i = #Chunk, 1, -1 do
|
|
local chunk = Chunk[i]
|
|
if chunk.type == 'function'
|
|
or chunk.type == 'main' then
|
|
if not chunk.gotos then
|
|
chunk.gotos = {}
|
|
end
|
|
chunk.gotos[#chunk.gotos+1] = action
|
|
break
|
|
end
|
|
end
|
|
for i = #Chunk, 1, -1 do
|
|
local chunk = Chunk[i]
|
|
if chunk.type == 'ifblock'
|
|
or chunk.type == 'elseifblock'
|
|
or chunk.type == 'elseblock' then
|
|
chunk.hasGoTo = true
|
|
break
|
|
end
|
|
end
|
|
|
|
pushActionIntoCurrentChunk(action)
|
|
return action
|
|
end
|
|
|
|
local function parseIfBlock(parent)
|
|
local ifLeft = getPosition(Tokens[Index], 'left')
|
|
local ifRight = getPosition(Tokens[Index] + 1, 'right')
|
|
Index = Index + 2
|
|
local ifblock = {
|
|
type = 'ifblock',
|
|
parent = parent,
|
|
start = ifLeft,
|
|
finish = ifRight,
|
|
keyword = {
|
|
[1] = ifLeft,
|
|
[2] = ifRight,
|
|
}
|
|
}
|
|
skipSpace()
|
|
local filter = parseExp()
|
|
if filter then
|
|
ifblock.filter = filter
|
|
ifblock.finish = filter.finish
|
|
filter.parent = ifblock
|
|
else
|
|
missExp()
|
|
end
|
|
skipSpace()
|
|
local thenToken = Tokens[Index + 1]
|
|
if thenToken == 'then'
|
|
or thenToken == 'do' then
|
|
ifblock.finish = getPosition(Tokens[Index] + #thenToken - 1, 'right')
|
|
ifblock.keyword[3] = getPosition(Tokens[Index], 'left')
|
|
ifblock.keyword[4] = ifblock.finish
|
|
if thenToken == 'do' then
|
|
pushError {
|
|
type = 'ERR_THEN_AS_DO',
|
|
start = ifblock.keyword[3],
|
|
finish = ifblock.keyword[4],
|
|
fix = {
|
|
title = 'FIX_THEN_AS_DO',
|
|
{
|
|
start = ifblock.keyword[3],
|
|
finish = ifblock.keyword[4],
|
|
text = 'then',
|
|
}
|
|
}
|
|
}
|
|
end
|
|
Index = Index + 2
|
|
else
|
|
missSymbol 'then'
|
|
end
|
|
pushChunk(ifblock)
|
|
parseActions()
|
|
popChunk()
|
|
ifblock.finish = getPosition(Tokens[Index], 'left')
|
|
if ifblock.locals then
|
|
LocalCount = LocalCount - #ifblock.locals
|
|
end
|
|
return ifblock
|
|
end
|
|
|
|
local function parseElseIfBlock(parent)
|
|
local ifLeft = getPosition(Tokens[Index], 'left')
|
|
local ifRight = getPosition(Tokens[Index] + 5, 'right')
|
|
local elseifblock = {
|
|
type = 'elseifblock',
|
|
parent = parent,
|
|
start = ifLeft,
|
|
finish = ifRight,
|
|
keyword = {
|
|
[1] = ifLeft,
|
|
[2] = ifRight,
|
|
}
|
|
}
|
|
Index = Index + 2
|
|
skipSpace()
|
|
local filter = parseExp()
|
|
if filter then
|
|
elseifblock.filter = filter
|
|
elseifblock.finish = filter.finish
|
|
filter.parent = elseifblock
|
|
else
|
|
missExp()
|
|
end
|
|
skipSpace()
|
|
local thenToken = Tokens[Index + 1]
|
|
if thenToken == 'then'
|
|
or thenToken == 'do' then
|
|
elseifblock.finish = getPosition(Tokens[Index] + #thenToken - 1, 'right')
|
|
elseifblock.keyword[3] = getPosition(Tokens[Index], 'left')
|
|
elseifblock.keyword[4] = elseifblock.finish
|
|
if thenToken == 'do' then
|
|
pushError {
|
|
type = 'ERR_THEN_AS_DO',
|
|
start = elseifblock.keyword[3],
|
|
finish = elseifblock.keyword[4],
|
|
fix = {
|
|
title = 'FIX_THEN_AS_DO',
|
|
{
|
|
start = elseifblock.keyword[3],
|
|
finish = elseifblock.keyword[4],
|
|
text = 'then',
|
|
}
|
|
}
|
|
}
|
|
end
|
|
Index = Index + 2
|
|
else
|
|
missSymbol 'then'
|
|
end
|
|
pushChunk(elseifblock)
|
|
parseActions()
|
|
popChunk()
|
|
elseifblock.finish = getPosition(Tokens[Index], 'left')
|
|
if elseifblock.locals then
|
|
LocalCount = LocalCount - #elseifblock.locals
|
|
end
|
|
return elseifblock
|
|
end
|
|
|
|
local function parseElseBlock(parent)
|
|
local ifLeft = getPosition(Tokens[Index], 'left')
|
|
local ifRight = getPosition(Tokens[Index] + 3, 'right')
|
|
local elseblock = {
|
|
type = 'elseblock',
|
|
parent = parent,
|
|
start = ifLeft,
|
|
finish = ifRight,
|
|
keyword = {
|
|
[1] = ifLeft,
|
|
[2] = ifRight,
|
|
}
|
|
}
|
|
Index = Index + 2
|
|
skipSpace()
|
|
pushChunk(elseblock)
|
|
parseActions()
|
|
popChunk()
|
|
elseblock.finish = getPosition(Tokens[Index], 'left')
|
|
if elseblock.locals then
|
|
LocalCount = LocalCount - #elseblock.locals
|
|
end
|
|
return elseblock
|
|
end
|
|
|
|
local function parseIf()
|
|
local token = Tokens[Index + 1]
|
|
local left = getPosition(Tokens[Index], 'left')
|
|
local action = {
|
|
type = 'if',
|
|
start = left,
|
|
finish = getPosition(Tokens[Index] + #token - 1, 'right'),
|
|
}
|
|
pushActionIntoCurrentChunk(action)
|
|
if token ~= 'if' then
|
|
missSymbol('if', left, left)
|
|
end
|
|
local hasElse
|
|
while true do
|
|
local word = Tokens[Index + 1]
|
|
local child
|
|
if word == 'if' then
|
|
child = parseIfBlock(action)
|
|
elseif word == 'elseif' then
|
|
child = parseElseIfBlock(action)
|
|
elseif word == 'else' then
|
|
child = parseElseBlock(action)
|
|
end
|
|
if not child then
|
|
break
|
|
end
|
|
if hasElse then
|
|
pushError {
|
|
type = 'BLOCK_AFTER_ELSE',
|
|
start = child.start,
|
|
finish = child.finish,
|
|
}
|
|
end
|
|
if word == 'else' then
|
|
hasElse = true
|
|
end
|
|
action[#action+1] = child
|
|
action.finish = child.finish
|
|
skipSpace()
|
|
end
|
|
|
|
if Tokens[Index + 1] == 'end' then
|
|
action.finish = getPosition(Tokens[Index] + 2, 'right')
|
|
Index = Index + 2
|
|
else
|
|
missEnd(action[1].keyword[1], action[1].keyword[2])
|
|
end
|
|
|
|
return action
|
|
end
|
|
|
|
local function parseFor()
|
|
local action = {
|
|
type = 'for',
|
|
start = getPosition(Tokens[Index], 'left'),
|
|
finish = getPosition(Tokens[Index] + 2, 'right'),
|
|
keyword = {},
|
|
}
|
|
action.keyword[1] = action.start
|
|
action.keyword[2] = action.finish
|
|
Index = Index + 2
|
|
pushActionIntoCurrentChunk(action)
|
|
pushChunk(action)
|
|
skipSpace()
|
|
local nameOrList = parseNameOrList(action)
|
|
if not nameOrList then
|
|
missName()
|
|
end
|
|
skipSpace()
|
|
-- for i =
|
|
if expectAssign() then
|
|
action.type = 'loop'
|
|
|
|
skipSpace()
|
|
local expList = parseExpList()
|
|
local name
|
|
if nameOrList then
|
|
if nameOrList.type == 'name' then
|
|
name = nameOrList
|
|
else
|
|
name = nameOrList[1]
|
|
end
|
|
end
|
|
if name then
|
|
local loc = createLocal(name)
|
|
loc.parent = action
|
|
action.finish = name.finish
|
|
action.loc = loc
|
|
end
|
|
if expList then
|
|
expList.parent = action
|
|
local value = expList[1]
|
|
if value then
|
|
value.parent = expList
|
|
action.init = value
|
|
action.finish = expList[#expList].finish
|
|
end
|
|
local max = expList[2]
|
|
if max then
|
|
max.parent = expList
|
|
action.max = max
|
|
action.finish = max.finish
|
|
else
|
|
pushError {
|
|
type = 'MISS_LOOP_MAX',
|
|
start = lastRightPosition(),
|
|
finish = lastRightPosition(),
|
|
}
|
|
end
|
|
local step = expList[3]
|
|
if step then
|
|
step.parent = expList
|
|
action.step = step
|
|
action.finish = step.finish
|
|
end
|
|
else
|
|
pushError {
|
|
type = 'MISS_LOOP_MIN',
|
|
start = lastRightPosition(),
|
|
finish = lastRightPosition(),
|
|
}
|
|
end
|
|
|
|
if action.loc then
|
|
action.loc.effect = action.finish
|
|
end
|
|
elseif Tokens[Index + 1] == 'in' then
|
|
action.type = 'in'
|
|
local inLeft = getPosition(Tokens[Index], 'left')
|
|
local inRight = getPosition(Tokens[Index] + 1, 'right')
|
|
Index = Index + 2
|
|
skipSpace()
|
|
|
|
local exps = parseExpList()
|
|
|
|
action.finish = inRight
|
|
action.keyword[3] = inLeft
|
|
action.keyword[4] = inRight
|
|
|
|
local list
|
|
if nameOrList and nameOrList.type == 'name' then
|
|
list = {
|
|
type = 'list',
|
|
start = nameOrList.start,
|
|
finish = nameOrList.finish,
|
|
parent = action,
|
|
[1] = nameOrList,
|
|
}
|
|
else
|
|
list = nameOrList
|
|
end
|
|
|
|
if exps then
|
|
local lastExp = exps[#exps]
|
|
if lastExp then
|
|
action.finish = lastExp.finish
|
|
end
|
|
|
|
action.exps = exps
|
|
exps.parent = action
|
|
for i = 1, #exps do
|
|
local exp = exps[i]
|
|
exp.parent = exps
|
|
end
|
|
else
|
|
missExp()
|
|
end
|
|
|
|
if list then
|
|
local lastName = list[#list]
|
|
list.range = lastName and lastName.range or inRight
|
|
action.keys = list
|
|
for i = 1, #list do
|
|
local loc = createLocal(list[i])
|
|
loc.parent = action
|
|
loc.effect = action.finish
|
|
end
|
|
end
|
|
else
|
|
missSymbol 'in'
|
|
end
|
|
|
|
skipSpace()
|
|
local doToken = Tokens[Index + 1]
|
|
if doToken == 'do'
|
|
or doToken == 'then' then
|
|
local left = getPosition(Tokens[Index], 'left')
|
|
local right = getPosition(Tokens[Index] + #doToken - 1, 'right')
|
|
action.finish = left
|
|
action.keyword[#action.keyword+1] = left
|
|
action.keyword[#action.keyword+1] = right
|
|
if doToken == 'then' then
|
|
pushError {
|
|
type = 'ERR_DO_AS_THEN',
|
|
start = left,
|
|
finish = right,
|
|
fix = {
|
|
title = 'FIX_DO_AS_THEN',
|
|
{
|
|
start = left,
|
|
finish = right,
|
|
text = 'do',
|
|
}
|
|
}
|
|
}
|
|
end
|
|
Index = Index + 2
|
|
else
|
|
missSymbol 'do'
|
|
end
|
|
|
|
skipSpace()
|
|
parseActions()
|
|
popChunk()
|
|
|
|
skipSpace()
|
|
if Tokens[Index + 1] == 'end' then
|
|
action.finish = getPosition(Tokens[Index] + 2, 'right')
|
|
action.keyword[#action.keyword+1] = getPosition(Tokens[Index], 'left')
|
|
action.keyword[#action.keyword+1] = action.finish
|
|
Index = Index + 2
|
|
else
|
|
missEnd(action.keyword[1], action.keyword[2])
|
|
end
|
|
|
|
if action.locals then
|
|
LocalCount = LocalCount - #action.locals
|
|
end
|
|
|
|
return action
|
|
end
|
|
|
|
local function parseWhile()
|
|
local action = {
|
|
type = 'while',
|
|
start = getPosition(Tokens[Index], 'left'),
|
|
finish = getPosition(Tokens[Index] + 4, 'right'),
|
|
keyword = {},
|
|
}
|
|
action.keyword[1] = action.start
|
|
action.keyword[2] = action.finish
|
|
Index = Index + 2
|
|
|
|
skipSpace()
|
|
local nextToken = Tokens[Index + 1]
|
|
local filter = nextToken ~= 'do'
|
|
and nextToken ~= 'then'
|
|
and parseExp()
|
|
if filter then
|
|
action.filter = filter
|
|
action.finish = filter.finish
|
|
filter.parent = action
|
|
else
|
|
missExp()
|
|
end
|
|
|
|
skipSpace()
|
|
local doToken = Tokens[Index + 1]
|
|
if doToken == 'do'
|
|
or doToken == 'then' then
|
|
local left = getPosition(Tokens[Index], 'left')
|
|
local right = getPosition(Tokens[Index] + #doToken - 1, 'right')
|
|
action.finish = left
|
|
action.keyword[#action.keyword+1] = left
|
|
action.keyword[#action.keyword+1] = right
|
|
if doToken == 'then' then
|
|
pushError {
|
|
type = 'ERR_DO_AS_THEN',
|
|
start = left,
|
|
finish = right,
|
|
fix = {
|
|
title = 'FIX_DO_AS_THEN',
|
|
{
|
|
start = left,
|
|
finish = right,
|
|
text = 'do',
|
|
}
|
|
}
|
|
}
|
|
end
|
|
Index = Index + 2
|
|
else
|
|
missSymbol 'do'
|
|
end
|
|
|
|
pushActionIntoCurrentChunk(action)
|
|
pushChunk(action)
|
|
skipSpace()
|
|
parseActions()
|
|
popChunk()
|
|
|
|
skipSpace()
|
|
if Tokens[Index + 1] == 'end' then
|
|
action.finish = getPosition(Tokens[Index] + 2, 'right')
|
|
action.keyword[#action.keyword+1] = getPosition(Tokens[Index], 'left')
|
|
action.keyword[#action.keyword+1] = action.finish
|
|
Index = Index + 2
|
|
else
|
|
missEnd(action.keyword[1], action.keyword[2])
|
|
end
|
|
|
|
if action.locals then
|
|
LocalCount = LocalCount - #action.locals
|
|
end
|
|
|
|
return action
|
|
end
|
|
|
|
local function parseRepeat()
|
|
local action = {
|
|
type = 'repeat',
|
|
start = getPosition(Tokens[Index], 'left'),
|
|
finish = getPosition(Tokens[Index] + 5, 'right'),
|
|
keyword = {},
|
|
}
|
|
action.keyword[1] = action.start
|
|
action.keyword[2] = action.finish
|
|
Index = Index + 2
|
|
|
|
pushActionIntoCurrentChunk(action)
|
|
pushChunk(action)
|
|
skipSpace()
|
|
parseActions()
|
|
|
|
skipSpace()
|
|
if Tokens[Index + 1] == 'until' then
|
|
action.finish = getPosition(Tokens[Index] + 4, 'right')
|
|
action.keyword[#action.keyword+1] = getPosition(Tokens[Index], 'left')
|
|
action.keyword[#action.keyword+1] = action.finish
|
|
Index = Index + 2
|
|
|
|
skipSpace()
|
|
local filter = parseExp()
|
|
if filter then
|
|
action.filter = filter
|
|
filter.parent = action
|
|
else
|
|
missExp()
|
|
end
|
|
|
|
else
|
|
missSymbol 'until'
|
|
end
|
|
|
|
popChunk()
|
|
if action.filter then
|
|
action.finish = action.filter.finish
|
|
end
|
|
|
|
if action.locals then
|
|
LocalCount = LocalCount - #action.locals
|
|
end
|
|
|
|
return action
|
|
end
|
|
|
|
local function parseBreak()
|
|
local returnLeft = getPosition(Tokens[Index], 'left')
|
|
local returnRight = getPosition(Tokens[Index] + #Tokens[Index + 1] - 1, 'right')
|
|
Index = Index + 2
|
|
skipSpace()
|
|
local action = {
|
|
type = 'break',
|
|
start = returnLeft,
|
|
finish = returnRight,
|
|
}
|
|
|
|
local ok
|
|
for i = #Chunk, 1, -1 do
|
|
local chunk = Chunk[i]
|
|
if chunk.type == 'function' then
|
|
break
|
|
end
|
|
if chunk.type == 'while'
|
|
or chunk.type == 'in'
|
|
or chunk.type == 'loop'
|
|
or chunk.type == 'repeat'
|
|
or chunk.type == 'for' then
|
|
if not chunk.breaks then
|
|
chunk.breaks = {}
|
|
end
|
|
chunk.breaks[#chunk.breaks+1] = action
|
|
ok = true
|
|
break
|
|
end
|
|
end
|
|
for i = #Chunk, 1, -1 do
|
|
local chunk = Chunk[i]
|
|
if chunk.type == 'ifblock'
|
|
or chunk.type == 'elseifblock'
|
|
or chunk.type == 'elseblock' then
|
|
chunk.hasBreak = true
|
|
break
|
|
end
|
|
end
|
|
if not ok and Mode == 'Lua' then
|
|
pushError {
|
|
type = 'BREAK_OUTSIDE',
|
|
start = action.start,
|
|
finish = action.finish,
|
|
}
|
|
end
|
|
|
|
pushActionIntoCurrentChunk(action)
|
|
return action
|
|
end
|
|
|
|
function parseAction()
|
|
local token = Tokens[Index + 1]
|
|
|
|
if token == '::' then
|
|
return parseLabel()
|
|
end
|
|
|
|
if token == 'local' then
|
|
return parseLocal()
|
|
end
|
|
|
|
if token == 'if'
|
|
or token == 'elseif'
|
|
or token == 'else' then
|
|
return parseIf()
|
|
end
|
|
|
|
if token == 'for' then
|
|
return parseFor()
|
|
end
|
|
|
|
if token == 'do' then
|
|
return parseDo()
|
|
end
|
|
|
|
if token == 'return' then
|
|
return parseReturn()
|
|
end
|
|
|
|
if token == 'break' then
|
|
return parseBreak()
|
|
end
|
|
|
|
if token == 'continue' and State.options.nonstandardSymbol['continue'] then
|
|
return parseBreak()
|
|
end
|
|
|
|
if token == 'while' then
|
|
return parseWhile()
|
|
end
|
|
|
|
if token == 'repeat' then
|
|
return parseRepeat()
|
|
end
|
|
|
|
if token == 'goto' and isKeyWord 'goto' then
|
|
return parseGoTo()
|
|
end
|
|
|
|
if token == 'function' then
|
|
local exp = parseFunction(false, true)
|
|
local name = exp.name
|
|
if name then
|
|
exp.name = nil
|
|
name.type = GetToSetMap[name.type]
|
|
name.value = exp
|
|
name.vstart = exp.start
|
|
name.range = exp.finish
|
|
exp.parent = name
|
|
if name.type == 'setlocal' then
|
|
local loc = name.node
|
|
if loc.attrs then
|
|
pushError {
|
|
type = 'SET_CONST',
|
|
start = name.start,
|
|
finish = name.finish,
|
|
}
|
|
end
|
|
end
|
|
pushActionIntoCurrentChunk(name)
|
|
return name
|
|
else
|
|
pushActionIntoCurrentChunk(exp)
|
|
missName(exp.keyword[2])
|
|
return exp
|
|
end
|
|
end
|
|
|
|
local exp = parseExp(true)
|
|
if exp then
|
|
local action = compileExpAsAction(exp)
|
|
if action then
|
|
return action
|
|
end
|
|
end
|
|
return nil, true
|
|
end
|
|
|
|
local function skipFirstComment()
|
|
if Tokens[Index + 1] ~= '#' then
|
|
return
|
|
end
|
|
while true do
|
|
Index = Index + 2
|
|
local token = Tokens[Index + 1]
|
|
if not token then
|
|
break
|
|
end
|
|
if NLMap[token] then
|
|
skipNL()
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
local function parseLua()
|
|
local main = {
|
|
type = 'main',
|
|
start = 0,
|
|
finish = 0,
|
|
}
|
|
pushChunk(main)
|
|
createLocal{
|
|
type = 'local',
|
|
start = -1,
|
|
finish = -1,
|
|
effect = -1,
|
|
parent = main,
|
|
tag = '_ENV',
|
|
special= '_G',
|
|
[1] = State.ENVMode,
|
|
}
|
|
LocalCount = 0
|
|
skipFirstComment()
|
|
while true do
|
|
parseActions()
|
|
if Index <= #Tokens then
|
|
unknownSymbol()
|
|
Index = Index + 2
|
|
else
|
|
break
|
|
end
|
|
end
|
|
popChunk()
|
|
main.finish = getPosition(#Lua, 'right')
|
|
|
|
return main
|
|
end
|
|
|
|
local function initState(lua, version, options)
|
|
Lua = lua
|
|
Line = 0
|
|
LineOffset = 1
|
|
LastTokenFinish = 0
|
|
LocalCount = 0
|
|
Chunk = {}
|
|
Tokens = tokens(lua)
|
|
Index = 1
|
|
---@class parser.state
|
|
---@field uri uri
|
|
---@field lines integer[]
|
|
local state = {
|
|
version = version,
|
|
lua = lua,
|
|
ast = {},
|
|
errs = {},
|
|
comms = {},
|
|
lines = {
|
|
[0] = 1,
|
|
size = #lua,
|
|
},
|
|
options = options or {},
|
|
}
|
|
if not state.options.nonstandardSymbol then
|
|
state.options.nonstandardSymbol = {}
|
|
end
|
|
State = state
|
|
if version == 'Lua 5.1' or version == 'LuaJIT' then
|
|
state.ENVMode = '@fenv'
|
|
else
|
|
state.ENVMode = '_ENV'
|
|
end
|
|
|
|
pushError = 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 'Error'
|
|
errs[#errs+1] = err
|
|
return err
|
|
end
|
|
end
|
|
|
|
return function (lua, mode, version, options)
|
|
Mode = mode
|
|
initState(lua, version, options)
|
|
skipSpace()
|
|
if mode == 'Lua' then
|
|
State.ast = parseLua()
|
|
elseif mode == 'Nil' then
|
|
State.ast = parseNil()
|
|
elseif mode == 'Boolean' then
|
|
State.ast = parseBoolean()
|
|
elseif mode == 'String' then
|
|
State.ast = parseString()
|
|
elseif mode == 'Number' then
|
|
State.ast = parseNumber()
|
|
elseif mode == 'Name' then
|
|
State.ast = parseName()
|
|
elseif mode == 'Exp' then
|
|
State.ast = parseExp()
|
|
elseif mode == 'Action' then
|
|
State.ast = parseAction()
|
|
end
|
|
|
|
if State.ast then
|
|
State.ast.state = State
|
|
end
|
|
|
|
while true do
|
|
if Index <= #Tokens then
|
|
unknownSymbol()
|
|
Index = Index + 2
|
|
else
|
|
break
|
|
end
|
|
end
|
|
|
|
return State
|
|
end
|