nixos/lua-lsp/script/parser/compile.lua

3916 lines
104 KiB
Lua
Raw Normal View History

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