diff options
author | 最萌小汐 <sumneko@hotmail.com> | 2022-06-15 21:06:07 +0800 |
---|---|---|
committer | 最萌小汐 <sumneko@hotmail.com> | 2022-06-15 21:06:07 +0800 |
commit | 6fd670b15c9403e92ab9a803f8fa885fe3d9bee2 (patch) | |
tree | ae9f0559a6acee9aa1df64f6090f14726bde77c1 /script/parser | |
parent | 241c518d2e02f2b65c59460d0bb8d4101d98614a (diff) | |
download | lua-language-server-6fd670b15c9403e92ab9a803f8fa885fe3d9bee2.zip |
cleanup
Diffstat (limited to 'script/parser')
-rw-r--r-- | script/parser/ast.lua | 1997 | ||||
-rw-r--r-- | script/parser/calcline.lua | 94 | ||||
-rw-r--r-- | script/parser/compile.lua | 4249 | ||||
-rw-r--r-- | script/parser/grammar.lua | 573 | ||||
-rw-r--r-- | script/parser/init.lua | 5 | ||||
-rw-r--r-- | script/parser/luadoc.lua | 4 | ||||
-rw-r--r-- | script/parser/newparser.lua | 3868 | ||||
-rw-r--r-- | script/parser/parse.lua | 63 | ||||
-rw-r--r-- | script/parser/split.lua | 9 |
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 |