summaryrefslogtreecommitdiff
path: root/script/parser
diff options
context:
space:
mode:
Diffstat (limited to 'script/parser')
-rw-r--r--script/parser/ast.lua1997
-rw-r--r--script/parser/calcline.lua94
-rw-r--r--script/parser/compile.lua4249
-rw-r--r--script/parser/grammar.lua573
-rw-r--r--script/parser/init.lua5
-rw-r--r--script/parser/luadoc.lua4
-rw-r--r--script/parser/newparser.lua3868
-rw-r--r--script/parser/parse.lua63
-rw-r--r--script/parser/split.lua9
9 files changed, 3759 insertions, 7103 deletions
diff --git a/script/parser/ast.lua b/script/parser/ast.lua
deleted file mode 100644
index 648a6890..00000000
--- a/script/parser/ast.lua
+++ /dev/null
@@ -1,1997 +0,0 @@
-local tonumber = tonumber
-local stringChar = string.char
-local utf8Char = utf8.char
-local tableUnpack = table.unpack
-local mathType = math.type
-local tableRemove = table.remove
-local tableSort = table.sort
-local print = print
-local tostring = tostring
-
-_ENV = nil
-
-local DefaultState = {
- lua = '',
- options = {},
-}
-
-local State = DefaultState
-local PushError
-local PushDiag
-local PushComment
-
--- goto 单独处理
-local RESERVED = {
- ['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 VersionOp = {
- ['&'] = {'Lua 5.3', 'Lua 5.4'},
- ['~'] = {'Lua 5.3', 'Lua 5.4'},
- ['|'] = {'Lua 5.3', 'Lua 5.4'},
- ['<<'] = {'Lua 5.3', 'Lua 5.4'},
- ['>>'] = {'Lua 5.3', 'Lua 5.4'},
- ['//'] = {'Lua 5.3', 'Lua 5.4'},
-}
-
-local SymbolAlias = {
- ['||'] = 'or',
- ['&&'] = 'and',
- ['!='] = '~=',
- ['!'] = 'not',
-}
-
-local function checkOpVersion(op)
- local versions = VersionOp[op.type]
- if not versions then
- return
- end
- for i = 1, #versions do
- if versions[i] == State.version then
- return
- end
- end
- PushError {
- type = 'UNSUPPORT_SYMBOL',
- start = op.start,
- finish = op.finish,
- version = versions,
- info = {
- version = State.version,
- }
- }
-end
-
-local function checkMissEnd(start)
- if not State.MissEndErr then
- return
- end
- local err = State.MissEndErr
- State.MissEndErr = nil
- local _, finish = State.lua:find('[%w_]+', start)
- if not finish then
- return
- end
- err.info.related = {
- {
- start = start,
- finish = finish,
- }
- }
- PushError {
- type = 'MISS_END',
- start = start,
- finish = finish,
- }
-end
-
-local function getSelect(vararg, index)
- return {
- type = 'select',
- start = vararg.start,
- finish = vararg.finish,
- vararg = vararg,
- sindex = index,
- }
-end
-
-local function getValue(values, i)
- if not values then
- return nil, nil
- end
- local value = values[i]
- if not value then
- local last = values[#values]
- if not last then
- return nil, nil
- end
- if last.type == 'call' or last.type == 'varargs' then
- return getSelect(last, i - #values + 1)
- end
- return nil, nil
- end
- if value.type == 'call' or value.type == 'varargs' then
- value = getSelect(value, 1)
- end
- return value
-end
-
-local function createLocal(key, effect, value, attrs)
- if not key then
- return nil
- end
- key.type = 'local'
- key.effect = effect
- key.value = value
- key.attrs = attrs
- if value then
- key.range = value.finish
- end
- return key
-end
-
-local function createCall(args, start, finish)
- if args then
- args.type = 'callargs'
- args.start = start
- args.finish = finish
- end
- return {
- type = 'call',
- start = start,
- finish = finish,
- args = args,
- }
-end
-
-local function packList(start, list, finish)
- local lastFinish = start
- local wantName = true
- local count = 0
- for i = 1, #list do
- local ast = list[i]
- if ast.type == ',' then
- if wantName or i == #list then
- PushError {
- type = 'UNEXPECT_SYMBOL',
- start = ast.start,
- finish = ast.finish,
- info = {
- symbol = ',',
- }
- }
- end
- wantName = true
- else
- if not wantName then
- PushError {
- type = 'MISS_SYMBOL',
- start = lastFinish,
- finish = ast.start - 1,
- info = {
- symbol = ',',
- }
- }
- end
- wantName = false
- count = count + 1
- list[count] = list[i]
- end
- lastFinish = ast.finish + 1
- end
- for i = count + 1, #list do
- list[i] = nil
- end
- list.type = 'list'
- list.start = start
- list.finish = finish - 1
- return list
-end
-
-local BinaryLevel = {
- ['or'] = 1,
- ['and'] = 2,
- ['<='] = 3,
- ['>='] = 3,
- ['<'] = 3,
- ['>'] = 3,
- ['~='] = 3,
- ['=='] = 3,
- ['|'] = 4,
- ['~'] = 5,
- ['&'] = 6,
- ['<<'] = 7,
- ['>>'] = 7,
- ['..'] = 8,
- ['+'] = 9,
- ['-'] = 9,
- ['*'] = 10,
- ['//'] = 10,
- ['/'] = 10,
- ['%'] = 10,
- ['^'] = 11,
-}
-
-local BinaryForward = {
- [01] = true,
- [02] = true,
- [03] = true,
- [04] = true,
- [05] = true,
- [06] = true,
- [07] = true,
- [08] = false,
- [09] = true,
- [10] = true,
- [11] = false,
-}
-
-local Defs = {
- Nil = function (pos)
- return {
- type = 'nil',
- start = pos,
- finish = pos + 2,
- }
- end,
- True = function (pos)
- return {
- type = 'boolean',
- start = pos,
- finish = pos + 3,
- [1] = true,
- }
- end,
- False = function (pos)
- return {
- type = 'boolean',
- start = pos,
- finish = pos + 4,
- [1] = false,
- }
- end,
- ShortComment = function (start, text, finish)
- PushComment {
- type = 'comment.short',
- start = start,
- finish = finish - 1,
- text = text,
- }
- end,
- LongComment = function (start, beforeEq, afterEq, str, close, finish)
- PushComment {
- type = 'comment.long',
- start = start,
- finish = finish - 1,
- text = str,
- }
- if not close then
- local endSymbol = ']' .. ('='):rep(afterEq-beforeEq) .. ']'
- local s, _, w = str:find('(%][%=]*%])[%c%s]*$')
- if s then
- PushError {
- type = 'ERR_LCOMMENT_END',
- start = finish - #str + s - 1,
- finish = finish - #str + s + #w - 2,
- info = {
- symbol = endSymbol,
- },
- fix = {
- title = 'FIX_LCOMMENT_END',
- {
- start = finish - #str + s - 1,
- finish = finish - #str + s + #w - 2,
- text = endSymbol,
- }
- },
- }
- end
- PushError {
- type = 'MISS_SYMBOL',
- start = finish,
- finish = finish,
- info = {
- symbol = endSymbol,
- },
- fix = {
- title = 'ADD_LCOMMENT_END',
- {
- start = finish,
- finish = finish,
- text = endSymbol,
- }
- },
- }
- end
- end,
- CLongComment = function (start1, finish1, str, start2, finish2)
- if State.options.nonstandardSymbol and State.options.nonstandardSymbol['/**/'] then
- else
- PushError {
- type = 'ERR_C_LONG_COMMENT',
- start = start1,
- finish = finish2 - 1,
- fix = {
- title = 'FIX_C_LONG_COMMENT',
- {
- start = start1,
- finish = finish1 - 1,
- text = '--[[',
- },
- {
- start = start2,
- finish = finish2 - 1,
- text = '--]]'
- },
- }
- }
- end
- PushComment {
- type = 'comment.clong',
- start = start1,
- finish = finish2 - 1,
- text = str,
- }
- end,
- CCommentPrefix = function (start, finish, commentFinish)
- if State.options.nonstandardSymbol and State.options.nonstandardSymbol['//'] then
- else
- PushError {
- type = 'ERR_COMMENT_PREFIX',
- start = start,
- finish = finish - 1,
- fix = {
- title = 'FIX_COMMENT_PREFIX',
- {
- start = start,
- finish = finish - 1,
- text = '--',
- },
- }
- }
- end
- PushComment {
- type = 'comment.cshort',
- start = start,
- finish = commentFinish - 1,
- text = '',
- }
- end,
- String = function (start, quote, str, finish)
- if quote == '`' then
- if State.options.nonstandardSymbol and State.options.nonstandardSymbol['`'] then
- else
- PushError {
- type = 'ERR_NONSTANDARD_SYMBOL',
- start = start,
- finish = finish - 1,
- info = {
- symbol = '"',
- },
- fix = {
- title = 'FIX_NONSTANDARD_SYMBOL',
- symbol = '"',
- {
- start = start,
- finish = start,
- text = '"',
- },
- {
- start = finish - 1,
- finish = finish - 1,
- text = '"',
- },
- }
- }
- end
- end
- return {
- type = 'string',
- start = start,
- finish = finish - 1,
- [1] = str,
- [2] = quote,
- }
- end,
- LongString = function (beforeEq, afterEq, str, missPos)
- if missPos then
- local endSymbol = ']' .. ('='):rep(afterEq-beforeEq) .. ']'
- local s, _, w = str:find('(%][%=]*%])[%c%s]*$')
- if s then
- PushError {
- type = 'ERR_LSTRING_END',
- start = missPos - #str + s - 1,
- finish = missPos - #str + s + #w - 2,
- info = {
- symbol = endSymbol,
- },
- fix = {
- title = 'FIX_LSTRING_END',
- {
- start = missPos - #str + s - 1,
- finish = missPos - #str + s + #w - 2,
- text = endSymbol,
- }
- },
- }
- end
- PushError {
- type = 'MISS_SYMBOL',
- start = missPos,
- finish = missPos,
- info = {
- symbol = endSymbol,
- },
- fix = {
- title = 'ADD_LSTRING_END',
- {
- start = missPos,
- finish = missPos,
- text = endSymbol,
- }
- },
- }
- end
- return '[' .. ('='):rep(afterEq-beforeEq) .. '[', str
- end,
- Char10 = function (char)
- char = tonumber(char)
- if not char or char < 0 or char > 255 then
- return ''
- end
- return stringChar(char)
- end,
- Char16 = function (pos, char)
- if State.version == 'Lua 5.1' then
- PushError {
- type = 'ERR_ESC',
- start = pos-1,
- finish = pos,
- version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'},
- info = {
- version = State.version,
- }
- }
- return char
- end
- return stringChar(tonumber(char, 16))
- end,
- CharUtf8 = function (pos, char)
- if State.version ~= 'Lua 5.3'
- and State.version ~= 'Lua 5.4'
- and State.version ~= 'LuaJIT'
- then
- PushError {
- type = 'ERR_ESC',
- start = pos-3,
- finish = pos-2,
- version = {'Lua 5.3', 'Lua 5.4', 'LuaJIT'},
- info = {
- version = State.version,
- }
- }
- return char
- end
- if #char == 0 then
- PushError {
- type = 'UTF8_SMALL',
- start = pos-3,
- finish = pos,
- }
- return ''
- end
- local v = tonumber(char, 16)
- if not v then
- for i = 1, #char do
- if not tonumber(char:sub(i, i), 16) then
- PushError {
- type = 'MUST_X16',
- start = pos + i - 1,
- finish = pos + i - 1,
- }
- end
- end
- return ''
- end
- if State.version == 'Lua 5.4' then
- if v < 0 or v > 0x7FFFFFFF then
- PushError {
- type = 'UTF8_MAX',
- start = pos-3,
- finish = pos+#char,
- info = {
- min = '00000000',
- max = '7FFFFFFF',
- }
- }
- end
- else
- if v < 0 or v > 0x10FFFF then
- PushError {
- type = 'UTF8_MAX',
- start = pos-3,
- finish = pos+#char,
- version = v <= 0x7FFFFFFF and 'Lua 5.4' or nil,
- info = {
- min = '000000',
- max = '10FFFF',
- }
- }
- end
- end
- if v >= 0 and v <= 0x10FFFF then
- return utf8Char(v)
- end
- return ''
- end,
- Number = function (start, number, finish)
- local n = tonumber(number)
- if n then
- State.LastNumber = {
- type = mathType(n) == 'integer' and 'integer' or 'number',
- start = start,
- finish = finish - 1,
- [1] = n,
- }
- State.LastRaw = number
- return State.LastNumber
- else
- PushError {
- type = 'MALFORMED_NUMBER',
- start = start,
- finish = finish - 1,
- }
- State.LastNumber = {
- type = 'number',
- start = start,
- finish = finish - 1,
- [1] = 0,
- }
- State.LastRaw = number
- return State.LastNumber
- end
- end,
- FFINumber = function (start, symbol)
- local lastNumber = State.LastNumber
- if State.LastRaw:find('.', 1, true) then
- PushError {
- type = 'UNKNOWN_SYMBOL',
- start = start,
- finish = start + #symbol - 1,
- info = {
- symbol = symbol,
- }
- }
- lastNumber[1] = 0
- return
- end
- if State.version ~= 'LuaJIT' then
- PushError {
- type = 'UNSUPPORT_SYMBOL',
- start = start,
- finish = start + #symbol - 1,
- version = 'LuaJIT',
- info = {
- version = State.version,
- }
- }
- lastNumber[1] = 0
- end
- end,
- ImaginaryNumber = function (start, symbol)
- local lastNumber = State.LastNumber
- if State.version ~= 'LuaJIT' then
- PushError {
- type = 'UNSUPPORT_SYMBOL',
- start = start,
- finish = start + #symbol - 1,
- version = 'LuaJIT',
- info = {
- version = State.version,
- }
- }
- end
- lastNumber[1] = 0
- end,
- Integer2 = function (start, word)
- if State.version ~= 'LuaJIT' then
- PushError {
- type = 'UNSUPPORT_SYMBOL',
- start = start,
- finish = start + 1,
- version = 'LuaJIT',
- info = {
- version = State.version,
- }
- }
- end
- local num = 0
- for i = 1, #word do
- if word:sub(i, i) == '1' then
- num = num | (1 << (i - 1))
- end
- end
- return tostring(num)
- end,
- Name = function (start, str, finish)
- local isKeyWord
- if RESERVED[str] then
- isKeyWord = true
- elseif str == 'goto' then
- if State.version ~= 'Lua 5.1' and State.version ~= 'LuaJIT' then
- isKeyWord = true
- end
- end
- if isKeyWord then
- PushError {
- type = 'KEYWORD',
- start = start,
- finish = finish - 1,
- }
- end
- if not State.options.unicodeName and str:find '[\x80-\xff]' then
- PushError {
- type = 'UNICODE_NAME',
- start = start,
- finish = finish - 1,
- }
- end
- return {
- type = 'name',
- start = start,
- finish = finish - 1,
- [1] = str,
- }
- end,
- GetField = function (dot, field)
- local obj = {
- type = 'getfield',
- field = field,
- dot = dot,
- start = dot.start,
- finish = (field or dot).finish,
- }
- if field then
- field.type = 'field'
- field.parent = obj
- end
- return obj
- end,
- GetIndex = function (start, index, finish)
- local obj = {
- type = 'getindex',
- bstart = start,
- start = start,
- finish = finish - 1,
- index = index,
- }
- if index then
- index.parent = obj
- end
- return obj
- end,
- GetMethod = function (colon, method)
- local obj = {
- type = 'getmethod',
- method = method,
- colon = colon,
- start = colon.start,
- finish = (method or colon).finish,
- }
- if method then
- method.type = 'method'
- method.parent = obj
- end
- return obj
- end,
- Single = function (unit)
- unit.type = 'getname'
- return unit
- end,
- Simple = function (units)
- local last = units[1]
- for i = 2, #units do
- local current = units[i]
- current.node = last
- current.start = last.start
- last.next = current
- last = units[i]
- end
- return last
- end,
- SimpleCall = function (call)
- if call.type ~= 'call' and call.type ~= 'getmethod' then
- PushError {
- type = 'EXP_IN_ACTION',
- start = call.start,
- finish = call.finish,
- }
- end
- return call
- end,
- BinaryOp = function (start, op)
- if SymbolAlias[op] then
- if State.options.nonstandardSymbol and State.options.nonstandardSymbol[op] then
- else
- PushError {
- type = 'ERR_NONSTANDARD_SYMBOL',
- start = start,
- finish = start + #op - 1,
- info = {
- symbol = SymbolAlias[op],
- },
- fix = {
- title = 'FIX_NONSTANDARD_SYMBOL',
- symbol = SymbolAlias[op],
- {
- start = start,
- finish = start + #op - 1,
- text = SymbolAlias[op],
- },
- }
- }
- end
- op = SymbolAlias[op]
- end
- return {
- type = op,
- start = start,
- finish = start + #op - 1,
- }
- end,
- UnaryOp = function (start, op)
- if SymbolAlias[op] then
- if State.options.nonstandardSymbol and State.options.nonstandardSymbol[op] then
- else
- PushError {
- type = 'ERR_NONSTANDARD_SYMBOL',
- start = start,
- finish = start + #op - 1,
- info = {
- symbol = SymbolAlias[op],
- },
- fix = {
- title = 'FIX_NONSTANDARD_SYMBOL',
- symbol = SymbolAlias[op],
- {
- start = start,
- finish = start + #op - 1,
- text = SymbolAlias[op],
- },
- }
- }
- end
- op = SymbolAlias[op]
- end
- return {
- type = op,
- start = start,
- finish = start + #op - 1,
- }
- end,
- Unary = function (first, ...)
- if not ... then
- return nil
- end
- local list = {first, ...}
- local e = list[#list]
- for i = #list - 1, 1, -1 do
- local op = list[i]
- checkOpVersion(op)
- e = {
- type = 'unary',
- op = op,
- start = op.start,
- finish = e.finish,
- [1] = e,
- }
- end
- return e
- end,
- SubBinary = function (op, symb)
- if symb then
- return op, symb
- end
- PushError {
- type = 'MISS_EXP',
- start = op.start,
- finish = op.finish,
- }
- end,
- Binary = function (first, op, second, ...)
- if not first then
- return second
- end
- if not op then
- return first
- end
- if not ... then
- checkOpVersion(op)
- return {
- type = 'binary',
- op = op,
- start = first.start,
- finish = second.finish,
- [1] = first,
- [2] = second,
- }
- end
- local list = {first, op, second, ...}
- local ops = {}
- for i = 2, #list, 2 do
- ops[#ops+1] = i
- end
- tableSort(ops, function (a, b)
- local op1 = list[a]
- local op2 = list[b]
- local lv1 = BinaryLevel[op1.type]
- local lv2 = BinaryLevel[op2.type]
- if lv1 == lv2 then
- local forward = BinaryForward[lv1]
- if forward then
- return op1.start > op2.start
- else
- return op1.start < op2.start
- end
- else
- return lv1 < lv2
- end
- end)
- local final
- for i = #ops, 1, -1 do
- local n = ops[i]
- local op = list[n]
- local left = list[n-1]
- local right = list[n+1]
- local exp = {
- type = 'binary',
- op = op,
- start = left.start,
- finish = right and right.finish or op.finish,
- [1] = left,
- [2] = right,
- }
- local leftIndex, rightIndex
- if list[left] then
- leftIndex = list[left[1]]
- else
- leftIndex = n - 1
- end
- if list[right] then
- rightIndex = list[right[2]]
- else
- rightIndex = n + 1
- end
-
- list[leftIndex] = exp
- list[rightIndex] = exp
- list[left] = leftIndex
- list[right] = rightIndex
- list[exp] = n
- final = exp
-
- checkOpVersion(op)
- end
- return final
- end,
- Paren = function (start, exp, finish)
- if exp and exp.type == 'paren' then
- exp.start = start
- exp.finish = finish - 1
- return exp
- end
- return {
- type = 'paren',
- start = start,
- finish = finish - 1,
- exp = exp
- }
- end,
- VarArgs = function (dots)
- dots.type = 'varargs'
- return dots
- end,
- PackLoopArgs = function (start, list, finish)
- local list = packList(start, list, finish)
- if #list == 0 then
- PushError {
- type = 'MISS_LOOP_MIN',
- start = finish,
- finish = finish,
- }
- elseif #list == 1 then
- PushError {
- type = 'MISS_LOOP_MAX',
- start = finish,
- finish = finish,
- }
- end
- return list
- end,
- PackInNameList = function (start, list, finish)
- local list = packList(start, list, finish)
- if #list == 0 then
- PushError {
- type = 'MISS_NAME',
- start = start,
- finish = finish,
- }
- end
- return list
- end,
- PackInExpList = function (start, list, finish)
- local list = packList(start, list, finish)
- if #list == 0 then
- PushError {
- type = 'MISS_EXP',
- start = start,
- finish = finish,
- }
- end
- return list
- end,
- PackExpList = function (start, list, finish)
- local list = packList(start, list, finish)
- return list
- end,
- PackNameList = function (start, list, finish)
- local list = packList(start, list, finish)
- return list
- end,
- Call = function (start, args, finish)
- return createCall(args, start, finish-1)
- end,
- COMMA = function (start)
- return {
- type = ',',
- start = start,
- finish = start,
- }
- end,
- SEMICOLON = function (start)
- return {
- type = ';',
- start = start,
- finish = start,
- }
- end,
- DOTS = function (start)
- return {
- type = '...',
- start = start,
- finish = start + 2,
- }
- end,
- COLON = function (start)
- return {
- type = ':',
- start = start,
- finish = start,
- }
- end,
- ASSIGN = function (start, symbol)
- if State.options.nonstandardSymbol and State.options.nonstandardSymbol[symbol] then
- else
- PushError {
- type = 'UNSUPPORT_SYMBOL',
- start = start,
- finish = start + #symbol - 1,
- info = {
- version = 'Lua',
- }
- }
- end
- end,
- DOT = function (start)
- return {
- type = '.',
- start = start,
- finish = start,
- }
- end,
- Function = function (functionStart, functionFinish, name, args, actions, endStart, endFinish)
- actions.type = 'function'
- actions.start = functionStart
- actions.finish = endFinish - 1
- actions.args = args
- actions.keyword= {
- functionStart, functionFinish - 1,
- endStart, endFinish - 1,
- }
- checkMissEnd(functionStart)
- if not name then
- return actions
- end
- if name.type == 'getname' then
- name.type = 'setname'
- name.value = actions
- elseif name.type == 'getfield' then
- name.type = 'setfield'
- name.value = actions
- elseif name.type == 'getmethod' then
- name.type = 'setmethod'
- name.value = actions
- elseif name.type == 'getindex' then
- name.type = 'setfield'
- name.value = actions
- PushError {
- type = 'INDEX_IN_FUNC_NAME',
- start = name.bstart,
- finish = name.finish,
- }
- end
- name.range = actions.finish
- name.vstart = functionStart
- return name
- end,
- LocalFunction = function (start, name)
- if name.type == 'function' then
- PushError {
- type = 'MISS_NAME',
- start = name.keyword[2] + 1,
- finish = name.keyword[2] + 1,
- }
- return name
- end
- if name.type ~= 'setname' then
- PushError {
- type = 'UNEXPECT_LFUNC_NAME',
- start = name.start,
- finish = name.finish,
- }
- return name
- end
-
- local loc = createLocal(name, name.start, name.value)
- loc.localfunction = true
- loc.vstart = name.value.start
- return name
- end,
- NamedFunction = function (name)
- if name.type == 'function' then
- PushError {
- type = 'MISS_NAME',
- start = name.keyword[2] + 1,
- finish = name.keyword[2] + 1,
- }
- end
- return name
- end,
- ExpFunction = function (func)
- if func.type ~= 'function' then
- PushError {
- type = 'UNEXPECT_EFUNC_NAME',
- start = func.start,
- finish = func.finish,
- }
- return func.value
- end
- return func
- end,
- Table = function (start, tbl, finish)
- tbl.type = 'table'
- tbl.start = start
- tbl.finish = finish - 1
- local wantField = true
- local lastStart = start + 1
- local fieldCount = 0
- local n = 0
- for i = 1, #tbl do
- local field = tbl[i]
- if field.type == ',' or field.type == ';' then
- if wantField then
- PushError {
- type = 'MISS_EXP',
- start = lastStart,
- finish = field.start - 1,
- }
- end
- wantField = true
- lastStart = field.finish + 1
- else
- if not wantField then
- PushError {
- type = 'MISS_SEP_IN_TABLE',
- start = lastStart,
- finish = field.start - 1,
- }
- end
- wantField = false
- lastStart = field.finish + 1
- fieldCount = fieldCount + 1
- tbl[fieldCount] = field
- if field.type == 'tableexp' then
- n = n + 1
- field.tindex = n
- end
- end
- end
- for i = fieldCount + 1, #tbl do
- tbl[i] = nil
- end
- return tbl
- end,
- NewField = function (start, field, value, finish)
- local obj = {
- type = 'tablefield',
- start = start,
- finish = finish-1,
- field = field,
- value = value,
- }
- if field then
- field.type = 'field'
- field.parent = obj
- end
- return obj
- end,
- NewIndex = function (start, index, value, finish)
- local obj = {
- type = 'tableindex',
- start = start,
- finish = finish-1,
- index = index,
- value = value,
- }
- if index then
- index.parent = obj
- end
- return obj
- end,
- TableExp = function (start, value, finish)
- if not value then
- return
- end
- local obj = {
- type = 'tableexp',
- start = start,
- finish = finish-1,
- value = value,
- }
- return obj
- end,
- FuncArgs = function (start, args, finish)
- args.type = 'funcargs'
- args.start = start
- args.finish = finish - 1
- local lastStart = start + 1
- local wantName = true
- local argCount = 0
- for i = 1, #args do
- local arg = args[i]
- local argAst = arg
- if argAst.type == ',' then
- if wantName then
- PushError {
- type = 'MISS_NAME',
- start = lastStart,
- finish = argAst.start-1,
- }
- end
- wantName = true
- else
- if not wantName then
- PushError {
- type = 'MISS_SYMBOL',
- start = lastStart-1,
- finish = argAst.start-1,
- info = {
- symbol = ',',
- }
- }
- end
- wantName = false
- argCount = argCount + 1
-
- if argAst.type == '...' then
- args[argCount] = arg
- if i < #args then
- local a = args[i+1]
- local b = args[#args]
- PushError {
- type = 'ARGS_AFTER_DOTS',
- start = a.start,
- finish = b.finish,
- }
- end
- break
- else
- args[argCount] = createLocal(arg, arg.start)
- end
- end
- lastStart = argAst.finish + 1
- end
- for i = argCount + 1, #args do
- args[i] = nil
- end
- if wantName and argCount > 0 then
- PushError {
- type = 'MISS_NAME',
- start = lastStart,
- finish = finish - 1,
- }
- end
- return args
- end,
- Set = function (start, keys, eqFinish, values, finish)
- for i = 1, #keys do
- local key = keys[i]
- if key.type == 'getname' then
- key.type = 'setname'
- key.value = getValue(values, i)
- elseif key.type == 'getfield' then
- key.type = 'setfield'
- key.value = getValue(values, i)
- elseif key.type == 'getindex' then
- key.type = 'setindex'
- key.value = getValue(values, i)
- else
- PushError {
- type = 'UNEXPECT_SYMBOL',
- start = eqFinish - 1,
- finish = eqFinish - 1,
- info = {
- symbol = '=',
- }
- }
- end
- if key.value then
- key.range = key.value.finish
- end
- end
- if values then
- for i = #keys+1, #values do
- local value = values[i]
- PushDiag('redundant-value', {
- start = value.start,
- finish = value.finish,
- max = #keys,
- passed = #values,
- })
- end
- end
- return tableUnpack(keys)
- end,
- LocalAttr = function (attrs)
- if #attrs == 0 then
- return nil
- end
- for i = 1, #attrs do
- local attr = attrs[i]
- local attrAst = attr
- attrAst.type = 'localattr'
- if State.version ~= 'Lua 5.4' then
- PushError {
- type = 'UNSUPPORT_SYMBOL',
- start = attrAst.start,
- finish = attrAst.finish,
- version = 'Lua 5.4',
- info = {
- version = State.version,
- }
- }
- elseif attrAst[1] ~= 'const' and attrAst[1] ~= 'close' then
- PushError {
- type = 'UNKNOWN_TAG',
- start = attrAst.start,
- finish = attrAst.finish,
- info = {
- tag = attrAst[1],
- }
- }
- elseif i > 1 then
- PushError {
- type = 'MULTI_TAG',
- start = attrAst.start,
- finish = attrAst.finish,
- info = {
- tag = attrAst[1],
- }
- }
- end
- end
- attrs.start = attrs[1].start
- attrs.finish = attrs[#attrs].finish
- return attrs
- end,
- LocalName = function (name, attrs)
- if not name then
- return
- end
- name.attrs = attrs
- return name
- end,
- Local = function (start, keys, values, finish)
- for i = 1, #keys do
- local key = keys[i]
- local attrs = key.attrs
- key.attrs = nil
- local value = getValue(values, i)
- createLocal(key, finish, value, attrs)
- end
- if values then
- for i = #keys+1, #values do
- local value = values[i]
- PushDiag('redundant-value', {
- start = value.start,
- finish = value.finish,
- max = #keys,
- passed = #values,
- })
- end
- end
- return tableUnpack(keys)
- end,
- Do = function (start, actions, endA, endB)
- actions.type = 'do'
- actions.start = start
- actions.finish = endB - 1
- actions.keyword= {
- start, start + #'do' - 1,
- endA , endB - 1,
- }
- checkMissEnd(start)
- return actions
- end,
- Break = function (start, finish)
- return {
- type = 'break',
- start = start,
- finish = finish - 1,
- }
- end,
- Return = function (start, exps, finish)
- exps.type = 'return'
- exps.start = start
- exps.finish = finish - 1
- return exps
- end,
- Label = function (start, name, finish)
- if State.version == 'Lua 5.1' then
- PushError {
- type = 'UNSUPPORT_SYMBOL',
- start = start,
- finish = finish - 1,
- version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'},
- info = {
- version = State.version,
- }
- }
- return
- end
- if not name then
- return
- end
- name.type = 'label'
- return name
- end,
- GoTo = function (start, name, finish)
- if State.version == 'Lua 5.1' then
- PushError {
- type = 'UNSUPPORT_SYMBOL',
- start = start,
- finish = finish - 1,
- version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'},
- info = {
- version = State.version,
- }
- }
- return
- end
- if not name then
- return
- end
- name.type = 'goto'
- return name
- end,
- IfBlock = function (ifStart, ifFinish, exp, thenStart, thenFinish, actions, finish)
- actions.type = 'ifblock'
- actions.start = ifStart
- actions.finish = finish - 1
- actions.filter = exp
- actions.keyword= {
- ifStart, ifFinish - 1,
- thenStart, thenFinish - 1,
- }
- return actions
- end,
- ElseIfBlock = function (elseifStart, elseifFinish, exp, thenStart, thenFinish, actions, finish)
- actions.type = 'elseifblock'
- actions.start = elseifStart
- actions.finish = finish - 1
- actions.filter = exp
- actions.keyword= {
- elseifStart, elseifFinish - 1,
- thenStart, thenFinish - 1,
- }
- return actions
- end,
- ElseBlock = function (elseStart, elseFinish, actions, finish)
- actions.type = 'elseblock'
- actions.start = elseStart
- actions.finish = finish - 1
- actions.keyword= {
- elseStart, elseFinish - 1,
- }
- return actions
- end,
- If = function (start, blocks, endStart, endFinish)
- blocks.type = 'if'
- blocks.start = start
- blocks.finish = endFinish - 1
- local hasElse
- for i = 1, #blocks do
- local block = blocks[i]
- if i == 1 and block.type ~= 'ifblock' then
- PushError {
- type = 'MISS_SYMBOL',
- start = block.start,
- finish = block.start,
- info = {
- symbol = 'if',
- }
- }
- end
- if hasElse then
- PushError {
- type = 'BLOCK_AFTER_ELSE',
- start = block.start,
- finish = block.finish,
- }
- end
- if block.type == 'elseblock' then
- hasElse = true
- end
- end
- checkMissEnd(start)
- return blocks
- end,
- Loop = function (forA, forB, arg, steps, doA, doB, blockStart, block, endA, endB)
- local loc = createLocal(arg, blockStart, steps[1])
- block.type = 'loop'
- block.start = forA
- block.finish = endB - 1
- block.loc = loc
- block.max = steps[2]
- block.step = steps[3]
- block.keyword= {
- forA, forB - 1,
- doA , doB - 1,
- endA, endB - 1,
- }
- checkMissEnd(forA)
- return block
- end,
- In = function (forA, forB, keys, inA, inB, exp, doA, doB, blockStart, block, endA, endB)
- local func = tableRemove(exp, 1)
- block.type = 'in'
- block.start = forA
- block.finish = endB - 1
- block.keys = keys
- block.keyword= {
- forA, forB - 1,
- inA , inB - 1,
- doA , doB - 1,
- endA, endB - 1,
- }
-
- local values
- if func then
- local call = createCall(exp, func.finish + 1, exp.finish)
- if #exp == 0 then
- exp[1] = getSelect(func, 2)
- exp[2] = getSelect(func, 3)
- exp[3] = getSelect(func, 4)
- end
- call.node = func
- call.start = inA
- call.finish = doB - 1
- func.next = call
- func.iterator = true
- values = { call }
- keys.range = call.finish
- end
- for i = 1, #keys do
- local loc = keys[i]
- if values then
- createLocal(loc, blockStart, getValue(values, i))
- else
- createLocal(loc, blockStart)
- end
- end
- checkMissEnd(forA)
- return block
- end,
- While = function (whileA, whileB, filter, doA, doB, block, endA, endB)
- block.type = 'while'
- block.start = whileA
- block.finish = endB - 1
- block.filter = filter
- block.keyword= {
- whileA, whileB - 1,
- doA , doB - 1,
- endA , endB - 1,
- }
- checkMissEnd(whileA)
- return block
- end,
- Repeat = function (repeatA, repeatB, block, untilA, untilB, filter, finish)
- block.type = 'repeat'
- block.start = repeatA
- block.finish = finish
- block.filter = filter
- block.keyword= {
- repeatA, repeatB - 1,
- untilA , untilB - 1,
- }
- return block
- end,
- RTContinue = function (_, pos, ...)
- if State.options.nonstandardSymbol and State.options.nonstandardSymbol['continue'] then
- return pos, ...
- else
- return false
- end
- end,
- Continue = function (start, finish)
- return {
- type = 'nonstandardSymbol.continue',
- start = start,
- finish = finish - 1,
- }
- end,
- Lua = function (start, actions, finish)
- actions.type = 'main'
- actions.start = start
- actions.finish = finish - 1
- return actions
- end,
-
- -- 捕获错误
- UnknownSymbol = function (start, symbol)
- PushError {
- type = 'UNKNOWN_SYMBOL',
- start = start,
- finish = start + #symbol - 1,
- info = {
- symbol = symbol,
- }
- }
- end,
- UnknownAction = function (start, symbol)
- PushError {
- type = 'UNKNOWN_SYMBOL',
- start = start,
- finish = start + #symbol - 1,
- info = {
- symbol = symbol,
- }
- }
- end,
- DirtyName = function (pos)
- PushError {
- type = 'MISS_NAME',
- start = pos,
- finish = pos,
- }
- return nil
- end,
- DirtyExp = function (pos)
- PushError {
- type = 'MISS_EXP',
- start = pos,
- finish = pos,
- }
- return nil
- end,
- MissExp = function (pos)
- PushError {
- type = 'MISS_EXP',
- start = pos,
- finish = pos,
- }
- end,
- MissExponent = function (start, finish)
- PushError {
- type = 'MISS_EXPONENT',
- start = start,
- finish = finish - 1,
- }
- end,
- MissQuote1 = function (pos)
- PushError {
- type = 'MISS_SYMBOL',
- start = pos,
- finish = pos,
- info = {
- symbol = '"'
- }
- }
- end,
- MissQuote2 = function (pos)
- PushError {
- type = 'MISS_SYMBOL',
- start = pos,
- finish = pos,
- info = {
- symbol = "'"
- }
- }
- end,
- MissQuote3 = function (pos)
- PushError {
- type = 'MISS_SYMBOL',
- start = pos,
- finish = pos,
- info = {
- symbol = "`"
- }
- }
- end,
- MissEscX = function (pos)
- PushError {
- type = 'MISS_ESC_X',
- start = pos-2,
- finish = pos+1,
- }
- end,
- MissTL = function (pos)
- PushError {
- type = 'MISS_SYMBOL',
- start = pos,
- finish = pos,
- info = {
- symbol = '{',
- }
- }
- end,
- MissTR = function (pos)
- PushError {
- type = 'MISS_SYMBOL',
- start = pos,
- finish = pos,
- info = {
- symbol = '}',
- }
- }
- end,
- MissBR = function (pos)
- PushError {
- type = 'MISS_SYMBOL',
- start = pos,
- finish = pos,
- info = {
- symbol = ']',
- }
- }
- end,
- MissPL = function (pos)
- PushError {
- type = 'MISS_SYMBOL',
- start = pos,
- finish = pos,
- info = {
- symbol = '(',
- }
- }
- end,
- MissPR = function (pos)
- PushError {
- type = 'MISS_SYMBOL',
- start = pos,
- finish = pos,
- info = {
- symbol = ')',
- }
- }
- end,
- ErrEsc = function (pos)
- PushError {
- type = 'ERR_ESC',
- start = pos-1,
- finish = pos,
- }
- end,
- MustX16 = function (pos, str)
- PushError {
- type = 'MUST_X16',
- start = pos,
- finish = pos + #str - 1,
- }
- end,
- MissAssign = function (pos)
- PushError {
- type = 'MISS_SYMBOL',
- start = pos,
- finish = pos,
- info = {
- symbol = '=',
- }
- }
- end,
- MissTableSep = function (pos)
- PushError {
- type = 'MISS_SYMBOL',
- start = pos,
- finish = pos,
- info = {
- symbol = ','
- }
- }
- end,
- MissField = function (pos)
- PushError {
- type = 'MISS_FIELD',
- start = pos,
- finish = pos,
- }
- end,
- MissMethod = function (pos)
- PushError {
- type = 'MISS_METHOD',
- start = pos,
- finish = pos,
- }
- end,
- MissLabel = function (pos)
- PushError {
- type = 'MISS_SYMBOL',
- start = pos,
- finish = pos,
- info = {
- symbol = '::',
- }
- }
- end,
- MissEnd = function (pos)
- State.MissEndErr = PushError {
- type = 'MISS_SYMBOL',
- start = pos,
- finish = pos,
- info = {
- symbol = 'end',
- }
- }
- return pos, pos
- end,
- MissDo = function (pos)
- PushError {
- type = 'MISS_SYMBOL',
- start = pos,
- finish = pos,
- info = {
- symbol = 'do',
- }
- }
- return pos, pos
- end,
- MissComma = function (pos)
- PushError {
- type = 'MISS_SYMBOL',
- start = pos,
- finish = pos,
- info = {
- symbol = ',',
- }
- }
- end,
- MissIn = function (pos)
- PushError {
- type = 'MISS_SYMBOL',
- start = pos,
- finish = pos,
- info = {
- symbol = 'in',
- }
- }
- return pos, pos
- end,
- MissUntil = function (pos)
- PushError {
- type = 'MISS_SYMBOL',
- start = pos,
- finish = pos,
- info = {
- symbol = 'until',
- }
- }
- return pos, pos
- end,
- MissThen = function (pos)
- PushError {
- type = 'MISS_SYMBOL',
- start = pos,
- finish = pos,
- info = {
- symbol = 'then',
- }
- }
- return pos, pos
- end,
- MissName = function (pos)
- PushError {
- type = 'MISS_NAME',
- start = pos,
- finish = pos,
- }
- end,
- ExpInAction = function (start, exp, finish)
- PushError {
- type = 'EXP_IN_ACTION',
- start = start,
- finish = finish - 1,
- }
- -- 当exp为nil时,不能返回任何值,否则会产生带洞的actionlist
- if exp then
- return exp
- else
- return
- end
- end,
- MissIf = function (start, block)
- PushError {
- type = 'MISS_SYMBOL',
- start = start,
- finish = start,
- info = {
- symbol = 'if',
- }
- }
- return block
- end,
- MissGT = function (start)
- PushError {
- type = 'MISS_SYMBOL',
- start = start,
- finish = start,
- info = {
- symbol = '>'
- }
- }
- end,
- ErrAssign = function (start, finish)
- PushError {
- type = 'ERR_ASSIGN_AS_EQ',
- start = start,
- finish = finish - 1,
- fix = {
- title = 'FIX_ASSIGN_AS_EQ',
- {
- start = start,
- finish = finish - 1,
- text = '=',
- }
- }
- }
- end,
- ErrEQ = function (start, finish)
- PushError {
- type = 'ERR_EQ_AS_ASSIGN',
- start = start,
- finish = finish - 1,
- fix = {
- title = 'FIX_EQ_AS_ASSIGN',
- {
- start = start,
- finish = finish - 1,
- text = '==',
- }
- }
- }
- return '=='
- end,
- ErrUEQ = function (start, finish)
- PushError {
- type = 'ERR_UEQ',
- start = start,
- finish = finish - 1,
- fix = {
- title = 'FIX_UEQ',
- {
- start = start,
- finish = finish - 1,
- text = '~=',
- }
- }
- }
- return '=='
- end,
- ErrThen = function (start, finish)
- PushError {
- type = 'ERR_THEN_AS_DO',
- start = start,
- finish = finish - 1,
- fix = {
- title = 'FIX_THEN_AS_DO',
- {
- start = start,
- finish = finish - 1,
- text = 'then',
- }
- }
- }
- return start, finish
- end,
- ErrDo = function (start, finish)
- PushError {
- type = 'ERR_DO_AS_THEN',
- start = start,
- finish = finish - 1,
- fix = {
- title = 'FIX_DO_AS_THEN',
- {
- start = start,
- finish = finish - 1,
- text = 'do',
- }
- }
- }
- return start, finish
- end,
- MissSpaceBetween = function (start)
- PushError {
- type = 'MISS_SPACE_BETWEEN',
- start = start,
- finish = start + 1,
- fix = {
- title = 'FIX_INSERT_SPACE',
- {
- start = start + 1,
- finish = start,
- text = ' ',
- }
- }
- }
- end,
- CallArgSnip = function (name, tailStart, tailSymbol)
- PushError {
- type = 'UNEXPECT_SYMBOL',
- start = tailStart,
- finish = tailStart,
- info = {
- symbol = tailSymbol,
- }
- }
- return name
- end
-}
-
-local function init(state)
- State = state
- PushError = state.pushError
- PushDiag = state.pushDiag
- PushComment = state.pushComment
-end
-
-local function close()
- State = DefaultState
- PushError = function (...) end
- PushDiag = function (...) end
- PushComment = function (...) end
-end
-
-return {
- defs = Defs,
- init = init,
- close = close,
-}
diff --git a/script/parser/calcline.lua b/script/parser/calcline.lua
deleted file mode 100644
index 2e944167..00000000
--- a/script/parser/calcline.lua
+++ /dev/null
@@ -1,94 +0,0 @@
-local m = require 'lpeglabel'
-local util = require 'utility'
-
-local row
-local fl
-local NL = (m.P'\r\n' + m.S'\r\n') * m.Cp() / function (pos)
- row = row + 1
- fl = pos
-end
-local ROWCOL = (NL + m.P(1))^0
-local function rowcol(str, n)
- row = 1
- fl = 1
- ROWCOL:match(str:sub(1, n))
- local col = n - fl + 1
- return row, col
-end
-
-local function rowcol_utf8(str, n)
- row = 1
- fl = 1
- ROWCOL:match(str:sub(1, n))
- return row, util.utf8Len(str, fl, n)
-end
-
-local function position(str, _row, _col)
- local cur = 1
- local row = 1
- while true do
- if row == _row then
- return cur + _col - 1
- elseif row > _row then
- return cur - 1
- end
- local pos = str:find('[\r\n]', cur)
- if not pos then
- return #str
- end
- row = row + 1
- if str:sub(pos, pos+1) == '\r\n' then
- cur = pos + 2
- else
- cur = pos + 1
- end
- end
-end
-
-local function position_utf8(str, _row, _col)
- local cur = 1
- local row = 1
- while true do
- if row == _row then
- return utf8.offset(str, _col, cur)
- elseif row > _row then
- return cur - 1
- end
- local pos = str:find('[\r\n]', cur)
- if not pos then
- return #str
- end
- row = row + 1
- if str:sub(pos, pos+1) == '\r\n' then
- cur = pos + 2
- else
- cur = pos + 1
- end
- end
-end
-
-local NL = m.P'\r\n' + m.S'\r\n'
-
-local function line(str, row)
- local count = 0
- local res
- local LINE = m.Cmt((1 - NL)^0, function (_, _, c)
- count = count + 1
- if count == row then
- res = c
- return false
- end
- return true
- end)
- local MATCH = (LINE * NL)^0 * LINE
- MATCH:match(str)
- return res
-end
-
-return {
- rowcol = rowcol,
- rowcol_utf8 = rowcol_utf8,
- position = position,
- position_utf8 = position_utf8,
- line = line,
-}
diff --git a/script/parser/compile.lua b/script/parser/compile.lua
index 752728d1..c7e9256a 100644
--- a/script/parser/compile.lua
+++ b/script/parser/compile.lua
@@ -1,12 +1,111 @@
-local guide = require 'parser.guide'
-local parse = require 'parser.parse'
-local newparser = require 'parser.newparser'
-local type = type
-local tableInsert = table.insert
-local pairs = pairs
-local os = os
-
-local specials = {
+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 mtype = math.type
+local tonumber = tonumber
+local maxinteger = math.maxinteger
+local assert = assert
+local next = next
+
+_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,
@@ -18,491 +117,615 @@ local specials = {
['xpcall'] = true,
['pairs'] = true,
['ipairs'] = true,
+ ['assert'] = true,
+ ['error'] = true,
+ ['type'] = true,
}
-_ENV = nil
+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 pushError, Compile, CompileBlock, Block, GoToTag, ENVMode, Compiled, LocalCount, Version, Root, Options
-local function addRef(node, obj)
- if not node.ref then
- node.ref = {}
- end
- node.ref[#node.ref+1] = obj
- obj.node = node
-end
+local parseExp, parseAction
+
+local pushError
local function addSpecial(name, obj)
- if not Root.specials then
- Root.specials = {}
+ if not State.specials then
+ State.specials = {}
end
- if not Root.specials[name] then
- Root.specials[name] = {}
+ if not State.specials[name] then
+ State.specials[name] = {}
end
- Root.specials[name][#Root.specials[name]+1] = obj
+ State.specials[name][#State.specials[name]+1] = obj
obj.special = name
end
-local vmMap = {
- ['getname'] = function (obj)
- local loc = guide.getLocal(obj, obj[1], obj.start)
- if loc then
- obj.type = 'getlocal'
- obj.loc = loc
- addRef(loc, obj)
- if loc.special then
- addSpecial(loc.special, obj)
- end
- else
- obj.type = 'getglobal'
- local node = guide.getLocal(obj, ENVMode, obj.start)
- if node then
- addRef(node, obj)
- end
- local name = obj[1]
- if specials[name] then
- addSpecial(name, obj)
- elseif Options and Options.special then
- local asName = Options.special[name]
- if specials[asName] then
- addSpecial(asName, obj)
- end
- 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
+---@return integer newOffset
+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
- return obj
- end,
- ['getfield'] = function (obj)
- Compile(obj.node, obj)
- end,
- ['call'] = function (obj)
- Compile(obj.node, obj)
- if obj.node and obj.node.type == 'getmethod' then
- if not obj.args then
- obj.args = {
- type = 'callargs',
- start = obj.start,
- finish = obj.finish,
- parent = obj,
- }
- end
- local newNode = {}
- for k, v in pairs(obj.node.node) do
- newNode[k] = v
- end
- newNode.mirror = obj.node.node
- newNode.dummy = true
- newNode.parent = obj.args
- obj.node.node.mirror = newNode
- tableInsert(obj.args, 1, newNode)
- Compiled[newNode] = true
- end
- Compile(obj.args, obj)
- end,
- ['callargs'] = function (obj)
- for i = 1, #obj do
- Compile(obj[i], obj)
- end
- end,
- ['binary'] = function (obj)
- Compile(obj[1], obj)
- Compile(obj[2], obj)
- end,
- ['unary'] = function (obj)
- Compile(obj[1], obj)
- end,
- ['varargs'] = function (obj)
- local func = guide.getParentFunction(obj)
- if func then
- local index, vararg = guide.getFunctionVarArgs(func)
- if not index then
- pushError {
- type = 'UNEXPECT_DOTS',
- start = obj.start,
- finish = obj.finish,
+ 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
- if vararg then
- if not vararg.ref then
- vararg.ref = {}
- end
- vararg.ref[#vararg.ref+1] = obj
- obj.node = vararg
- end
- end
- end,
- ['paren'] = function (obj)
- Compile(obj.exp, obj)
- end,
- ['getindex'] = function (obj)
- Compile(obj.node, obj)
- Compile(obj.index, obj)
- end,
- ['setindex'] = function (obj)
- Compile(obj.node, obj)
- Compile(obj.index, obj)
- Compile(obj.value, obj)
- end,
- ['getmethod'] = function (obj)
- Compile(obj.node, obj)
- Compile(obj.method, obj)
- end,
- ['setmethod'] = function (obj)
- Compile(obj.node, obj)
- Compile(obj.method, obj)
- local value = obj.value
- local localself = {
- type = 'local',
- start = value.start,
- finish = value.start,
- method = obj,
- effect = obj.finish,
- tag = 'self',
- dummy = true,
- [1] = 'self',
+ },
}
- if not value.args then
- value.args = {
- type = 'funcargs',
- start = obj.start,
- finish = obj.finish,
- }
+ 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
- tableInsert(value.args, 1, localself)
- Compile(value, obj)
- end,
- ['function'] = function (obj)
- local lastBlock = Block
- local LastLocalCount = LocalCount
- Block = obj
- LocalCount = 0
- Compile(obj.args, obj)
- for i = 1, #obj do
- Compile(obj[i], obj)
- end
- Block = lastBlock
- LocalCount = LastLocalCount
- end,
- ['funcargs'] = function (obj)
- for i = 1, #obj do
- Compile(obj[i], obj)
- end
- end,
- ['table'] = function (obj)
- for i = 1, #obj do
- Compile(obj[i], obj)
- end
- end,
- ['tablefield'] = function (obj)
- Compile(obj.value, obj)
- end,
- ['tableindex'] = function (obj)
- Compile(obj.index, obj)
- Compile(obj.value, obj)
- end,
- ['tableexp'] = function (obj)
- Compile(obj.value, obj)
- end,
- ['index'] = function (obj)
- Compile(obj.index, obj)
- end,
- ['select'] = function (obj)
- local vararg = obj.vararg
- if vararg.parent then
- if not vararg.extParent then
- vararg.extParent = {}
- end
- vararg.extParent[#vararg.extParent+1] = obj
- else
- Compile(vararg, obj)
- end
- end,
- ['setname'] = function (obj)
- Compile(obj.value, obj)
- local loc = guide.getLocal(obj, obj[1], obj.start)
- if loc then
- obj.type = 'setlocal'
- obj.loc = loc
- addRef(loc, obj)
- if loc.attrs then
- local const
- for i = 1, #loc.attrs do
- local attr = loc.attrs[i][1]
- if attr == 'const'
- or attr == 'close' then
- const = true
- break
- end
- end
- if const then
- pushError {
- type = 'SET_CONST',
- start = obj.start,
- finish = obj.finish,
- }
- end
- end
- else
- obj.type = 'setglobal'
- local node = guide.getLocal(obj, ENVMode, obj.start)
- if node then
- addRef(node, obj)
- end
- local name = obj[1]
- if specials[name] then
- addSpecial(name, obj)
- elseif Options and Options.special then
- local asName = Options.special[name]
- if specials[asName] then
- addSpecial(asName, obj)
- end
- 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
- end,
- ['local'] = function (obj)
- local attrs = obj.attrs
- if attrs then
- for i = 1, #attrs do
- Compile(attrs[i], obj)
+ while true do
+ local nl = Tokens[Index + 1]
+ if not nl or NLMap[nl] then
+ break
end
+ Index = Index + 2
end
- if Block then
- if not Block.locals then
- Block.locals = {}
- end
- Block.locals[#Block.locals+1] = obj
- LocalCount = LocalCount + 1
- if LocalCount > LocalLimit then
- pushError {
- type = 'LOCAL_LIMIT',
- start = obj.start,
- finish = obj.finish,
+ State.comms[#State.comms+1] = {
+ type = chead and 'comment.cshort' or 'comment.short',
+ start = left,
+ finish = lastRightPosition(),
+ text = ssub(Lua, start + 2, Tokens[Index] and (Tokens[Index] - 1) or #Lua),
+ }
+ 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 == '/=' then
+ if not State.options.nonstandardSymbol[token] then
+ unknownSymbol()
end
+ Index = Index + 2
+ return true
end
- if obj.localfunction then
- obj.localfunction = nil
+ end
+ return false
+end
+
+local function parseLocalAttrs()
+ local attrs
+ while true do
+ skipSpace()
+ local token = Tokens[Index + 1]
+ if token ~= '<' then
+ break
end
- Compile(obj.value, obj)
- if obj.value and obj.value.special then
- addSpecial(obj.value.special, obj)
+ if not attrs then
+ attrs = {
+ type = 'localattrs',
+ }
end
- end,
- ['setfield'] = function (obj)
- Compile(obj.node, obj)
- Compile(obj.value, obj)
- end,
- ['do'] = function (obj)
- local lastBlock = Block
- Block = obj
- CompileBlock(obj, obj)
- if Block.locals then
- LocalCount = LocalCount - #Block.locals
+ 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
- Block = lastBlock
- end,
- ['return'] = function (obj)
- for i = 1, #obj do
- Compile(obj[i], obj)
+ 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 Block and Block[#Block] ~= obj then
+ if State.version ~= 'Lua 5.4' then
pushError {
- type = 'ACTION_AFTER_RETURN',
- start = obj.start,
- finish = obj.finish,
+ type = 'UNSUPPORT_SYMBOL',
+ start = attr.start,
+ finish = attr.finish,
+ version = 'Lua 5.4',
+ info = {
+ version = State.version
+ }
}
end
- local func = guide.getParentFunction(obj)
- if func then
- if not func.returns then
- func.returns = {}
- end
- func.returns[#func.returns+1] = obj
+ 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
- end,
- ['label'] = function (obj)
- local block = guide.getBlock(obj)
- if block then
- if not block.labels then
- block.labels = {}
- end
- local name = obj[1]
- local label = guide.getLabel(block, name)
- if label then
- if Version == 'Lua 5.4'
- or block == guide.getBlock(label) then
- pushError {
- type = 'REDEFINED_LABEL',
- start = obj.start,
- finish = obj.finish,
- relative = {
- {
- label.start,
- label.finish,
- }
- }
- }
- end
- end
- block.labels[name] = obj
- end
- end,
- ['goto'] = function (obj)
- GoToTag[#GoToTag+1] = obj
- end,
- ['if'] = function (obj)
- for i = 1, #obj do
- Compile(obj[i], obj)
- end
- end,
- ['ifblock'] = function (obj)
- local lastBlock = Block
- Block = obj
- Compile(obj.filter, obj)
- CompileBlock(obj, obj)
- if Block.locals then
- LocalCount = LocalCount - #Block.locals
- end
- Block = lastBlock
- end,
- ['elseifblock'] = function (obj)
- local lastBlock = Block
- Block = obj
- Compile(obj.filter, obj)
- CompileBlock(obj, obj)
- if Block.locals then
- LocalCount = LocalCount - #Block.locals
- end
- Block = lastBlock
- end,
- ['elseblock'] = function (obj)
- local lastBlock = Block
- Block = obj
- CompileBlock(obj, obj)
- if Block.locals then
- LocalCount = LocalCount - #Block.locals
- end
- Block = lastBlock
- end,
- ['loop'] = function (obj)
- local lastBlock = Block
- Block = obj
- Compile(obj.loc, obj)
- Compile(obj.max, obj)
- Compile(obj.step, obj)
- CompileBlock(obj, obj)
- if Block.locals then
- LocalCount = LocalCount - #Block.locals
- end
- Block = lastBlock
- end,
- ['in'] = function (obj)
- local lastBlock = Block
- Block = obj
- local keys = obj.keys
- for i = 1, #keys do
- Compile(keys[i], obj)
- end
- CompileBlock(obj, obj)
- if Block.locals then
- LocalCount = LocalCount - #Block.locals
- end
- Block = lastBlock
- end,
- ['while'] = function (obj)
- local lastBlock = Block
- Block = obj
- Compile(obj.filter, obj)
- CompileBlock(obj, obj)
- if Block.locals then
- LocalCount = LocalCount - #Block.locals
- end
- Block = lastBlock
- end,
- ['repeat'] = function (obj)
- local lastBlock = Block
- Block = obj
- CompileBlock(obj, obj)
- Compile(obj.filter, obj)
- if Block.locals then
- LocalCount = LocalCount - #Block.locals
- end
- Block = lastBlock
- end,
- ['break'] = function (obj)
- local block = guide.getBreakBlock(obj)
- if block then
- if not block.breaks then
- block.breaks = {}
- end
- block.breaks[#block.breaks+1] = obj
- else
+ locals[#locals+1] = obj
+ LocalCount = LocalCount + 1
+ if LocalCount > LocalLimit then
pushError {
- type = 'BREAK_OUTSIDE',
+ type = 'LOCAL_LIMIT',
start = obj.start,
finish = obj.finish,
}
end
- end,
- ['main'] = function (obj)
- Block = obj
- Compile({
- type = 'local',
- start = 0,
- finish = 0,
- effect = 0,
- tag = '_ENV',
- special= '_G',
- [1] = ENVMode,
- }, obj)
- --- _ENV 是上值,不计入局部变量计数
- LocalCount = 0
- CompileBlock(obj, obj)
- Block = nil
- end,
-}
-
-function CompileBlock(obj, parent)
- for i = 1, #obj do
- local act = obj[i]
- act.parent = parent
- local f = vmMap[act.type]
- if f then
- f(act)
- end
end
+ return obj
end
-function Compile(obj, parent)
- if not obj then
- return nil
- end
- if Compiled[obj] then
- return
- end
- Compiled[obj] = true
- obj.parent = parent
- local f = vmMap[obj.type]
- if not f then
- return
- end
- f(obj)
+local function pushChunk(chunk)
+ Chunk[#Chunk+1] = chunk
end
-local function compileGoTo(obj)
- local name = obj[1]
- local label = guide.getLabel(obj, name)
- if not label then
- pushError {
- type = 'NO_VISIBLE_LABEL',
- start = obj.start,
- finish = obj.finish,
- info = {
- label = name,
- }
- }
- return
- end
+local function resolveLable(label, obj)
if not label.ref then
label.ref = {}
end
@@ -538,10 +761,10 @@ local function compileGoTo(obj)
local ref = refs[j]
if ref.finish > label.finish then
pushError {
- type = 'JUMP_LOCAL_SCOPE',
- start = obj.start,
- finish = obj.finish,
- info = {
+ type = 'JUMP_LOCAL_SCOPE',
+ start = obj.start,
+ finish = obj.finish,
+ info = {
loc = loc[1],
},
relative = {
@@ -562,42 +785,3084 @@ local function compileGoTo(obj)
end
end
-local function PostCompile()
- for i = 1, #GoToTag do
- compileGoTo(GoToTag[i])
+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
-return function (lua, mode, version, options)
- do
- local state, err = newparser(lua, mode, version, options)
- return state, err
- end
- local state, err = parse(lua, mode, version, options)
- if not state then
- return nil, err
- end
- --if options and options.delay then
- -- options.delay()
- --end
- local clock = os.clock()
- pushError = state.pushError
- ENVMode = state.ENVMode
- Compiled = {}
- GoToTag = {}
+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 <= 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 dropTail()
+ local token = Tokens[Index + 1]
+ if token ~= '?'
+ and token ~= ':' then
+ return
+ end
+ local pl, pt, pp = 0, 0, 0
+ while true do
+ local token = Tokens[Index + 1]
+ if not token then
+ break
+ end
+ if NLMap[token] then
+ break
+ end
+ if token == ',' then
+ if pl > 0
+ or pt > 0
+ or pp > 0 then
+ goto CONTINUE
+ else
+ break
+ end
+ end
+ if token == '<' then
+ pl = pl + 1
+ goto CONTINUE
+ end
+ if token == '{' then
+ pt = pt + 1
+ goto CONTINUE
+ end
+ if token == '(' then
+ pp = pp + 1
+ goto CONTINUE
+ end
+ if token == '>' then
+ if pl <= 0 then
+ break
+ end
+ pl = pl - 1
+ goto CONTINUE
+ end
+ if token == '}' then
+ if pt <= 0 then
+ break
+ end
+ pt = pt - 1
+ goto CONTINUE
+ end
+ if token == ')' then
+ if pp <= 0 then
+ break
+ end
+ pp = pp - 1
+ goto CONTINUE
+ end
+ ::CONTINUE::
+ Index = Index + 2
+ end
+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
+ dropTail()
+ 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
+ local eqRight = lastRightPosition()
+ skipSpace()
+ local fvalue = parseExp()
+ local tfield = {
+ type = 'tablefield',
+ start = name.start,
+ finish = fvalue and fvalue.finish or eqRight,
+ 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.parent = tbl
+ index = index + 1
+ tbl[index] = tindex
+ if expectAssign() then
+ skipSpace()
+ local ivalue = parseExp()
+ if ivalue then
+ ivalue.parent = tindex
+ tindex.finish = 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 parseSimple(node, funcName)
+ 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'
+ 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)
+ 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]
+ if Specials[name] then
+ addSpecial(name, node)
+ else
+ local ospeicals = State.options.special
+ if ospeicals and ospeicals[name] then
+ addSpecial(ospeicals[name], node)
+ end
+ end
+ 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
- Version = version
- Root = state.ast
- if Root then
- Root.state = state
- end
- Options = options
- if type(state.ast) == 'table' then
- Compile(state.ast)
- end
- PostCompile()
- state.compileClock = os.clock() - clock
- Compiled = nil
- GoToTag = nil
- return state
+ 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
+ local parenLeft = getPosition(Tokens[Index], 'left')
+ Index = Index + 2
+ params = parseParams(params)
+ if params then
+ params.type = 'funcargs'
+ params.start = parenLeft
+ params.finish = lastRightPosition()
+ params.parent = func
+ func.args = params
+ end
+ 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
+ missEnd(funcLeft, funcRight)
+ end
+ LocalCount = LastLocalCount
+ return func
+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()
+ return table
+ end
+
+ if CharMapStrSH[token] then
+ local string = parseShortString()
+ return string
+ end
+
+ if CharMapStrLH[token] then
+ local string = parseLongString()
+ return string
+ 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
+ end
+ Index = Index + 2
+ skipSpace()
+ local second = parser(true)
+ if not second then
+ missName()
+ return
+ end
+ if isLocal then
+ createLocal(second, parseLocalAttrs())
+ second.effect = maxinteger
+ 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())
+ third.effect = maxinteger
+ 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())
+ name.effect = maxinteger
+ end
+ rest[#rest+1] = name
+ end
+end
+
+local function bindValue(n, v, index, lastValue, isLocal, isSet)
+ if isLocal then
+ n.effect = lastRightPosition()
+ 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
+ if isLocal then
+ n.effect = lastRightPosition()
+ end
+ 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
+ bindValue(n1, v1, 1, nil, isLocal, isSet)
+ local lastValue = v1
+ if n2 then
+ max = 2
+ bindValue(n2, v2, 2, lastValue, isLocal, isSet)
+ lastValue = v2 or lastValue
+ pushActionIntoCurrentChunk(n2)
+ end
+ if nrest then
+ for i = 1, #nrest do
+ local n = nrest[i]
+ local v = vrest and vrest[i]
+ max = i + 2
+ bindValue(n, v, max, lastValue, isLocal, isSet)
+ lastValue = v or lastValue
+ pushActionIntoCurrentChunk(n)
+ 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 action, isSet = parseMultiVars(exp, parseExp)
+ 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 == 'else' 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)
+ if loc.value then
+ loc.effect = loc.value.finish
+ else
+ loc.effect = loc.finish
+ end
+
+ 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 == 'else' 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 = lastRightPosition()
+ 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 = lastRightPosition()
+ 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 = lastRightPosition()
+ 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
+ local state = {
+ version = version,
+ lua = lua,
+ ast = {},
+ errs = {},
+ comms = {},
+ lines = {
+ [0] = 1,
+ },
+ 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
+
+ state.pushError = pushError
+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
diff --git a/script/parser/grammar.lua b/script/parser/grammar.lua
deleted file mode 100644
index a28b7950..00000000
--- a/script/parser/grammar.lua
+++ /dev/null
@@ -1,573 +0,0 @@
-local re = require 'parser.relabel'
-local m = require 'lpeglabel'
-local ast = require 'parser.ast'
-
-local scriptBuf = ''
-local compiled = {}
-local defs = ast.defs
-
--- goto 可以作为名字,合法性之后处理
-local RESERVED = {
- ['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,
-}
-
-defs.nl = (m.P'\r\n' + m.S'\r\n')
-defs.s = m.S' \t'
-defs.S = - defs.s
-defs.ea = '\a'
-defs.eb = '\b'
-defs.ef = '\f'
-defs.en = '\n'
-defs.er = '\r'
-defs.et = '\t'
-defs.ev = '\v'
-defs['nil'] = m.Cp() / function () return nil end
-defs['false'] = m.Cp() / function () return false end
-
-defs.NotReserved = function (_, _, str)
- if RESERVED[str] then
- return false
- end
- return true
-end
-defs.Reserved = function (_, _, str)
- if RESERVED[str] then
- return true
- end
- return false
-end
-defs.None = function () end
-defs.np = m.Cp() / function (n) return n+1 end
-defs.NameBody = m.R('az', 'AZ', '__', '\x80\xff') * m.R('09', 'az', 'AZ', '__', '\x80\xff')^0
-defs.NoNil = function (o)
- if o == nil then
- return
- end
- return o
-end
-
-m.setmaxstack(1000)
-
-local eof = re.compile '!. / %{SYNTAX_ERROR}'
-
-local function grammar(tag)
- return function (script)
- scriptBuf = script .. '\r\n' .. scriptBuf
- compiled[tag] = re.compile(scriptBuf, defs) * eof
- end
-end
-
-local function errorpos(pos, err)
- return {
- type = 'UNKNOWN',
- start = pos or 0,
- finish = pos or 0,
- err = err,
- }
-end
-
-grammar 'Comment' [[
-Comment <- LongComment
- / '--' ShortComment
-LongComment <- ({} '--[' {} {:eq: '='* :} {} '[' %nl?
- {(!CommentClose .)*}
- ((CommentClose / %nil) {}))
- -> LongComment
- / (
- {} '/*' {} %nl?
- {(!'*/' .)*}
- {} '*/' {}
- )
- -> CLongComment
-CommentClose <- {']' =eq ']'}
-ShortComment <- ({} {(!%nl .)*} {})
- -> ShortComment
-]]
-
-grammar 'Sp' [[
-Sp <- (Comment / %nl / %s)*
-Sps <- (Comment / %nl / %s)+
-]]
-
-grammar 'Common' [[
-Word <- [a-zA-Z0-9_]
-Cut <- !Word
-X16 <- [a-fA-F0-9]
-Rest <- (!%nl .)*
-
-AND <- Sp {'and'} Cut
-BREAK <- Sp 'break' Cut
-FALSE <- Sp 'false' Cut
-GOTO <- Sp 'goto' Cut
-LOCAL <- Sp 'local' Cut
-NIL <- Sp 'nil' Cut
-NOT <- Sp 'not' Cut
-OR <- Sp {'or'} Cut
-RETURN <- Sp 'return' Cut
-TRUE <- Sp 'true' Cut
-CONTINUE <- Sp 'continue' Cut
-
-DO <- Sp {} 'do' {} Cut
- / Sp({} 'then' {} Cut) -> ErrDo
-IF <- Sp {} 'if' {} Cut
-ELSE <- Sp {} 'else' {} Cut
-ELSEIF <- Sp {} 'elseif' {} Cut
-END <- Sp {} 'end' {} Cut
-FOR <- Sp {} 'for' {} Cut
-FUNCTION <- Sp {} 'function' {} Cut
-IN <- Sp {} 'in' {} Cut
-REPEAT <- Sp {} 'repeat' {} Cut
-THEN <- Sp {} 'then' {} Cut
- / Sp({} 'do' {} Cut) -> ErrThen
-UNTIL <- Sp {} 'until' {} Cut
-WHILE <- Sp {} 'while' {} Cut
-
-
-Esc <- '\' -> ''
- EChar
-EChar <- 'a' -> ea
- / 'b' -> eb
- / 'f' -> ef
- / 'n' -> en
- / 'r' -> er
- / 't' -> et
- / 'v' -> ev
- / '\'
- / '"'
- / "'"
- / %nl
- / ('z' (%nl / %s)*) -> ''
- / ({} 'x' {X16 X16}) -> Char16
- / ([0-9] [0-9]? [0-9]?) -> Char10
- / ('u{' {} {Word*} '}') -> CharUtf8
- -- 错误处理
- / 'x' {} -> MissEscX
- / 'u' !'{' {} -> MissTL
- / 'u{' Word* !'}' {} -> MissTR
- / {} -> ErrEsc
-
-BOR <- Sp {'|'}
-BXOR <- Sp {'~'} !'='
-BAND <- Sp {'&'}
-Bshift <- Sp {BshiftList}
-BshiftList <- '<<'
- / '>>'
-Concat <- Sp {'..'}
-Adds <- Sp {AddsList}
-AddsList <- '+'
- / '-'
-Muls <- Sp {MulsList}
-MulsList <- '*'
- / '//'
- / '/'
- / '%'
-Unary <- Sp {} {UnaryList}
-UnaryList <- NOT
- / '#'
- / '-'
- / '~' !'='
-POWER <- Sp {'^'}
-
-BinaryOp <-( Sp {} {'or' / '||'} Cut
- / Sp {} {'and' / '&&'} Cut
- / Sp {} {'<=' / '>=' / '<'!'<' / '>'!'>' / '~=' / '==' / '!='}
- / Sp {} ({} '=' {}) -> ErrEQ
- / Sp {} ({} '!=' {}) -> ErrUEQ
- / Sp {} {'|'}
- / Sp {} {'~'}
- / Sp {} {'&'}
- / Sp {} {'<<' / '>>'}
- / Sp {} {'..'} !'.'
- / Sp {} {'+' / '-'}
- / Sp {} {'*' / '//' / '/' / '%'}
- / Sp {} {'^'}
- )-> BinaryOp
-UnaryOp <-( Sp {} {'not' Cut / '#' / '~' !'=' / '-' !'-' / '!' !'='}
- )-> UnaryOp
-
-PL <- Sp '('
-PR <- Sp ')'
-BL <- Sp '[' !'[' !'='
-BR <- Sp ']'
-TL <- Sp '{'
-TR <- Sp '}'
-COMMA <- Sp ({} ',')
- -> COMMA
-SEMICOLON <- Sp ({} ';')
- -> SEMICOLON
-DOTS <- Sp ({} '...')
- -> DOTS
-DOT <- Sp ({} '.' !'.')
- -> DOT
-COLON <- Sp ({} ':' !':')
- -> COLON
-LABEL <- Sp '::'
-ASSIGN <- Sp '=' !'='
- / Sp ({} {'+=' / '-=' / '*=' / '\='})
- -> ASSIGN
-AssignOrEQ <- Sp ({} '==' {})
- -> ErrAssign
- / ASSIGN
-
-DirtyBR <- BR / {} -> MissBR
-DirtyTR <- TR / {} -> MissTR
-DirtyPR <- PR / {} -> MissPR
-DirtyLabel <- LABEL / {} -> MissLabel
-NeedEnd <- END / {} -> MissEnd
-NeedDo <- DO / {} -> MissDo
-NeedAssign <- ASSIGN / {} -> MissAssign
-NeedComma <- COMMA / {} -> MissComma
-NeedIn <- IN / {} -> MissIn
-NeedUntil <- UNTIL / {} -> MissUntil
-NeedThen <- THEN / {} -> MissThen
-]]
-
-grammar 'Nil' [[
-Nil <- Sp ({} -> Nil) NIL
-]]
-
-grammar 'Boolean' [[
-Boolean <- Sp ({} -> True) TRUE
- / Sp ({} -> False) FALSE
-]]
-
-grammar 'String' [[
-String <- Sp ({} StringDef {})
- -> String
-StringDef <- {'"'}
- {~(Esc / !%nl !'"' .)*~} -> 1
- ('"' / {} -> MissQuote1)
- / {"'"}
- {~(Esc / !%nl !"'" .)*~} -> 1
- ("'" / {} -> MissQuote2)
- / {'`'}
- {(!%nl !'`' .)*} -> 1
- ('`' / {} -> MissQuote3)
- / ('[' {} {:eq: '='* :} {} '[' %nl?
- {(!StringClose .)*} -> 1
- (StringClose / {}))
- -> LongString
-StringClose <- ']' =eq ']'
-]]
-
-grammar 'Number' [[
-Number <- Sp ({} {~ '-'? NumberDef ~} {}) -> Number
- NumberSuffix?
- ErrNumber?
-NumberDef <- Number16 / Integer2 / Number10
-NumberSuffix<- ({} {[uU]? [lL] [lL]}) -> FFINumber
- / ({} {[iI]}) -> ImaginaryNumber
-ErrNumber <- ({} {([0-9a-zA-Z] / '.')+}) -> UnknownSymbol
-
-Number10 <- Float10 Float10Exp?
- / Integer10 Float10? Float10Exp?
-Integer10 <- [0-9]+ ('.' [0-9]*)?
-Float10 <- '.' [0-9]+
-Float10Exp <- [eE] [+-]? [0-9]+
- / ({} [eE] [+-]? {}) -> MissExponent
-
-Number16 <- '0' [xX] Float16 Float16Exp?
- / '0' [xX] Integer16 Float16? Float16Exp?
-Integer16 <- X16+ ('.' X16*)?
- / ({} {Word*}) -> MustX16
-Float16 <- '.' X16+
- / '.' ({} {Word*}) -> MustX16
-Float16Exp <- [pP] [+-]? [0-9]+
- / ({} [pP] [+-]? {}) -> MissExponent
-
-Integer2 <- ({} '0' [bB] {[01]+})
- -> Integer2
-]]
-
-grammar 'Name' [[
-Name <- Sp ({} NameBody {})
- -> Name
-NameBody <- {%NameBody}
-KeyWord <- Sp NameBody=>Reserved
-MustName <- Name / DirtyName
-DirtyName <- {} -> DirtyName
-]]
-
-grammar 'DocType' [[
-DocType <- (!%nl !')' !',' DocChar)+
-DocChar <- '(' (!%nl !')' .)+ ')'?
- / '<' (!%nl !'>' .)+ '>'?
- / .
-]]
-
-grammar 'Exp' [[
-Exp <- (UnUnit BinUnit*)
- -> Binary
-BinUnit <- (BinaryOp UnUnit?)
- -> SubBinary
-UnUnit <- Number
- / (UnaryOp+ (ExpUnit / MissExp))
- -> Unary
- / ExpUnit
-ExpUnit <- Nil
- / Boolean
- / String
- / Number
- / Dots
- / Table
- / ExpFunction
- / Simple
-
-Simple <- {| Prefix (Sp Suffix)* |}
- -> Simple
-Prefix <- Sp ({} PL DirtyExp DirtyPR {})
- -> Paren
- / Single
-Single <- !FUNCTION Name
- -> Single
-Suffix <- SuffixWithoutCall
- / ({} PL SuffixCall DirtyPR {})
- -> Call
-SuffixCall <- Sp ({} {| (COMMA / CallArg)+ |} {})
- -> PackExpList
- / %nil
-CallArg <- Sp (Name {} {'?'? ':'} Sps DocType)
- -> CallArgSnip
- / Exp->NoNil
-SuffixWithoutCall
- <- (DOT (Name / MissField))
- -> GetField
- / ({} BL DirtyExp DirtyBR {})
- -> GetIndex
- / (COLON (Name / MissMethod) NeedCall)
- -> GetMethod
- / ({} {| Table |} {})
- -> Call
- / ({} {| String |} {})
- -> Call
-NeedCall <- (!(Sp CallStart) {} -> MissPL)?
-MissField <- {} -> MissField
-MissMethod <- {} -> MissMethod
-CallStart <- PL
- / TL
- / '"'
- / "'"
- / '[' '='* '['
-
-DirtyExp <- !THEN !DO !END Exp
- / {} -> DirtyExp
-MaybeExp <- Exp / MissExp
-MissExp <- {} -> MissExp
-ExpList <- Sp {| MaybeExp (Sp ',' MaybeExp)* |}
-
-Dots <- DOTS
- -> VarArgs
-
-Table <- Sp ({} TL {| TableField* |} DirtyTR {})
- -> Table
-TableField <- COMMA
- / SEMICOLON
- / Dots
- / NewIndex
- / NewField
- / TableExp
-Index <- BL DirtyExp DirtyBR
-NewIndex <- Sp ({} Index NeedAssign DirtyExp {})
- -> NewIndex
-NewField <- Sp ({} MustName ASSIGN DirtyExp {})
- -> NewField
-TableExp <- Sp ({} Exp {})
- -> TableExp
-
-ExpFunction <- Function
- -> ExpFunction
-Function <- FunctionBody
- -> Function
-FunctionBody
- <- FUNCTION FuncName FuncArgs
- {| (!END Action)* |}
- NeedEnd
- / FUNCTION FuncName FuncArgsMiss
- {| %nil |}
- NeedEnd
-FuncName <- !END {| Single (Sp SuffixWithoutCall)* |}
- -> Simple
- / %nil
-
-FuncArgs <- Sp ({} PL {| FuncArg+ |} DirtyPR {})
- -> FuncArgs
- / PL DirtyPR %nil
-FuncArgsMiss<- {} -> MissPL DirtyPR %nil
-FuncArg <- DOTS
- / Name
- / COMMA
-
--- 纯占位,修改了 `relabel.lua` 使重复定义不抛错
-Action <- !END .
-]]
-
-grammar 'Action' [[
-Action <- Sp (CrtAction / UnkAction)
-CrtAction <- Semicolon
- / Do
- / Break
- / Return
- / Label
- / GoTo
- / If
- / For
- / While
- / Repeat
- / NamedFunction
- / LocalFunction
- / Local
- / Set
- / Continue
- / Call
- / ExpInAction
-UnkAction <- ({} {Word+})
- -> UnknownAction
- / ({} '//' {} (LongComment / ShortComment) {})
- -> CCommentPrefix
- / ({} {. (!Sps !CrtAction .)*})
- -> UnknownAction
-ExpInAction <- Sp ({} Exp {})
- -> ExpInAction
-
-Semicolon <- Sp ';'
-SimpleList <- {| Simple (Sp ',' Simple)* |}
-
-Do <- Sp ({}
- 'do' Cut
- {| (!END Action)* |}
- NeedEnd)
- -> Do
-
-Break <- Sp ({} BREAK {})
- -> Break
-
-Continue <- Sp ({} CONTINUE {})
- => RTContinue
- -> Continue
-
-Return <- Sp ({} RETURN ReturnExpList {})
- -> Return
-ReturnExpList
- <- Sp !END !ELSEIF !ELSE {| Exp (Sp ',' MaybeExp)* |}
- / Sp {| %nil |}
-
-Label <- Sp ({} LABEL MustName DirtyLabel {})
- -> Label
-
-GoTo <- Sp ({} GOTO MustName {})
- -> GoTo
-
-If <- Sp ({} {| IfHead IfBody* |} NeedEnd)
- -> If
-
-IfHead <- Sp (IfPart {}) -> IfBlock
- / Sp (ElseIfPart {}) -> ElseIfBlock
- / Sp (ElsePart {}) -> ElseBlock
-IfBody <- Sp (ElseIfPart {}) -> ElseIfBlock
- / Sp (ElsePart {}) -> ElseBlock
-IfPart <- IF DirtyExp NeedThen
- {| (!ELSEIF !ELSE !END Action)* |}
-ElseIfPart <- ELSEIF DirtyExp NeedThen
- {| (!ELSEIF !ELSE !END Action)* |}
-ElsePart <- ELSE
- {| (!ELSEIF !ELSE !END Action)* |}
-
-For <- Loop / In
-
-Loop <- LoopBody
- -> Loop
-LoopBody <- FOR LoopArgs NeedDo
- {} {| (!END Action)* |}
- NeedEnd
-LoopArgs <- MustName AssignOrEQ
- ({} {| (COMMA / !DO !END Exp->NoNil)* |} {})
- -> PackLoopArgs
-
-In <- InBody
- -> In
-InBody <- FOR InNameList NeedIn InExpList NeedDo
- {} {| (!END Action)* |}
- NeedEnd
-InNameList <- ({} {| (COMMA / !IN !DO !END Name->NoNil)* |} {})
- -> PackInNameList
-InExpList <- ({} {| (COMMA / !DO !DO !END Exp->NoNil)* |} {})
- -> PackInExpList
-
-While <- WhileBody
- -> While
-WhileBody <- WHILE DirtyExp NeedDo
- {| (!END Action)* |}
- NeedEnd
-
-Repeat <- (RepeatBody {})
- -> Repeat
-RepeatBody <- REPEAT
- {| (!UNTIL Action)* |}
- NeedUntil DirtyExp
-
-LocalAttr <- {| (Sp '<' Sp MustName Sp LocalAttrEnd)+ |}
- -> LocalAttr
-LocalAttrEnd<- ({} '>' &'=') -> MissSpaceBetween
- / '>'
- / {} -> MissGT
-Local <- Sp ({} LOCAL LocalNameList ((AssignOrEQ ExpList) / %nil) {})
- -> Local
-Set <- Sp ({} SimpleList AssignOrEQ {} ExpList {})
- -> Set
-LocalNameList
- <- {| LocalName (Sp ',' LocalName)* |}
-LocalName <- (MustName LocalAttr?)
- -> LocalName
-
-NamedFunction
- <- Function
- -> NamedFunction
-
-Call <- Simple
- -> SimpleCall
-
-LocalFunction
- <- Sp ({} LOCAL Function)
- -> LocalFunction
-]]
-
-grammar 'Lua' [[
-Lua <- Head?
- ({} {| Action* |} {}) -> Lua
- Sp
-Head <- '#' (!%nl .)*
-]]
-
-return function (lua, mode)
- local gram = compiled[mode] or compiled['Lua']
- local r, _, pos = gram:match(lua)
- if not r then
- local err = errorpos(pos)
- return nil, err
- end
- if type(r) ~= 'table' then
- return nil
- end
-
- return r
-end
diff --git a/script/parser/init.lua b/script/parser/init.lua
index 219f8900..bc004f77 100644
--- a/script/parser/init.lua
+++ b/script/parser/init.lua
@@ -1,13 +1,8 @@
local api = {
- grammar = require 'parser.grammar',
- parse = require 'parser.parse',
compile = require 'parser.compile',
- split = require 'parser.split',
- calcline = require 'parser.calcline',
lines = require 'parser.lines',
guide = require 'parser.guide',
luadoc = require 'parser.luadoc',
- tokens = require 'parser.tokens',
}
return api
diff --git a/script/parser/luadoc.lua b/script/parser/luadoc.lua
index 99ef8781..061110c8 100644
--- a/script/parser/luadoc.lua
+++ b/script/parser/luadoc.lua
@@ -1,7 +1,7 @@
local m = require 'lpeglabel'
local re = require 'parser.relabel'
local guide = require 'parser.guide'
-local parser = require 'parser.newparser'
+local compile = require 'parser.compile'
local util = require 'utility'
local TokenTypes, TokenStarts, TokenFinishs, TokenContents, TokenMarks
@@ -1320,7 +1320,7 @@ local function trimTailComment(text)
comment = text:sub(3)
end
if comment:find '^%s*[\'"[]' then
- local state = parser(comment:gsub('^%s+', ''), 'String')
+ local state = compile(comment:gsub('^%s+', ''), 'String')
if state and state.ast then
comment = state.ast[1]
end
diff --git a/script/parser/newparser.lua b/script/parser/newparser.lua
deleted file mode 100644
index c7e9256a..00000000
--- a/script/parser/newparser.lua
+++ /dev/null
@@ -1,3868 +0,0 @@
-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 mtype = math.type
-local tonumber = tonumber
-local maxinteger = math.maxinteger
-local assert = assert
-local next = next
-
-_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
----@return integer newOffset
-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
- State.comms[#State.comms+1] = {
- type = chead and 'comment.cshort' or 'comment.short',
- start = left,
- finish = lastRightPosition(),
- text = ssub(Lua, start + 2, Tokens[Index] and (Tokens[Index] - 1) or #Lua),
- }
- 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 == '/=' 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 <= 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 dropTail()
- local token = Tokens[Index + 1]
- if token ~= '?'
- and token ~= ':' then
- return
- end
- local pl, pt, pp = 0, 0, 0
- while true do
- local token = Tokens[Index + 1]
- if not token then
- break
- end
- if NLMap[token] then
- break
- end
- if token == ',' then
- if pl > 0
- or pt > 0
- or pp > 0 then
- goto CONTINUE
- else
- break
- end
- end
- if token == '<' then
- pl = pl + 1
- goto CONTINUE
- end
- if token == '{' then
- pt = pt + 1
- goto CONTINUE
- end
- if token == '(' then
- pp = pp + 1
- goto CONTINUE
- end
- if token == '>' then
- if pl <= 0 then
- break
- end
- pl = pl - 1
- goto CONTINUE
- end
- if token == '}' then
- if pt <= 0 then
- break
- end
- pt = pt - 1
- goto CONTINUE
- end
- if token == ')' then
- if pp <= 0 then
- break
- end
- pp = pp - 1
- goto CONTINUE
- end
- ::CONTINUE::
- Index = Index + 2
- end
-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
- dropTail()
- 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
- local eqRight = lastRightPosition()
- skipSpace()
- local fvalue = parseExp()
- local tfield = {
- type = 'tablefield',
- start = name.start,
- finish = fvalue and fvalue.finish or eqRight,
- 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.parent = tbl
- index = index + 1
- tbl[index] = tindex
- if expectAssign() then
- skipSpace()
- local ivalue = parseExp()
- if ivalue then
- ivalue.parent = tindex
- tindex.finish = 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 parseSimple(node, funcName)
- 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'
- 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)
- 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]
- if Specials[name] then
- addSpecial(name, node)
- else
- local ospeicals = State.options.special
- if ospeicals and ospeicals[name] then
- addSpecial(ospeicals[name], node)
- end
- end
- 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
- local parenLeft = getPosition(Tokens[Index], 'left')
- Index = Index + 2
- params = parseParams(params)
- if params then
- params.type = 'funcargs'
- params.start = parenLeft
- params.finish = lastRightPosition()
- params.parent = func
- func.args = params
- end
- 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
- missEnd(funcLeft, funcRight)
- end
- LocalCount = LastLocalCount
- return func
-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()
- return table
- end
-
- if CharMapStrSH[token] then
- local string = parseShortString()
- return string
- end
-
- if CharMapStrLH[token] then
- local string = parseLongString()
- return string
- 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
- end
- Index = Index + 2
- skipSpace()
- local second = parser(true)
- if not second then
- missName()
- return
- end
- if isLocal then
- createLocal(second, parseLocalAttrs())
- second.effect = maxinteger
- 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())
- third.effect = maxinteger
- 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())
- name.effect = maxinteger
- end
- rest[#rest+1] = name
- end
-end
-
-local function bindValue(n, v, index, lastValue, isLocal, isSet)
- if isLocal then
- n.effect = lastRightPosition()
- 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
- if isLocal then
- n.effect = lastRightPosition()
- end
- 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
- bindValue(n1, v1, 1, nil, isLocal, isSet)
- local lastValue = v1
- if n2 then
- max = 2
- bindValue(n2, v2, 2, lastValue, isLocal, isSet)
- lastValue = v2 or lastValue
- pushActionIntoCurrentChunk(n2)
- end
- if nrest then
- for i = 1, #nrest do
- local n = nrest[i]
- local v = vrest and vrest[i]
- max = i + 2
- bindValue(n, v, max, lastValue, isLocal, isSet)
- lastValue = v or lastValue
- pushActionIntoCurrentChunk(n)
- 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 action, isSet = parseMultiVars(exp, parseExp)
- 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 == 'else' 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)
- if loc.value then
- loc.effect = loc.value.finish
- else
- loc.effect = loc.finish
- end
-
- 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 == 'else' 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 = lastRightPosition()
- 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 = lastRightPosition()
- 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 = lastRightPosition()
- 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
- local state = {
- version = version,
- lua = lua,
- ast = {},
- errs = {},
- comms = {},
- lines = {
- [0] = 1,
- },
- 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
-
- state.pushError = pushError
-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
diff --git a/script/parser/parse.lua b/script/parser/parse.lua
deleted file mode 100644
index e7c7d177..00000000
--- a/script/parser/parse.lua
+++ /dev/null
@@ -1,63 +0,0 @@
-local ast = require 'parser.ast'
-local grammar = require 'parser.grammar'
-
-local function buildState(lua, version, options)
- local errs = {}
- local diags = {}
- local comms = {}
- local state = {
- version = version,
- lua = lua,
- root = {},
- errs = errs,
- diags = diags,
- comms = comms,
- options = options or {},
- pushError = function (err)
- 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,
- pushDiag = function (code, info)
- if not diags[code] then
- diags[code] = {}
- end
- diags[code][#diags[code]+1] = info
- end,
- pushComment = function (comment)
- comms[#comms+1] = comment
- end
- }
- if version == 'Lua 5.1' or version == 'LuaJIT' then
- state.ENVMode = '@fenv'
- else
- state.ENVMode = '_ENV'
- end
- return state
-end
-
-return function (lua, mode, version, options)
- local state = buildState(lua, version, options)
- local clock = os.clock()
- ast.init(state)
- local suc, res, err = xpcall(grammar, debug.traceback, lua, mode)
- ast.close()
- if not suc then
- return nil, res
- end
- if not res and err then
- state.pushError(err)
- end
- state.ast = res
- state.parseClock = os.clock() - clock
- return state
-end
diff --git a/script/parser/split.lua b/script/parser/split.lua
deleted file mode 100644
index 6ce4a4e7..00000000
--- a/script/parser/split.lua
+++ /dev/null
@@ -1,9 +0,0 @@
-local m = require 'lpeglabel'
-
-local NL = m.P'\r\n' + m.S'\r\n'
-local LINE = m.C(1 - NL)
-
-return function (str)
- local MATCH = m.Ct((LINE * NL)^0 * LINE)
- return MATCH:match(str)
-end