From bba2d71fca820ba5c6e6e5c3fe04233b8ab7c5ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Tue, 5 Sep 2023 14:49:02 +0800 Subject: [PATCH 01/94] =?UTF-8?q?=E6=96=B0=E8=A7=A3=E6=9E=90=E5=99=A8?= =?UTF-8?q?=E7=AC=AC=E4=B8=80=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/class.lua | 329 ++ src/parser.old/compile.lua | 3989 +++++++++++++++++++++++ src/{parser => parser.old}/guide.lua | 0 src/parser.old/init.lua | 8 + src/{parser => parser.old}/lines.lua | 0 src/{parser => parser.old}/luadoc.lua | 0 src/{parser => parser.old}/relabel.lua | 0 src/{parser => parser.old}/tokens.lua | 0 src/parser/compile.lua | 4018 +----------------------- src/parser/init.lua | 14 +- src/parser/node.lua | 10 + 11 files changed, 4373 insertions(+), 3995 deletions(-) create mode 100644 src/class.lua create mode 100644 src/parser.old/compile.lua rename src/{parser => parser.old}/guide.lua (100%) create mode 100644 src/parser.old/init.lua rename src/{parser => parser.old}/lines.lua (100%) rename src/{parser => parser.old}/luadoc.lua (100%) rename src/{parser => parser.old}/relabel.lua (100%) rename src/{parser => parser.old}/tokens.lua (100%) create mode 100644 src/parser/node.lua diff --git a/src/class.lua b/src/class.lua new file mode 100644 index 0000000..2cbc4b9 --- /dev/null +++ b/src/class.lua @@ -0,0 +1,329 @@ +---@class Class +local M = {} + +---@private +---@type table +M._classes = {} + +---@private +---@type table +M._classConfig = {} + +---@private +M._errorHandler = error + +---@class Class.Base +---@field public __init? fun(self: any, ...) +---@field public __del? fun(self: any) +---@field public __alloc? fun(self: any) +---@field package __call fun(self: any, ...) +---@field public __getter table + +---@class Class.Config +---@field private name string +---@field package extendsMap table +---@field package extendsCalls Class.Extends.CallData[] +---@field private superCache table +---@field package superClass? Class.Base +---@field public getter table +---@field package initCalls? false|fun(...)[] +local Config = {} + +---@param name string +---@return Class.Config +function M.getConfig(name) + if not M._classConfig[name] then + M._classConfig[name] = setmetatable({ + name = name, + extendsMap = {}, + superCache = {}, + extendsCalls = {}, + }, { __index = Config }) + end + return M._classConfig[name] +end + +-- 定义一个类 +---@generic T: string +---@param name `T` +---@param super? string +---@return T +---@return Class.Config +function M.declare(name, super) + local config = M.getConfig(name) + if M._classes[name] then + return M._classes[name], config + end + local class = {} + local getter = {} + class.__name = name + class.__getter = getter + + ---@param self any + ---@param k any + ---@return any + local function getterFunc(self, k) + local f = getter[k] + if f then + return f(self) + else + return class[k] + end + end + + function class:__index(k) + if next(class.__getter) then + class.__index = getterFunc + return getterFunc(self, k) + else + class.__index = class + return class[k] + end + end + + function class:__call(...) + M.runInit(self, name, ...) + return self + end + + M._classes[name] = class + + local mt = { + __call = function (self, ...) + if not self.__alloc then + return self + end + return self:__alloc(...) + end, + } + setmetatable(class, mt) + + local superClass = M._classes[super] + if superClass then + if class == superClass then + M._errorHandler(('class %q can not inherit itself'):format(name)) + end + + config.superClass = superClass + config:extends(super, function () end) + end + + return class, config +end + +-- 获取一个类 +---@generic T: string +---@param name `T` +---@return T +function M.get(name) + return M._classes[name] +end + +-- 实例化一个类 +---@generic T: string +---@param name `T` +---@param tbl? table +---@return T +function M.new(name, tbl) + local class = M._classes[name] + if not class then + M._errorHandler(('class %q not found'):format(name)) + end + + if not tbl then + tbl = {} + end + tbl.__class__ = name + + local instance = setmetatable(tbl, class) + + return instance +end + +-- 析构一个实例 +---@param obj table +function M.delete(obj) + if obj.__deleted__ then + return + end + obj.__deleted__ = true + local name = obj.__class__ + if not name then + M._errorHandler('can not delete undeclared class') + end + + M.runDel(obj, name) +end + +-- 获取类的名称 +---@param obj table +---@return string? +function M.type(obj) + return obj.__class__ +end + +-- 判断一个实例是否有效 +---@param obj table +---@return boolean +function M.isValid(obj) + return obj.__class__ + and not obj.__deleted__ +end + +---@param name string +---@return fun(...) +function M.super(name) + local config = M.getConfig(name) + return config:super(name) +end + +---@alias Class.Extends.CallData { name: string, init?: fun(self: any, super: fun(...), ...) } + +---@generic Class: string +---@generic Extends: string +---@param name `Class` +---@param extendsName `Extends` +---@param init? fun(self: Class, super: Extends, ...) +function M.extends(name, extendsName, init) + local config = M.getConfig(name) + config:extends(extendsName, init) +end + +---@private +---@param obj table +---@param name string +---@param ... any +function M.runInit(obj, name, ...) + local data = M.getConfig(name) + if data.initCalls == false then + return + end + if not data.initCalls then + local initCalls = {} + + local function collectInitCalls(cname) + local class = M._classes[cname] + local cdata = M.getConfig(cname) + local extendsCalls = cdata.extendsCalls + if extendsCalls then + for _, call in ipairs(extendsCalls) do + if call.init then + initCalls[#initCalls+1] = function (cobj, ...) + call.init(cobj, function (...) + M.runInit(cobj, call.name, ...) + end, ...) + end + else + collectInitCalls(call.name) + end + end + end + if class.__init then + initCalls[#initCalls+1] = class.__init + end + end + + collectInitCalls(name) + + if #initCalls == 0 then + data.initCalls = false + return + else + data.initCalls = initCalls + end + end + + for i = 1, #data.initCalls do + data.initCalls[i](obj, ...) + end +end + +---@private +---@param obj table +---@param name string +function M.runDel(obj, name) + local class = M._classes[name] + local data = M.getConfig(name) + local extendsCalls = data.extendsCalls + if extendsCalls then + for _, call in ipairs(extendsCalls) do + M.runDel(obj, call.name) + end + end + if class.__del then + class.__del(obj) + end +end + +---@param errorHandler fun(msg: string) +function M.setErrorHandler(errorHandler) + M._errorHandler = errorHandler +end + +---@param name string +---@return fun(...) +function Config:super(name) + if not self.superCache[name] then + local class = M._classes[name] + if not class then + M._errorHandler(('class %q not found'):format(name)) + end + local super = self.superClass + if not super then + M._errorHandler(('class %q not inherit from any class'):format(name)) + end + ---@cast super -? + self.superCache[name] = function (...) + local k, obj = debug.getlocal(2, 1) + if k ~= 'self' then + M._errorHandler(('`%s()` must be called by the class'):format(name)) + end + super.__call(obj,...) + end + end + return self.superCache[name] +end + +---@generic Extends: string +---@param extendsName `Extends` +---@param init? fun(self: self, super: Extends) +function Config:extends(extendsName, init) + local class = M._classes[self.name] + local extends = M._classes[extendsName] + if not extends then + M._errorHandler(('class %q not found'):format(extendsName)) + end + if type(init) ~= 'nil' and type(init) ~= 'function' then + M._errorHandler(('init must be nil or function')) + end + if not self.extendsMap[extendsName] then + self.extendsMap[extendsName] = true + for k, v in pairs(extends) do + if not class[k] and not k:match '^__' then + class[k] = v + end + end + for k, v in pairs(extends.__getter) do + if not class.__getter[k] then + class.__getter[k] = v + end + end + end + table.insert(self.extendsCalls, { + init = init, + name = extendsName, + }) + -- 检查是否需要显性初始化 + if not init then + if not extends.__init then + return + end + local info = debug.getinfo(extends.__init, 'u') + if info.nparams <= 1 then + return + end + M._errorHandler(('must call super for extends "%s"'):format(extendsName)) + end +end + +return M diff --git a/src/parser.old/compile.lua b/src/parser.old/compile.lua new file mode 100644 index 0000000..1c7d6a0 --- /dev/null +++ b/src/parser.old/compile.lua @@ -0,0 +1,3989 @@ +local tokens = require 'parser.tokens' +local guide = require 'parser.guide' + +local sbyte = string.byte +local sfind = string.find +local smatch = string.match +local sgsub = string.gsub +local ssub = string.sub +local schar = string.char +local supper = string.upper +local uchar = utf8.char +local tconcat = table.concat +local tinsert = table.insert +local tointeger = math.tointeger +local tonumber = tonumber +local maxinteger = math.maxinteger +local assert = assert + +_ENV = nil + +---@alias parser.position integer + +---@param str string +---@return table +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, + ['os.exit'] = 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, LocalLimited + +local LocalLimit = 200 + +local parseExp, parseAction + +local pushError + +local function addSpecial(name, obj) + if not State.specials then + State.specials = {} + end + if not State.specials[name] then + State.specials[name] = {} + end + State.specials[name][#State.specials[name]+1] = obj + obj.special = name +end + +---@param offset integer +---@param leftOrRight '"left"'|'"right"' +local function getPosition(offset, leftOrRight) + if not offset or offset > #Lua then + return LineMulti * Line + #Lua - LineOffset + 1 + end + if leftOrRight == 'left' then + return LineMulti * Line + offset - LineOffset + else + return LineMulti * Line + offset - LineOffset + 1 + end +end + +---@return string? word +---@return parser.position? startPosition +---@return parser.position? finishPosition +local function peekWord() + local word = Tokens[Index + 1] + if not word then + return nil + end + if not CharMapWord[ssub(word, 1, 1)] then + return nil + end + local startPos = getPosition(Tokens[Index] , 'left') + local finishPos = getPosition(Tokens[Index] + #word - 1, 'right') + return word, startPos, finishPos +end + +local function lastRightPosition() + if Index < 2 then + return 0 + end + local token = Tokens[Index - 1] + if NLMap[token] then + return LastTokenFinish + elseif token then + return getPosition(Tokens[Index - 2] + #token - 1, 'right') + else + return getPosition(#Lua, 'right') + end +end + +local function missSymbol(symbol, start, finish) + pushError { + type = 'MISS_SYMBOL', + start = start or lastRightPosition(), + finish = finish or start or lastRightPosition(), + info = { + symbol = symbol, + } + } +end + +local function missExp() + pushError { + type = 'MISS_EXP', + start = lastRightPosition(), + finish = lastRightPosition(), + } +end + +local function missName(pos) + pushError { + type = 'MISS_NAME', + start = pos or lastRightPosition(), + finish = pos or lastRightPosition(), + } +end + +local function missEnd(relatedStart, relatedFinish) + pushError { + type = 'MISS_SYMBOL', + start = lastRightPosition(), + finish = lastRightPosition(), + info = { + symbol = 'end', + related = { + { + start = relatedStart, + finish = relatedFinish, + } + } + } + } + pushError { + type = 'MISS_END', + start = relatedStart, + finish = relatedFinish, + } +end + +local function unknownSymbol(start, finish, word) + local token = word or Tokens[Index + 1] + if not token then + return false + end + pushError { + type = 'UNKNOWN_SYMBOL', + start = start or getPosition(Tokens[Index], 'left'), + finish = finish or getPosition(Tokens[Index] + #token - 1, 'right'), + info = { + symbol = token, + } + } + return true +end + +local function skipUnknownSymbol(stopSymbol) + if unknownSymbol() then + Index = Index + 2 + return true + end + return false +end + +local function skipNL() + local token = Tokens[Index + 1] + if NLMap[token] then + if Index >= 2 and not NLMap[Tokens[Index - 1]] then + LastTokenFinish = getPosition(Tokens[Index - 2] + #Tokens[Index - 1] - 1, 'right') + end + Line = Line + 1 + LineOffset = Tokens[Index] + #token + Index = Index + 2 + State.lines[Line] = LineOffset + return true + end + return false +end + +local function getSavePoint() + local index = Index + local line = Line + local lineOffset = LineOffset + local errs = State.errs + local errCount = #errs + return function () + Index = index + Line = line + LineOffset = lineOffset + for i = errCount + 1, #errs do + errs[i] = nil + end + end +end + +local function fastForwardToken(offset) + while true do + local myOffset = Tokens[Index] + if not myOffset + or myOffset >= offset then + break + end + local token = Tokens[Index + 1] + if NLMap[token] then + Line = Line + 1 + LineOffset = Tokens[Index] + #token + State.lines[Line] = LineOffset + end + Index = Index + 2 + end +end + +local function resolveLongString(finishMark) + skipNL() + local miss + local start = Tokens[Index] + local finishOffset = sfind(Lua, finishMark, start, true) + if not finishOffset then + finishOffset = #Lua + 1 + miss = true + end + local stringResult = start and ssub(Lua, start, finishOffset - 1) or '' + local lastLN = stringResult:find '[\r\n][^\r\n]*$' + if lastLN then + local result = stringResult + : gsub('\r\n?', '\n') + stringResult = result + end + if finishMark == ']]' and State.version == 'Lua 5.1' then + local nestOffset = sfind(Lua, '[[', start, true) + if nestOffset and nestOffset < finishOffset then + fastForwardToken(nestOffset) + local nestStartPos = getPosition(nestOffset, 'left') + local nestFinishPos = getPosition(nestOffset + 1, 'right') + pushError { + type = 'NESTING_LONG_MARK', + start = nestStartPos, + finish = nestFinishPos, + } + end + 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 start then + return nil + end + fastForwardToken(finish + 1) + local startPos = getPosition(start, 'left') + local finishMark = sgsub(mark, '%[', ']') + local stringResult, finishPos = resolveLongString(finishMark) + return { + type = 'string', + start = startPos, + finish = finishPos, + [1] = stringResult, + [2] = mark, + } +end + +local function pushCommentHeadError(left) + if State.options.nonstandardSymbol['//'] then + return + end + pushError { + type = 'ERR_COMMENT_PREFIX', + start = left, + finish = left + 2, + fix = { + title = 'FIX_COMMENT_PREFIX', + { + start = left, + finish = left + 2, + text = '--', + }, + } + } +end + +local function pushLongCommentError(left, right) + if State.options.nonstandardSymbol['/**/'] then + return + end + pushError { + type = 'ERR_C_LONG_COMMENT', + start = left, + finish = right, + fix = { + title = 'FIX_C_LONG_COMMENT', + { + start = left, + finish = left + 2, + text = '--[[', + }, + { + start = right - 2, + finish = right, + text = '--]]' + }, + } + } +end + +local function skipComment(isAction) + local token = Tokens[Index + 1] + if token == '--' + or ( + token == '//' + and ( + isAction + or State.options.nonstandardSymbol['//'] + ) + ) then + local start = Tokens[Index] + local left = getPosition(start, 'left') + local chead = false + if token == '//' then + chead = true + pushCommentHeadError(left) + end + Index = Index + 2 + local longComment = start + 2 == Tokens[Index] and parseLongString() + if longComment then + longComment.type = 'comment.long' + longComment.text = longComment[1] + longComment.mark = longComment[2] + longComment[1] = nil + longComment[2] = nil + State.comms[#State.comms+1] = longComment + return true + end + while true do + local nl = Tokens[Index + 1] + if not nl or NLMap[nl] then + break + end + Index = Index + 2 + end + local right = Tokens[Index] and (Tokens[Index] - 1) or #Lua + State.comms[#State.comms+1] = { + type = chead and 'comment.cshort' or 'comment.short', + start = left, + finish = getPosition(right, 'right'), + text = ssub(Lua, start + 2, right), + } + return true + end + if token == '/*' then + local start = Tokens[Index] + local left = getPosition(start, 'left') + Index = Index + 2 + local result, right = resolveLongString '*/' + pushLongCommentError(left, right) + State.comms[#State.comms+1] = { + type = 'comment.long', + start = left, + finish = right, + text = result, + } + return true + end + return false +end + +local function skipSpace(isAction) + repeat until not skipNL() + and not skipComment(isAction) +end + +local function expectAssign(isAction) + local token = Tokens[Index + 1] + if token == '=' then + Index = Index + 2 + return true + end + if token == '==' then + local left = getPosition(Tokens[Index], 'left') + local right = getPosition(Tokens[Index] + #token - 1, 'right') + pushError { + type = 'ERR_ASSIGN_AS_EQ', + start = left, + finish = right, + fix = { + title = 'FIX_ASSIGN_AS_EQ', + { + start = left, + finish = right, + text = '=', + } + } + } + Index = Index + 2 + return true + end + if isAction then + if token == '+=' + or token == '-=' + or token == '*=' + or token == '/=' + or token == '%=' + or token == '^=' + or token == '//=' + or token == '|=' + or token == '&=' + or token == '>>=' + or token == '<<=' then + if not State.options.nonstandardSymbol[token] then + unknownSymbol() + end + Index = Index + 2 + return true + end + end + return false +end + +local function parseLocalAttrs() + local attrs + while true do + skipSpace() + local token = Tokens[Index + 1] + if token ~= '<' then + break + end + if not attrs then + attrs = { + type = 'localattrs', + } + end + local attr = { + type = 'localattr', + parent = attrs, + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index], 'right'), + } + attrs[#attrs+1] = attr + Index = Index + 2 + skipSpace() + local word, wstart, wfinish = peekWord() + if word then + attr[1] = word + attr.finish = wfinish + Index = Index + 2 + if word ~= 'const' + and word ~= 'close' then + pushError { + type = 'UNKNOWN_ATTRIBUTE', + start = wstart, + finish = wfinish, + } + end + else + missName() + end + attr.finish = lastRightPosition() + skipSpace() + if Tokens[Index + 1] == '>' then + attr.finish = getPosition(Tokens[Index], 'right') + Index = Index + 2 + elseif Tokens[Index + 1] == '>=' then + attr.finish = getPosition(Tokens[Index], 'right') + pushError { + type = 'MISS_SPACE_BETWEEN', + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + 1, 'right'), + } + Index = Index + 2 + else + missSymbol '>' + end + if State.version ~= 'Lua 5.4' then + pushError { + type = 'UNSUPPORT_SYMBOL', + start = attr.start, + finish = attr.finish, + version = 'Lua 5.4', + info = { + version = State.version + } + } + end + end + return attrs +end + +local function createLocal(obj, attrs) + obj.type = 'local' + obj.effect = obj.finish + + if attrs then + obj.attrs = attrs + attrs.parent = obj + end + + local chunk = Chunk[#Chunk] + if chunk then + local locals = chunk.locals + if not locals then + locals = {} + chunk.locals = locals + end + locals[#locals+1] = obj + LocalCount = LocalCount + 1 + if not LocalLimited and LocalCount > LocalLimit then + LocalLimited = true + pushError { + type = 'LOCAL_LIMIT', + start = obj.start, + finish = obj.finish, + } + end + end + return obj +end + +local function pushChunk(chunk) + Chunk[#Chunk+1] = chunk +end + +local function resolveLable(label, obj) + if not label.ref then + label.ref = {} + end + label.ref[#label.ref+1] = obj + obj.node = label + + -- 如果有局部变量在 goto 与 label 之间声明, + -- 并在 label 之后使用,则算作语法错误 + + -- 如果 label 在 goto 之前声明,那么不会有中间声明的局部变量 + if obj.start > label.start then + return + end + + local block = guide.getBlock(obj) + local locals = block and block.locals + if not locals then + return + end + + for i = 1, #locals do + local loc = locals[i] + -- 检查局部变量声明位置为 goto 与 label 之间 + if loc.start < obj.start or loc.finish > label.finish then + goto CONTINUE + end + -- 检查局部变量的使用位置在 label 之后 + local refs = loc.ref + if not refs then + goto CONTINUE + end + for j = 1, #refs do + local ref = refs[j] + if ref.finish > label.finish then + pushError { + type = 'JUMP_LOCAL_SCOPE', + start = obj.start, + finish = obj.finish, + info = { + loc = loc[1], + }, + relative = { + { + start = label.start, + finish = label.finish, + }, + { + start = loc.start, + finish = loc.finish, + } + }, + } + return + end + end + ::CONTINUE:: + end +end + +local function resolveGoTo(gotos) + for i = 1, #gotos do + local action = gotos[i] + local label = guide.getLabel(action, action[1]) + if label then + resolveLable(label, action) + else + pushError { + type = 'NO_VISIBLE_LABEL', + start = action.start, + finish = action.finish, + info = { + label = action[1], + } + } + end + end +end + +local function popChunk() + local chunk = Chunk[#Chunk] + if chunk.gotos then + resolveGoTo(chunk.gotos) + chunk.gotos = nil + end + local lastAction = chunk[#chunk] + if lastAction then + chunk.finish = lastAction.finish + end + Chunk[#Chunk] = nil +end + +local function parseNil() + if Tokens[Index + 1] ~= 'nil' then + return nil + end + local offset = Tokens[Index] + Index = Index + 2 + return { + type = 'nil', + start = getPosition(offset, 'left'), + finish = getPosition(offset + 2, 'right'), + } +end + +local function parseBoolean() + local word = Tokens[Index+1] + if word ~= 'true' + and word ~= 'false' then + return nil + end + local start = getPosition(Tokens[Index], 'left') + local finish = getPosition(Tokens[Index] + #word - 1, 'right') + Index = Index + 2 + return { + type = 'boolean', + start = start, + finish = finish, + [1] = word == 'true' and true or false, + } +end + +local function parseStringUnicode() + local offset = Tokens[Index] + 1 + if ssub(Lua, offset, offset) ~= '{' then + local pos = getPosition(offset, 'left') + missSymbol('{', pos) + return nil, offset + end + local leftPos = getPosition(offset, 'left') + local x16 = smatch(Lua, '^%w*', offset + 1) + local rightPos = getPosition(offset + #x16, 'right') + offset = offset + #x16 + 1 + if ssub(Lua, offset, offset) == '}' then + offset = offset + 1 + rightPos = rightPos + 1 + else + missSymbol('}', rightPos) + end + offset = offset + 1 + if #x16 == 0 then + pushError { + type = 'UTF8_SMALL', + start = leftPos, + finish = rightPos, + } + return '', offset + end + if State.version ~= 'Lua 5.3' + and State.version ~= 'Lua 5.4' + and State.version ~= 'LuaJIT' + then + pushError { + type = 'ERR_ESC', + start = leftPos - 2, + finish = rightPos, + version = {'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, + info = { + version = State.version, + } + } + return nil, offset + end + local byte = tonumber(x16, 16) + if not byte then + for i = 1, #x16 do + if not tonumber(ssub(x16, i, i), 16) then + pushError { + type = 'MUST_X16', + start = leftPos + i, + finish = leftPos + i + 1, + } + end + end + return nil, offset + end + if State.version == 'Lua 5.4' then + if byte < 0 or byte > 0x7FFFFFFF then + pushError { + type = 'UTF8_MAX', + start = leftPos, + finish = rightPos, + info = { + min = '00000000', + max = '7FFFFFFF', + } + } + return nil, offset + end + else + if byte < 0 or byte > 0x10FFFF then + pushError { + type = 'UTF8_MAX', + start = leftPos, + finish = rightPos, + version = byte <= 0x7FFFFFFF and 'Lua 5.4' or nil, + info = { + min = '000000', + max = '10FFFF', + } + } + end + end + if byte >= 0 and byte <= 0x10FFFF then + return uchar(byte), offset + end + return '', offset +end + +local stringPool = {} +local function parseShortString() + local mark = Tokens[Index+1] + local startOffset = Tokens[Index] + local startPos = getPosition(startOffset, 'left') + Index = Index + 2 + local stringIndex = 0 + local currentOffset = startOffset + 1 + local escs = {} + while true do + local token = Tokens[Index + 1] + if token == mark then + stringIndex = stringIndex + 1 + stringPool[stringIndex] = ssub(Lua, currentOffset, Tokens[Index] - 1) + Index = Index + 2 + break + end + if NLMap[token] then + stringIndex = stringIndex + 1 + stringPool[stringIndex] = ssub(Lua, currentOffset, Tokens[Index] - 1) + missSymbol(mark) + break + end + if not token then + stringIndex = stringIndex + 1 + stringPool[stringIndex] = ssub(Lua, currentOffset or -1) + missSymbol(mark) + break + end + if token == '\\' then + stringIndex = stringIndex + 1 + stringPool[stringIndex] = ssub(Lua, currentOffset, Tokens[Index] - 1) + currentOffset = Tokens[Index] + Index = Index + 2 + if not Tokens[Index] then + goto CONTINUE + end + local escLeft = getPosition(currentOffset, 'left') + -- has space? + if Tokens[Index] - currentOffset > 1 then + local right = getPosition(currentOffset + 1, 'right') + pushError { + type = 'ERR_ESC', + start = escLeft, + finish = right, + } + escs[#escs+1] = escLeft + escs[#escs+1] = right + escs[#escs+1] = 'err' + goto CONTINUE + end + local nextToken = ssub(Tokens[Index + 1], 1, 1) + if EscMap[nextToken] then + stringIndex = stringIndex + 1 + stringPool[stringIndex] = EscMap[nextToken] + currentOffset = Tokens[Index] + #nextToken + Index = Index + 2 + escs[#escs+1] = escLeft + escs[#escs+1] = escLeft + 2 + escs[#escs+1] = 'normal' + goto CONTINUE + end + if nextToken == mark then + stringIndex = stringIndex + 1 + stringPool[stringIndex] = mark + currentOffset = Tokens[Index] + #nextToken + Index = Index + 2 + escs[#escs+1] = escLeft + escs[#escs+1] = escLeft + 2 + escs[#escs+1] = 'normal' + goto CONTINUE + end + if nextToken == 'z' then + Index = Index + 2 + repeat until not skipNL() + currentOffset = Tokens[Index] + escs[#escs+1] = escLeft + escs[#escs+1] = escLeft + 2 + escs[#escs+1] = 'normal' + goto CONTINUE + end + if CharMapNumber[nextToken] then + local numbers = smatch(Tokens[Index + 1], '^%d+') + if #numbers > 3 then + numbers = ssub(numbers, 1, 3) + end + currentOffset = Tokens[Index] + #numbers + fastForwardToken(currentOffset) + local right = getPosition(currentOffset - 1, 'right') + local byte = tointeger(numbers) + if byte and byte <= 255 then + stringIndex = stringIndex + 1 + stringPool[stringIndex] = schar(byte) + else + pushError { + type = 'ERR_ESC', + start = escLeft, + finish = right, + } + end + escs[#escs+1] = escLeft + escs[#escs+1] = right + escs[#escs+1] = 'byte' + goto CONTINUE + end + if nextToken == 'x' then + local left = getPosition(Tokens[Index] - 1, 'left') + local x16 = ssub(Tokens[Index + 1], 2, 3) + local byte = tonumber(x16, 16) + if byte then + currentOffset = Tokens[Index] + 3 + stringIndex = stringIndex + 1 + stringPool[stringIndex] = schar(byte) + else + currentOffset = Tokens[Index] + 1 + pushError { + type = 'MISS_ESC_X', + start = getPosition(currentOffset, 'left'), + finish = getPosition(currentOffset + 1, 'right'), + } + end + local right = getPosition(currentOffset + 1, 'right') + escs[#escs+1] = escLeft + escs[#escs+1] = right + escs[#escs+1] = 'byte' + if State.version == 'Lua 5.1' then + pushError { + type = 'ERR_ESC', + start = left, + finish = left + 4, + version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, + info = { + version = State.version, + } + } + end + Index = Index + 2 + goto CONTINUE + end + if nextToken == 'u' then + local str, newOffset = parseStringUnicode() + if str then + stringIndex = stringIndex + 1 + stringPool[stringIndex] = str + end + currentOffset = newOffset + fastForwardToken(currentOffset - 1) + local right = getPosition(currentOffset + 1, 'right') + escs[#escs+1] = escLeft + escs[#escs+1] = right + escs[#escs+1] = 'unicode' + goto CONTINUE + end + if NLMap[nextToken] then + stringIndex = stringIndex + 1 + stringPool[stringIndex] = '\n' + currentOffset = Tokens[Index] + #nextToken + skipNL() + local right = getPosition(currentOffset + 1, 'right') + escs[#escs+1] = escLeft + escs[#escs+1] = escLeft + 1 + escs[#escs+1] = 'normal' + goto CONTINUE + end + local right = getPosition(currentOffset + 1, 'right') + pushError { + type = 'ERR_ESC', + start = escLeft, + finish = right, + } + escs[#escs+1] = escLeft + escs[#escs+1] = right + escs[#escs+1] = 'err' + end + Index = Index + 2 + ::CONTINUE:: + end + local stringResult = tconcat(stringPool, '', 1, stringIndex) + local str = { + type = 'string', + start = startPos, + finish = lastRightPosition(), + escs = #escs > 0 and escs or nil, + [1] = stringResult, + [2] = mark, + } + if mark == '`' then + if not State.options.nonstandardSymbol[mark] then + pushError { + type = 'ERR_NONSTANDARD_SYMBOL', + start = startPos, + finish = str.finish, + info = { + symbol = '"', + }, + fix = { + title = 'FIX_NONSTANDARD_SYMBOL', + symbol = '"', + { + start = startPos, + finish = startPos + 1, + text = '"', + }, + { + start = str.finish - 1, + finish = str.finish, + text = '"', + }, + } + } + end + end + return str +end + +local function parseString() + local c = Tokens[Index + 1] + if CharMapStrSH[c] then + return parseShortString() + end + if CharMapStrLH[c] then + return parseLongString() + end + return nil +end + +local function parseNumber10(start) + local integer = true + local integerPart = smatch(Lua, '^%d*', start) + local offset = start + #integerPart + -- float part + if ssub(Lua, offset, offset) == '.' then + local floatPart = smatch(Lua, '^%d*', offset + 1) + integer = false + offset = offset + #floatPart + 1 + end + -- exp part + local echar = ssub(Lua, offset, offset) + if CharMapE10[echar] then + integer = false + offset = offset + 1 + local nextChar = ssub(Lua, offset, offset) + if CharMapSign[nextChar] then + offset = offset + 1 + end + local exp = smatch(Lua, '^%d*', offset) + offset = offset + #exp + if #exp == 0 then + pushError { + type = 'MISS_EXPONENT', + start = getPosition(offset - 1, 'right'), + finish = getPosition(offset - 1, 'right'), + } + end + end + return tonumber(ssub(Lua, start, offset - 1)), offset, integer +end + +local function parseNumber16(start) + local integerPart = smatch(Lua, '^[%da-fA-F]*', start) + local offset = start + #integerPart + local integer = true + -- float part + if ssub(Lua, offset, offset) == '.' then + local floatPart = smatch(Lua, '^[%da-fA-F]*', offset + 1) + integer = false + offset = offset + #floatPart + 1 + if #integerPart == 0 and #floatPart == 0 then + pushError { + type = 'MUST_X16', + start = getPosition(offset - 1, 'right'), + finish = getPosition(offset - 1, 'right'), + } + end + else + if #integerPart == 0 then + pushError { + type = 'MUST_X16', + start = getPosition(offset - 1, 'right'), + finish = getPosition(offset - 1, 'right'), + } + return 0, offset + end + end + -- exp part + local echar = ssub(Lua, offset, offset) + if CharMapE16[echar] then + integer = false + offset = offset + 1 + local nextChar = ssub(Lua, offset, offset) + if CharMapSign[nextChar] then + offset = offset + 1 + end + local exp = smatch(Lua, '^%d*', offset) + offset = offset + #exp + end + local n = tonumber(ssub(Lua, start - 2, offset - 1)) + return n, offset, integer +end + +local function parseNumber2(start) + local bins = smatch(Lua, '^[01]*', start) + local offset = start + #bins + if State.version ~= 'LuaJIT' then + pushError { + type = 'UNSUPPORT_SYMBOL', + start = getPosition(start - 2, 'left'), + finish = getPosition(offset - 1, 'right'), + version = 'LuaJIT', + info = { + version = 'Lua 5.4', + } + } + end + return tonumber(bins, 2), offset +end + +local function dropNumberTail(offset, integer) + local _, finish, word = sfind(Lua, '^([%.%w_\x80-\xff]+)', offset) + if not finish then + return offset + end + if integer then + if supper(ssub(word, 1, 2)) == 'LL' then + if State.version ~= 'LuaJIT' then + pushError { + type = 'UNSUPPORT_SYMBOL', + start = getPosition(offset, 'left'), + finish = getPosition(offset + 1, 'right'), + version = 'LuaJIT', + info = { + version = State.version, + } + } + end + offset = offset + 2 + word = ssub(word, offset) + elseif supper(ssub(word, 1, 3)) == 'ULL' then + if State.version ~= 'LuaJIT' then + pushError { + type = 'UNSUPPORT_SYMBOL', + start = getPosition(offset, 'left'), + finish = getPosition(offset + 2, 'right'), + version = 'LuaJIT', + info = { + version = State.version, + } + } + end + offset = offset + 3 + word = ssub(word, offset) + end + end + if supper(ssub(word, 1, 1)) == 'I' then + if State.version ~= 'LuaJIT' then + pushError { + type = 'UNSUPPORT_SYMBOL', + start = getPosition(offset, 'left'), + finish = getPosition(offset, 'right'), + version = 'LuaJIT', + info = { + version = State.version, + } + } + end + offset = offset + 1 + word = ssub(word, offset) + end + if #word > 0 then + pushError { + type = 'MALFORMED_NUMBER', + start = getPosition(offset, 'left'), + finish = getPosition(finish, 'right'), + } + end + return finish + 1 +end + +local function parseNumber() + local offset = Tokens[Index] + if not offset then + return nil + end + local startPos = getPosition(offset, 'left') + local neg + if ssub(Lua, offset, offset) == '-' then + neg = true + offset = offset + 1 + end + local number, integer + local firstChar = ssub(Lua, offset, offset) + if firstChar == '.' then + number, offset = parseNumber10(offset) + integer = false + elseif firstChar == '0' then + local nextChar = ssub(Lua, offset + 1, offset + 1) + if CharMapN16[nextChar] then + number, offset, integer = parseNumber16(offset + 2) + elseif CharMapN2[nextChar] then + number, offset = parseNumber2(offset + 2) + integer = true + else + number, offset, integer = parseNumber10(offset) + end + elseif CharMapNumber[firstChar] then + number, offset, integer = parseNumber10(offset) + else + return nil + end + if not number then + number = 0 + end + if neg then + number = - number + end + local result = { + type = integer and 'integer' or 'number', + start = startPos, + finish = getPosition(offset - 1, 'right'), + [1] = number, + } + offset = dropNumberTail(offset, integer) + fastForwardToken(offset) + return result +end + +local function isKeyWord(word, nextToken) + if KeyWord[word] then + return true + end + if word == 'goto' then + if State.version == 'Lua 5.1' then + return false + end + if State.version == 'LuaJIT' then + if not nextToken then + return false + end + if CharMapWord[ssub(nextToken, 1, 1)] then + return true + end + return false + end + return true + 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, Tokens[Index + 1]) then + pushError { + type = 'KEYWORD', + start = startPos, + finish = finishPos, + } + end + return { + type = 'name', + start = startPos, + finish = finishPos, + [1] = word, + } +end + +local function parseNameOrList(parent) + local first = parseName() + if not first then + return nil + end + skipSpace() + local list + while true do + if Tokens[Index + 1] ~= ',' then + break + end + Index = Index + 2 + skipSpace() + local name = parseName(true) + if not name then + missName() + break + end + if not list then + list = { + type = 'list', + start = first.start, + finish = first.finish, + parent = parent, + [1] = first + } + end + list[#list+1] = name + list.finish = name.finish + end + return list or first +end + +local function parseExpList(mini) + local list + local wantSep = false + while true do + skipSpace() + local token = Tokens[Index + 1] + if not token then + break + end + if ListFinishMap[token] then + break + end + if token == ',' then + local sepPos = getPosition(Tokens[Index], 'right') + if not wantSep then + pushError { + type = 'UNEXPECT_SYMBOL', + start = getPosition(Tokens[Index], 'left'), + finish = sepPos, + info = { + symbol = ',', + } + } + end + wantSep = false + Index = Index + 2 + goto CONTINUE + else + if mini then + if wantSep then + break + end + local nextToken = peekWord() + if isKeyWord(nextToken, Tokens[Index + 2]) + and nextToken ~= 'function' + and nextToken ~= 'true' + and nextToken ~= 'false' + and nextToken ~= 'nil' + and nextToken ~= 'not' then + break + end + end + local exp = parseExp() + if not exp then + break + end + if wantSep then + missSymbol(',', list[#list].finish, exp.start) + end + wantSep = true + if not list then + list = { + type = 'list', + start = exp.start, + } + end + list[#list+1] = exp + list.finish = exp.finish + exp.parent = list + end + ::CONTINUE:: + end + if not list then + return nil + end + if not wantSep then + missExp() + end + return list +end + +local function parseIndex() + local start = getPosition(Tokens[Index], 'left') + Index = Index + 2 + skipSpace() + local exp = parseExp() + local index = { + type = 'index', + start = start, + finish = exp and exp.finish or (start + 1), + index = exp + } + if exp then + exp.parent = index + else + missExp() + end + skipSpace() + if Tokens[Index + 1] == ']' then + index.finish = getPosition(Tokens[Index], 'right') + Index = Index + 2 + else + missSymbol ']' + end + return index +end + +local function parseTable() + local tbl = { + type = 'table', + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index], 'right'), + } + Index = Index + 2 + local index = 0 + local tindex = 0 + local wantSep = false + while true do + skipSpace(true) + local token = Tokens[Index + 1] + if token == '}' then + Index = Index + 2 + break + end + if CharMapTSep[token] then + if not wantSep then + missExp() + end + wantSep = false + Index = Index + 2 + goto CONTINUE + end + local lastRight = lastRightPosition() + + if peekWord() then + local savePoint = getSavePoint() + local name = parseName() + if name then + skipSpace() + if Tokens[Index + 1] == '=' then + Index = Index + 2 + if wantSep then + pushError { + type = 'MISS_SEP_IN_TABLE', + start = lastRight, + finish = getPosition(Tokens[Index], 'left'), + } + end + wantSep = true + skipSpace() + local fvalue = parseExp() + local tfield = { + type = 'tablefield', + start = name.start, + finish = name.finish, + range = fvalue and fvalue.finish, + node = tbl, + parent = tbl, + field = name, + value = fvalue, + } + name.type = 'field' + name.parent = tfield + if fvalue then + fvalue.parent = tfield + else + missExp() + end + index = index + 1 + tbl[index] = tfield + goto CONTINUE + end + end + savePoint() + end + + local exp = parseExp(true) + if exp then + if wantSep then + pushError { + type = 'MISS_SEP_IN_TABLE', + start = lastRight, + finish = exp.start, + } + end + wantSep = true + if exp.type == 'varargs' then + index = index + 1 + tbl[index] = exp + exp.parent = tbl + goto CONTINUE + end + index = index + 1 + tindex = tindex + 1 + local texp = { + type = 'tableexp', + start = exp.start, + finish = exp.finish, + tindex = tindex, + parent = tbl, + value = exp, + } + exp.parent = texp + tbl[index] = texp + goto CONTINUE + end + + if token == '[' then + if wantSep then + pushError { + type = 'MISS_SEP_IN_TABLE', + start = lastRight, + finish = getPosition(Tokens[Index], 'left'), + } + end + wantSep = true + local tindex = parseIndex() + skipSpace() + tindex.type = 'tableindex' + tindex.node = tbl + tindex.parent = tbl + index = index + 1 + tbl[index] = tindex + if expectAssign() then + skipSpace() + local ivalue = parseExp() + if ivalue then + ivalue.parent = tindex + tindex.range = ivalue.finish + tindex.value = ivalue + else + missExp() + end + else + missSymbol '=' + end + goto CONTINUE + end + + missSymbol '}' + break + ::CONTINUE:: + end + tbl.finish = lastRightPosition() + return tbl +end + +local function addDummySelf(node, call) + if node.type ~= 'getmethod' then + return + end + -- dummy param `self` + if not call.args then + call.args = { + type = 'callargs', + start = call.start, + finish = call.finish, + parent = call, + } + end + local self = { + type = 'self', + start = node.colon.start, + finish = node.colon.finish, + parent = call.args, + [1] = 'self', + } + tinsert(call.args, 1, self) +end + +local function checkAmbiguityCall(call, parenPos) + if State.version ~= 'Lua 5.1' then + return + end + local node = call.node + local nodeRow = guide.rowColOf(node.finish) + local callRow = guide.rowColOf(parenPos) + if nodeRow == callRow then + return + end + pushError { + type = 'AMBIGUOUS_SYNTAX', + start = parenPos, + finish = call.finish, + } +end + +local function bindSpecial(source, name) + if Specials[name] then + addSpecial(name, source) + else + local ospeicals = State.options.special + if ospeicals and ospeicals[name] then + addSpecial(ospeicals[name], source) + end + end +end + +local function parseSimple(node, funcName) + local currentName + if node.type == 'getglobal' + or node.type == 'getlocal' then + currentName = node[1] + end + local lastMethod + while true do + if lastMethod and node.node == lastMethod then + if node.type ~= 'call' then + missSymbol('(', node.node.finish, node.node.finish) + end + lastMethod = nil + end + skipSpace() + local token = Tokens[Index + 1] + if token == '.' then + local dot = { + type = token, + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index], 'right'), + } + Index = Index + 2 + skipSpace() + local field = parseName(true) + local getfield = { + type = 'getfield', + start = node.start, + finish = lastRightPosition(), + node = node, + dot = dot, + field = field + } + if field then + field.parent = getfield + field.type = 'field' + if currentName then + if node.type == 'getlocal' + or node.type == 'getglobal' + or node.type == 'getfield' then + currentName = currentName .. '.' .. field[1] + bindSpecial(getfield, currentName) + else + currentName = nil + end + end + else + pushError { + type = 'MISS_FIELD', + start = lastRightPosition(), + finish = lastRightPosition(), + } + end + node.parent = getfield + node.next = getfield + node = getfield + elseif token == ':' then + local colon = { + type = token, + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index], 'right'), + } + Index = Index + 2 + skipSpace() + local method = parseName(true) + local getmethod = { + type = 'getmethod', + start = node.start, + finish = lastRightPosition(), + node = node, + colon = colon, + method = method + } + if method then + method.parent = getmethod + method.type = 'method' + else + pushError { + type = 'MISS_METHOD', + start = lastRightPosition(), + finish = lastRightPosition(), + } + end + node.parent = getmethod + node.next = getmethod + node = getmethod + if lastMethod then + missSymbol('(', node.node.finish, node.node.finish) + end + lastMethod = getmethod + elseif token == '(' then + if funcName then + break + end + local startPos = getPosition(Tokens[Index], 'left') + local call = { + type = 'call', + start = node.start, + node = node, + } + Index = Index + 2 + local args = parseExpList() + if Tokens[Index + 1] == ')' then + call.finish = getPosition(Tokens[Index], 'right') + Index = Index + 2 + else + call.finish = lastRightPosition() + missSymbol ')' + end + if args then + args.type = 'callargs' + args.start = startPos + args.finish = call.finish + args.parent = call + call.args = args + end + addDummySelf(node, call) + checkAmbiguityCall(call, startPos) + node.parent = call + node = call + elseif token == '{' then + if funcName then + break + end + local tbl = parseTable() + local call = { + type = 'call', + start = node.start, + finish = tbl.finish, + node = node, + } + local args = { + type = 'callargs', + start = tbl.start, + finish = tbl.finish, + parent = call, + [1] = tbl, + } + call.args = args + addDummySelf(node, call) + tbl.parent = args + node.parent = call + node = call + elseif CharMapStrSH[token] then + if funcName then + break + end + local str = parseShortString() + local call = { + type = 'call', + start = node.start, + finish = str.finish, + node = node, + } + local args = { + type = 'callargs', + start = str.start, + finish = str.finish, + parent = call, + [1] = str, + } + call.args = args + addDummySelf(node, call) + str.parent = args + node.parent = call + node = call + elseif CharMapStrLH[token] then + local str = parseLongString() + if str then + if funcName then + break + end + local call = { + type = 'call', + start = node.start, + finish = str.finish, + node = node, + } + local args = { + type = 'callargs', + start = str.start, + finish = str.finish, + parent = call, + [1] = str, + } + call.args = args + addDummySelf(node, call) + str.parent = args + node.parent = call + node = call + else + local index = parseIndex() + local bstart = index.start + index.type = 'getindex' + index.start = node.start + index.node = node + node.next = index + node.parent = index + node = index + if funcName then + pushError { + type = 'INDEX_IN_FUNC_NAME', + start = bstart, + finish = index.finish, + } + end + end + else + break + end + end + if node.type == 'call' + and node.node == lastMethod then + lastMethod = nil + end + if node.type == 'call' then + if node.node.special == 'error' + or node.node.special == 'os.exit' then + node.hasExit = true + end + end + if node == lastMethod then + if funcName then + lastMethod = nil + end + end + if lastMethod then + missSymbol('(', lastMethod.finish) + end + return node +end + +local function parseVarargs() + local varargs = { + type = 'varargs', + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + 2, 'right'), + } + Index = Index + 2 + for i = #Chunk, 1, -1 do + local chunk = Chunk[i] + if chunk.vararg then + if not chunk.vararg.ref then + chunk.vararg.ref = {} + end + chunk.vararg.ref[#chunk.vararg.ref+1] = varargs + varargs.node = chunk.vararg + break + end + if chunk.type == 'main' then + break + end + if chunk.type == 'function' then + pushError { + type = 'UNEXPECT_DOTS', + start = varargs.start, + finish = varargs.finish, + } + break + end + end + return varargs +end + +local function parseParen() + local pl = Tokens[Index] + local paren = { + type = 'paren', + start = getPosition(pl, 'left'), + finish = getPosition(pl, 'right') + } + Index = Index + 2 + skipSpace() + local exp = parseExp() + if exp then + paren.exp = exp + paren.finish = exp.finish + exp.parent = paren + else + missExp() + end + skipSpace() + if Tokens[Index + 1] == ')' then + paren.finish = getPosition(Tokens[Index], 'right') + Index = Index + 2 + else + missSymbol ')' + end + return paren +end + +local function getLocal(name, pos) + for i = #Chunk, 1, -1 do + local chunk = Chunk[i] + local locals = chunk.locals + if locals then + local res + for n = 1, #locals do + local loc = locals[n] + if loc.effect > pos then + break + end + if loc[1] == name then + if not res or res.effect < loc.effect then + res = loc + end + end + end + if res then + return res + end + end + end +end + +local function resolveName(node) + if not node then + return nil + end + local loc = getLocal(node[1], node.start) + if loc then + node.type = 'getlocal' + node.node = loc + if not loc.ref then + loc.ref = {} + end + loc.ref[#loc.ref+1] = node + if loc.special then + addSpecial(loc.special, node) + end + else + node.type = 'getglobal' + local env = getLocal(State.ENVMode, node.start) + if env then + node.node = env + if not env.ref then + env.ref = {} + end + env.ref[#env.ref+1] = node + end + end + local name = node[1] + bindSpecial(node, name) + return node +end + +local function isChunkFinishToken(token) + local currentChunk = Chunk[#Chunk] + if not currentChunk then + return false + end + local tp = currentChunk.type + if tp == 'main' then + return false + end + if tp == 'for' + or tp == 'in' + or tp == 'loop' + or tp == 'function' then + return token == 'end' + end + if tp == 'if' + or tp == 'ifblock' + or tp == 'elseifblock' + or tp == 'elseblock' then + return token == 'then' + or token == 'end' + or token == 'else' + or token == 'elseif' + end + if tp == 'repeat' then + return token == 'until' + end + return true +end + +local function parseActions() + local rtn, last + while true do + skipSpace(true) + local token = Tokens[Index + 1] + if token == ';' then + Index = Index + 2 + goto CONTINUE + end + if ChunkFinishMap[token] + and isChunkFinishToken(token) then + break + end + local action, failed = parseAction() + if failed then + if not skipUnknownSymbol() then + break + end + end + if action then + if not rtn and action.type == 'return' then + rtn = action + end + last = action + end + ::CONTINUE:: + end + if rtn and rtn ~= last then + pushError { + type = 'ACTION_AFTER_RETURN', + start = rtn.start, + finish = rtn.finish, + } + end +end + +local function parseParams(params) + local lastSep + local hasDots + while true do + skipSpace() + local token = Tokens[Index + 1] + if not token or token == ')' then + if lastSep then + missName() + end + break + end + if token == ',' then + if lastSep or lastSep == nil then + missName() + else + lastSep = true + end + Index = Index + 2 + goto CONTINUE + end + if token == '...' then + if lastSep == false then + missSymbol ',' + end + lastSep = false + if not params then + params = {} + end + local vararg = { + type = '...', + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + 2, 'right'), + parent = params, + [1] = '...', + } + local chunk = Chunk[#Chunk] + chunk.vararg = vararg + params[#params+1] = vararg + if hasDots then + pushError { + type = 'ARGS_AFTER_DOTS', + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + 2, 'right'), + } + end + hasDots = true + Index = Index + 2 + goto CONTINUE + end + if CharMapWord[ssub(token, 1, 1)] then + if lastSep == false then + missSymbol ',' + end + lastSep = false + if not params then + params = {} + end + params[#params+1] = createLocal { + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + #token - 1, 'right'), + parent = params, + [1] = token, + } + if hasDots then + pushError { + type = 'ARGS_AFTER_DOTS', + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + #token - 1, 'right'), + } + end + if isKeyWord(token, Tokens[Index + 3]) 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, + bstart = funcRight, + keyword = { + [1] = funcLeft, + [2] = funcRight, + }, + } + Index = Index + 2 + 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 + func.bstart = 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 + local LastLocalCount = LocalCount + LocalCount = 0 + pushChunk(func) + local params + if func.name and func.name.type == 'getmethod' then + if func.name.type == 'getmethod' then + params = {} + params[1] = createLocal { + start = funcRight, + finish = funcRight, + parent = params, + [1] = 'self', + } + params[1].type = 'self' + end + end + if hasLeftParen then + params = params or {} + local parenLeft = getPosition(Tokens[Index], 'left') + Index = Index + 2 + params = parseParams(params) + params.type = 'funcargs' + params.start = parenLeft + params.finish = lastRightPosition() + params.parent = func + func.args = params + skipSpace(true) + if Tokens[Index + 1] == ')' then + local parenRight = getPosition(Tokens[Index], 'right') + func.finish = parenRight + func.bstart = parenRight + if params then + params.finish = parenRight + end + Index = Index + 2 + skipSpace(true) + else + func.finish = lastRightPosition() + func.bstart = func.finish + if params then + params.finish = func.finish + end + missSymbol ')' + end + else + missSymbol '(' + end + parseActions() + popChunk() + if Tokens[Index + 1] == 'end' then + local endLeft = getPosition(Tokens[Index], 'left') + local endRight = getPosition(Tokens[Index] + 2, 'right') + func.keyword[3] = endLeft + func.keyword[4] = endRight + func.finish = endRight + Index = Index + 2 + else + func.finish = lastRightPosition() + missEnd(funcLeft, funcRight) + end + LocalCount = LastLocalCount + return func +end + +local function checkNeedParen(source) + local token = Tokens[Index + 1] + if token ~= '.' + and token ~= ':' then + return source + end + local exp = parseSimple(source, false) + if exp == source then + return exp + end + pushError { + type = 'NEED_PAREN', + start = source.start, + finish = source.finish, + fix = { + title = 'FIX_ADD_PAREN', + { + start = source.start, + finish = source.start, + text = '(', + }, + { + start = source.finish, + finish = source.finish, + text = ')', + } + } + } + return exp +end + +local function parseExpUnit() + local token = Tokens[Index + 1] + if token == '(' then + local paren = parseParen() + return parseSimple(paren, false) + end + + if token == '...' then + local varargs = parseVarargs() + return varargs + end + + if token == '{' then + local table = parseTable() + if not table then + return nil + end + local exp = checkNeedParen(table) + return exp + end + + if CharMapStrSH[token] then + local string = parseShortString() + if not string then + return nil + end + local exp = checkNeedParen(string) + return exp + end + + if CharMapStrLH[token] then + local string = parseLongString() + if not string then + return nil + end + local exp = checkNeedParen(string) + return exp + end + + local number = parseNumber() + if number then + return number + end + + if ChunkFinishMap[token] then + return nil + end + + if token == 'nil' then + return parseNil() + end + + if token == 'true' + or token == 'false' then + return parseBoolean() + end + + if token == 'function' then + return parseFunction() + end + + local node = parseName() + if node then + return parseSimple(resolveName(node), false) + end + + return nil +end + +local function parseUnaryOP() + local token = Tokens[Index + 1] + local symbol = UnarySymbol[token] and token or UnaryAlias[token] + if not symbol then + return nil + end + local myLevel = UnarySymbol[symbol] + local op = { + type = symbol, + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + #symbol - 1, 'right'), + } + Index = Index + 2 + return op, myLevel +end + +---@param level integer # op level must greater than this level +local function parseBinaryOP(asAction, level) + local token = Tokens[Index + 1] + local symbol = (BinarySymbol[token] and token) + or BinaryAlias[token] + or (not asAction and BinaryActionAlias[token]) + if not symbol then + return nil + end + if symbol == '//' and State.options.nonstandardSymbol['//'] then + return nil + end + local myLevel = BinarySymbol[symbol] + if level and myLevel < level then + return nil + end + local op = { + type = symbol, + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + #token - 1, 'right'), + } + if not asAction then + if token == '=' then + pushError { + type = 'ERR_EQ_AS_ASSIGN', + start = op.start, + finish = op.finish, + fix = { + title = 'FIX_EQ_AS_ASSIGN', + { + start = op.start, + finish = op.finish, + text = '==', + } + } + } + end + end + if BinaryAlias[token] then + if not State.options.nonstandardSymbol[token] then + pushError { + type = 'ERR_NONSTANDARD_SYMBOL', + start = op.start, + finish = op.finish, + info = { + symbol = symbol, + }, + fix = { + title = 'FIX_NONSTANDARD_SYMBOL', + symbol = symbol, + { + start = op.start, + finish = op.finish, + text = symbol, + }, + } + } + end + end + if token == '//' + or token == '<<' + or token == '>>' then + if State.version ~= 'Lua 5.3' + and State.version ~= 'Lua 5.4' then + pushError { + type = 'UNSUPPORT_SYMBOL', + version = {'Lua 5.3', 'Lua 5.4'}, + start = op.start, + finish = op.finish, + info = { + version = State.version, + } + } + end + end + Index = Index + 2 + return op, myLevel +end + +function parseExp(asAction, level) + local exp + local uop, uopLevel = parseUnaryOP() + if uop then + skipSpace() + local child = parseExp(asAction, uopLevel) + -- 预计算负数 + if uop.type == '-' + and child + and (child.type == 'number' or child.type == 'integer') then + child.start = uop.start + child[1] = - child[1] + exp = child + else + exp = { + type = 'unary', + op = uop, + start = uop.start, + finish = child and child.finish or uop.finish, + [1] = child, + } + if child then + child.parent = exp + else + missExp() + end + end + else + exp = parseExpUnit() + if not exp then + return nil + end + end + + while true do + skipSpace() + local bop, bopLevel = parseBinaryOP(asAction, level) + if not bop then + break + end + + ::AGAIN:: + skipSpace() + local isForward = SymbolForward[bopLevel] + local child = parseExp(asAction, isForward and (bopLevel + 0.5) or bopLevel) + if not child then + if skipUnknownSymbol() then + goto AGAIN + else + missExp() + end + end + local bin = { + type = 'binary', + start = exp.start, + finish = child and child.finish or bop.finish, + op = bop, + [1] = exp, + [2] = child + } + exp.parent = bin + if child then + child.parent = bin + end + exp = bin + end + + return exp +end + +local function skipSeps() + while true do + skipSpace() + if Tokens[Index + 1] == ',' then + missExp() + Index = Index + 2 + else + break + end + end +end + +---@return parser.object? first +---@return parser.object? second +---@return parser.object[]? rest +local function parseSetValues() + skipSpace() + local first = parseExp() + if not first then + return nil + end + skipSpace() + if Tokens[Index + 1] ~= ',' then + return first + end + Index = Index + 2 + skipSeps() + local second = parseExp() + if not second then + missExp() + return first + end + skipSpace() + if Tokens[Index + 1] ~= ',' then + return first, second + end + Index = Index + 2 + skipSeps() + local third = parseExp() + if not third then + missExp() + return first, second + end + + local rest = { third } + while true do + skipSpace() + if Tokens[Index + 1] ~= ',' then + return first, second, rest + end + Index = Index + 2 + skipSeps() + local exp = parseExp() + if not exp then + missExp() + return first, second, rest + end + rest[#rest+1] = exp + end +end + +local function pushActionIntoCurrentChunk(action) + local chunk = Chunk[#Chunk] + if chunk then + chunk[#chunk+1] = action + action.parent = chunk + end +end + +---@return parser.object? second +---@return parser.object[]? rest +local function parseVarTails(parser, isLocal) + if Tokens[Index + 1] ~= ',' then + return nil + end + Index = Index + 2 + skipSpace() + local second = parser(true) + if not second then + missName() + return nil + end + if isLocal then + createLocal(second, parseLocalAttrs()) + end + skipSpace() + if Tokens[Index + 1] ~= ',' then + return second + end + Index = Index + 2 + skipSeps() + local third = parser(true) + if not third then + missName() + return second + end + if isLocal then + createLocal(third, parseLocalAttrs()) + end + local rest = { third } + while true do + skipSpace() + if Tokens[Index + 1] ~= ',' then + return second, rest + end + Index = Index + 2 + skipSeps() + local name = parser(true) + if not name then + missName() + return second, rest + end + if isLocal then + createLocal(name, parseLocalAttrs()) + end + rest[#rest+1] = name + end +end + +local function bindValue(n, v, index, lastValue, isLocal, isSet) + if isLocal then + if v and v.special then + addSpecial(v.special, n) + end + elseif isSet then + n.type = GetToSetMap[n.type] or n.type + if n.type == 'setlocal' then + local loc = n.node + if loc.attrs then + pushError { + type = 'SET_CONST', + start = n.start, + finish = n.finish, + } + end + end + end + if not v and lastValue then + if lastValue.type == 'call' + or lastValue.type == 'varargs' then + v = lastValue + if not v.extParent then + v.extParent = {} + end + end + end + if v then + if v.type == 'call' + or v.type == 'varargs' then + local select = { + type = 'select', + sindex = index, + start = v.start, + finish = v.finish, + vararg = v + } + if v.parent then + v.extParent[#v.extParent+1] = select + else + v.parent = select + end + v = select + end + n.value = v + n.range = v.finish + v.parent = n + end +end + +local function parseMultiVars(n1, parser, isLocal) + local n2, nrest = parseVarTails(parser, isLocal) + skipSpace() + local v1, v2, vrest + local isSet + local max = 1 + if expectAssign(not isLocal) then + v1, v2, vrest = parseSetValues() + isSet = true + if not v1 then + missExp() + end + end + local index = 1 + bindValue(n1, v1, index, nil, isLocal, isSet) + local lastValue = v1 + local lastVar = n1 + if n2 then + max = 2 + if not v2 then + index = 2 + end + bindValue(n2, v2, index, lastValue, isLocal, isSet) + lastValue = v2 or lastValue + lastVar = n2 + pushActionIntoCurrentChunk(n2) + end + if nrest then + for i = 1, #nrest do + local n = nrest[i] + local v = vrest and vrest[i] + max = i + 2 + if not v then + index = index + 1 + end + bindValue(n, v, index, lastValue, isLocal, isSet) + lastValue = v or lastValue + lastVar = n + pushActionIntoCurrentChunk(n) + end + end + + if isLocal then + local effect = lastValue and lastValue.finish or lastVar.finish + n1.effect = effect + if n2 then + n2.effect = effect + end + if nrest then + for i = 1, #nrest do + nrest[i].effect = effect + end + end + end + + if v2 and not n2 then + v2.redundant = { + max = max, + passed = 2, + } + pushActionIntoCurrentChunk(v2) + end + if vrest then + for i = 1, #vrest do + local v = vrest[i] + if not nrest or not nrest[i] then + v.redundant = { + max = max, + passed = i + 2, + } + pushActionIntoCurrentChunk(v) + end + end + end + + return n1, isSet +end + +local function compileExpAsAction(exp) + pushActionIntoCurrentChunk(exp) + if GetToSetMap[exp.type] then + skipSpace() + local isLocal + if exp.type == 'getlocal' and exp[1] == State.ENVMode then + exp.special = nil + -- TODO: need + 1 at the end + LocalCount = LocalCount - 1 + local loc = createLocal(exp, parseLocalAttrs()) + loc.locPos = exp.start + loc.effect = maxinteger + isLocal = true + skipSpace() + end + local action, isSet = parseMultiVars(exp, parseExp, isLocal) + if isSet + or action.type == 'getmethod' then + return action + end + end + + if exp.type == 'call' then + if exp.hasExit then + for i = #Chunk, 1, -1 do + local block = Chunk[i] + if block.type == 'ifblock' + or block.type == 'elseifblock' + or block.type == 'elseblock' + or block.type == 'function' then + block.hasExit = true + break + end + end + end + return exp + end + + if exp.type == 'binary' then + if GetToSetMap[exp[1].type] then + local op = exp.op + if op.type == '==' then + pushError { + type = 'ERR_ASSIGN_AS_EQ', + start = op.start, + finish = op.finish, + fix = { + title = 'FIX_ASSIGN_AS_EQ', + { + start = op.start, + finish = op.finish, + text = '=', + } + } + } + return + end + end + end + + pushError { + type = 'EXP_IN_ACTION', + start = exp.start, + finish = exp.finish, + } + + return exp +end + +local function parseLocal() + local locPos = getPosition(Tokens[Index], 'left') + Index = Index + 2 + skipSpace() + local word = peekWord() + if not word then + missName() + return nil + end + + if word == 'function' then + local func = parseFunction(true, true) + local name = func.name + if name then + func.name = nil + name.value = func + name.vstart = func.start + name.range = func.finish + name.locPos = locPos + func.parent = name + pushActionIntoCurrentChunk(name) + return name + else + missName(func.keyword[2]) + pushActionIntoCurrentChunk(func) + return func + end + end + + local name = parseName(true) + if not name then + missName() + return nil + end + local loc = createLocal(name, parseLocalAttrs()) + loc.locPos = locPos + loc.effect = maxinteger + pushActionIntoCurrentChunk(loc) + skipSpace() + parseMultiVars(loc, parseName, true) + + return loc +end + +local function parseDo() + local doLeft = getPosition(Tokens[Index], 'left') + local doRight = getPosition(Tokens[Index] + 1, 'right') + local obj = { + type = 'do', + start = doLeft, + finish = doRight, + bstart = doRight, + keyword = { + [1] = doLeft, + [2] = doRight, + }, + } + Index = Index + 2 + pushActionIntoCurrentChunk(obj) + pushChunk(obj) + parseActions() + popChunk() + if Tokens[Index + 1] == 'end' then + obj.finish = getPosition(Tokens[Index] + 2, 'right') + obj.keyword[3] = getPosition(Tokens[Index], 'left') + obj.keyword[4] = getPosition(Tokens[Index] + 2, 'right') + Index = Index + 2 + else + missEnd(doLeft, doRight) + end + if obj.locals then + LocalCount = LocalCount - #obj.locals + end + + return obj +end + +local function parseReturn() + local returnLeft = getPosition(Tokens[Index], 'left') + local returnRight = getPosition(Tokens[Index] + 5, 'right') + Index = Index + 2 + skipSpace() + local rtn = parseExpList(true) + if rtn then + rtn.type = 'return' + rtn.start = returnLeft + else + rtn = { + type = 'return', + start = returnLeft, + finish = returnRight, + } + end + pushActionIntoCurrentChunk(rtn) + for i = #Chunk, 1, -1 do + local block = Chunk[i] + if block.type == 'function' + or block.type == 'main' then + if not block.returns then + block.returns = {} + end + block.returns[#block.returns+1] = rtn + break + end + end + for i = #Chunk, 1, -1 do + local block = Chunk[i] + if block.type == 'ifblock' + or block.type == 'elseifblock' + or block.type == 'elseblock' + or block.type == 'function' then + block.hasReturn = true + break + end + end + + return rtn +end + +local function parseLabel() + local left = getPosition(Tokens[Index], 'left') + Index = Index + 2 + skipSpace() + local label = parseName() + skipSpace() + + if not label then + missName() + end + + if Tokens[Index + 1] == '::' then + Index = Index + 2 + else + if label then + missSymbol '::' + end + end + + if not label then + return nil + end + + label.type = 'label' + pushActionIntoCurrentChunk(label) + + local block = guide.getBlock(label) + if block then + if not block.labels then + block.labels = {} + end + local name = label[1] + local olabel = guide.getLabel(block, name) + if olabel then + if State.version == 'Lua 5.4' + or block == guide.getBlock(olabel) then + pushError { + type = 'REDEFINED_LABEL', + start = label.start, + finish = label.finish, + relative = { + { + olabel.start, + olabel.finish, + } + } + } + end + end + block.labels[name] = label + end + + if State.version == 'Lua 5.1' then + pushError { + type = 'UNSUPPORT_SYMBOL', + start = left, + finish = lastRightPosition(), + version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, + info = { + version = State.version, + } + } + return + end + return label +end + +local function parseGoTo() + local start = getPosition(Tokens[Index], 'left') + Index = Index + 2 + skipSpace() + + local action = parseName() + if not action then + missName() + return nil + end + + action.type = 'goto' + action.keyStart = start + + for i = #Chunk, 1, -1 do + local chunk = Chunk[i] + if chunk.type == 'function' + or chunk.type == 'main' then + if not chunk.gotos then + chunk.gotos = {} + end + chunk.gotos[#chunk.gotos+1] = action + break + end + end + for i = #Chunk, 1, -1 do + local chunk = Chunk[i] + if chunk.type == 'ifblock' + or chunk.type == 'elseifblock' + or chunk.type == 'elseblock' then + chunk.hasGoTo = true + break + end + end + + pushActionIntoCurrentChunk(action) + return action +end + +local function parseIfBlock(parent) + local ifLeft = getPosition(Tokens[Index], 'left') + local ifRight = getPosition(Tokens[Index] + 1, 'right') + Index = Index + 2 + local ifblock = { + type = 'ifblock', + parent = parent, + start = ifLeft, + finish = ifRight, + bstart = ifRight, + keyword = { + [1] = ifLeft, + [2] = ifRight, + } + } + skipSpace() + local filter = parseExp() + if filter then + ifblock.filter = filter + ifblock.finish = filter.finish + ifblock.bstart = ifblock.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.bstart = ifblock.finish + ifblock.keyword[3] = getPosition(Tokens[Index], 'left') + ifblock.keyword[4] = ifblock.finish + if thenToken == 'do' then + pushError { + type = 'ERR_THEN_AS_DO', + start = ifblock.keyword[3], + finish = ifblock.keyword[4], + fix = { + title = 'FIX_THEN_AS_DO', + { + start = ifblock.keyword[3], + finish = ifblock.keyword[4], + text = 'then', + } + } + } + end + Index = Index + 2 + else + missSymbol 'then' + end + pushChunk(ifblock) + parseActions() + popChunk() + ifblock.finish = getPosition(Tokens[Index], 'left') + if ifblock.locals then + LocalCount = LocalCount - #ifblock.locals + end + return ifblock +end + +local function parseElseIfBlock(parent) + local ifLeft = getPosition(Tokens[Index], 'left') + local ifRight = getPosition(Tokens[Index] + 5, 'right') + local elseifblock = { + type = 'elseifblock', + parent = parent, + start = ifLeft, + finish = ifRight, + bstart = ifRight, + keyword = { + [1] = ifLeft, + [2] = ifRight, + } + } + Index = Index + 2 + skipSpace() + local filter = parseExp() + if filter then + elseifblock.filter = filter + elseifblock.finish = filter.finish + elseifblock.bstart = elseifblock.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.bstart = elseifblock.finish + elseifblock.keyword[3] = getPosition(Tokens[Index], 'left') + elseifblock.keyword[4] = elseifblock.finish + if thenToken == 'do' then + pushError { + type = 'ERR_THEN_AS_DO', + start = elseifblock.keyword[3], + finish = elseifblock.keyword[4], + fix = { + title = 'FIX_THEN_AS_DO', + { + start = elseifblock.keyword[3], + finish = elseifblock.keyword[4], + text = 'then', + } + } + } + end + Index = Index + 2 + else + missSymbol 'then' + end + pushChunk(elseifblock) + parseActions() + popChunk() + elseifblock.finish = getPosition(Tokens[Index], 'left') + if elseifblock.locals then + LocalCount = LocalCount - #elseifblock.locals + end + return elseifblock +end + +local function parseElseBlock(parent) + local ifLeft = getPosition(Tokens[Index], 'left') + local ifRight = getPosition(Tokens[Index] + 3, 'right') + local elseblock = { + type = 'elseblock', + parent = parent, + start = ifLeft, + finish = ifRight, + bstart = ifRight, + keyword = { + [1] = ifLeft, + [2] = ifRight, + } + } + Index = Index + 2 + skipSpace() + pushChunk(elseblock) + parseActions() + popChunk() + elseblock.finish = getPosition(Tokens[Index], 'left') + if elseblock.locals then + LocalCount = LocalCount - #elseblock.locals + end + return elseblock +end + +local function parseIf() + local token = Tokens[Index + 1] + local left = getPosition(Tokens[Index], 'left') + local action = { + type = 'if', + start = left, + finish = getPosition(Tokens[Index] + #token - 1, 'right'), + } + pushActionIntoCurrentChunk(action) + if token ~= 'if' then + missSymbol('if', left, left) + end + local hasElse + while true do + local word = Tokens[Index + 1] + local child + if word == 'if' then + child = parseIfBlock(action) + elseif word == 'elseif' then + child = parseElseIfBlock(action) + elseif word == 'else' then + child = parseElseBlock(action) + end + if not child then + break + end + if hasElse then + pushError { + type = 'BLOCK_AFTER_ELSE', + start = child.start, + finish = child.finish, + } + end + if word == 'else' then + hasElse = true + end + action[#action+1] = child + action.finish = child.finish + skipSpace() + end + + if Tokens[Index + 1] == 'end' then + action.finish = getPosition(Tokens[Index] + 2, 'right') + Index = Index + 2 + else + missEnd(action[1].keyword[1], action[1].keyword[2]) + end + + return action +end + +local function parseFor() + local action = { + type = 'for', + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + 2, 'right'), + keyword = {}, + } + action.bstart = action.finish + 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() + local forStateVars + -- 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 + -- for x in ... uses 4 variables + forStateVars = 3 + LocalCount = LocalCount + forStateVars + if name then + local loc = createLocal(name) + loc.parent = action + action.finish = name.finish + action.bstart = action.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 + action.bstart = action.finish + end + local max = expList[2] + if max then + max.parent = expList + action.max = max + action.finish = max.finish + action.bstart = action.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 + action.bstart = action.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.bstart = action.finish + 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 + action.bstart = action.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 State.version == 'Lua 5.4' then + forStateVars = 4 + else + forStateVars = 3 + end + LocalCount = LocalCount + forStateVars + + 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.bstart = action.finish + 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 + if forStateVars then + LocalCount = LocalCount - forStateVars + end + + return action +end + +local function parseWhile() + local action = { + type = 'while', + start = getPosition(Tokens[Index], 'left'), + finish = getPosition(Tokens[Index] + 4, 'right'), + keyword = {}, + } + action.bstart = action.finish + 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.bstart = action.finish + 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.bstart = action.finish + 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', Tokens[Index + 3]) 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 + LocalLimited = false + Chunk = {} + Tokens = tokens(lua) + Index = 1 + ---@class parser.state + ---@field uri uri + ---@field lines integer[] + local state = { + version = version, + lua = lua, + ast = {}, + errs = {}, + comms = {}, + lines = { + [0] = 1, + size = #lua, + }, + options = options or {}, + } + if not state.options.nonstandardSymbol then + state.options.nonstandardSymbol = {} + end + State = state + if version == 'Lua 5.1' or version == 'LuaJIT' then + state.ENVMode = '@fenv' + else + state.ENVMode = '_ENV' + end + + pushError = function (err) + local errs = state.errs + if err.finish < err.start then + err.finish = err.start + end + local last = errs[#errs] + if last then + if last.start <= err.start and last.finish >= err.finish then + return + end + end + err.level = err.level or 'Error' + errs[#errs+1] = err + return err + end +end + +return function (lua, mode, version, options) + Mode = mode + initState(lua, version, options) + skipSpace() + if mode == 'Lua' then + State.ast = parseLua() + elseif mode == 'Nil' then + State.ast = parseNil() + elseif mode == 'Boolean' then + State.ast = parseBoolean() + elseif mode == 'String' then + State.ast = parseString() + elseif mode == 'Number' then + State.ast = parseNumber() + elseif mode == 'Name' then + State.ast = parseName() + elseif mode == 'Exp' then + State.ast = parseExp() + elseif mode == 'Action' then + State.ast = parseAction() + end + + if State.ast then + State.ast.state = State + end + + while true do + if Index <= #Tokens then + unknownSymbol() + Index = Index + 2 + else + break + end + end + + return State +end diff --git a/src/parser/guide.lua b/src/parser.old/guide.lua similarity index 100% rename from src/parser/guide.lua rename to src/parser.old/guide.lua diff --git a/src/parser.old/init.lua b/src/parser.old/init.lua new file mode 100644 index 0000000..bc004f7 --- /dev/null +++ b/src/parser.old/init.lua @@ -0,0 +1,8 @@ +local api = { + compile = require 'parser.compile', + lines = require 'parser.lines', + guide = require 'parser.guide', + luadoc = require 'parser.luadoc', +} + +return api diff --git a/src/parser/lines.lua b/src/parser.old/lines.lua similarity index 100% rename from src/parser/lines.lua rename to src/parser.old/lines.lua diff --git a/src/parser/luadoc.lua b/src/parser.old/luadoc.lua similarity index 100% rename from src/parser/luadoc.lua rename to src/parser.old/luadoc.lua diff --git a/src/parser/relabel.lua b/src/parser.old/relabel.lua similarity index 100% rename from src/parser/relabel.lua rename to src/parser.old/relabel.lua diff --git a/src/parser/tokens.lua b/src/parser.old/tokens.lua similarity index 100% rename from src/parser/tokens.lua rename to src/parser.old/tokens.lua diff --git a/src/parser/compile.lua b/src/parser/compile.lua index e09c958..d0c2d05 100644 --- a/src/parser/compile.lua +++ b/src/parser/compile.lua @@ -1,3989 +1,31 @@ -local tokens = require 'parser.tokens' -local guide = require 'parser.guide' - -local sbyte = string.byte -local sfind = string.find -local smatch = string.match -local sgsub = string.gsub -local ssub = string.sub -local schar = string.char -local supper = string.upper -local uchar = utf8.char -local tconcat = table.concat -local tinsert = table.insert -local tointeger = math.tointeger -local tonumber = tonumber -local maxinteger = math.maxinteger -local assert = assert - -_ENV = nil - ----@alias parser.position integer - ----@param str string ----@return table -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, - ['os.exit'] = 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, LocalLimited - -local LocalLimit = 200 - -local parseExp, parseAction - -local pushError - -local function addSpecial(name, obj) - if not State.specials then - State.specials = {} - end - if not State.specials[name] then - State.specials[name] = {} - end - State.specials[name][#State.specials[name]+1] = obj - obj.special = name -end - ----@param offset integer ----@param leftOrRight '"left"'|'"right"' -local function getPosition(offset, leftOrRight) - if not offset or offset > #Lua then - return LineMulti * Line + #Lua - LineOffset + 1 - end - if leftOrRight == 'left' then - return LineMulti * Line + offset - LineOffset - else - return LineMulti * Line + offset - LineOffset + 1 - end -end - ----@return string? word ----@return parser.position? startPosition ----@return parser.position? finishPosition -local function peekWord() - local word = Tokens[Index + 1] - if not word then - return nil - end - if not CharMapWord[ssub(word, 1, 1)] then - return nil - end - local startPos = getPosition(Tokens[Index] , 'left') - local finishPos = getPosition(Tokens[Index] + #word - 1, 'right') - return word, startPos, finishPos -end - -local function lastRightPosition() - if Index < 2 then - return 0 - end - local token = Tokens[Index - 1] - if NLMap[token] then - return LastTokenFinish - elseif token then - return getPosition(Tokens[Index - 2] + #token - 1, 'right') - else - return getPosition(#Lua, 'right') - end -end - -local function missSymbol(symbol, start, finish) - pushError { - type = 'MISS_SYMBOL', - start = start or lastRightPosition(), - finish = finish or start or lastRightPosition(), - info = { - symbol = symbol, - } - } -end - -local function missExp() - pushError { - type = 'MISS_EXP', - start = lastRightPosition(), - finish = lastRightPosition(), - } -end - -local function missName(pos) - pushError { - type = 'MISS_NAME', - start = pos or lastRightPosition(), - finish = pos or lastRightPosition(), - } -end - -local function missEnd(relatedStart, relatedFinish) - pushError { - type = 'MISS_SYMBOL', - start = lastRightPosition(), - finish = lastRightPosition(), - info = { - symbol = 'end', - related = { - { - start = relatedStart, - finish = relatedFinish, - } - } - } - } - pushError { - type = 'MISS_END', - start = relatedStart, - finish = relatedFinish, - } -end - -local function unknownSymbol(start, finish, word) - local token = word or Tokens[Index + 1] - if not token then - return false - end - pushError { - type = 'UNKNOWN_SYMBOL', - start = start or getPosition(Tokens[Index], 'left'), - finish = finish or getPosition(Tokens[Index] + #token - 1, 'right'), - info = { - symbol = token, - } - } - return true -end - -local function skipUnknownSymbol(stopSymbol) - if unknownSymbol() then - Index = Index + 2 - return true - end - return false -end - -local function skipNL() - local token = Tokens[Index + 1] - if NLMap[token] then - if Index >= 2 and not NLMap[Tokens[Index - 1]] then - LastTokenFinish = getPosition(Tokens[Index - 2] + #Tokens[Index - 1] - 1, 'right') - end - Line = Line + 1 - LineOffset = Tokens[Index] + #token - Index = Index + 2 - State.lines[Line] = LineOffset - return true - end - return false -end - -local function getSavePoint() - local index = Index - local line = Line - local lineOffset = LineOffset - local errs = State.errs - local errCount = #errs - return function () - Index = index - Line = line - LineOffset = lineOffset - for i = errCount + 1, #errs do - errs[i] = nil - end - end -end - -local function fastForwardToken(offset) - while true do - local myOffset = Tokens[Index] - if not myOffset - or myOffset >= offset then - break - end - local token = Tokens[Index + 1] - if NLMap[token] then - Line = Line + 1 - LineOffset = Tokens[Index] + #token - State.lines[Line] = LineOffset - end - Index = Index + 2 - end -end - -local function resolveLongString(finishMark) - skipNL() - local miss - local start = Tokens[Index] - local finishOffset = sfind(Lua, finishMark, start, true) - if not finishOffset then - finishOffset = #Lua + 1 - miss = true - end - local stringResult = start and ssub(Lua, start, finishOffset - 1) or '' - local lastLN = stringResult:find '[\r\n][^\r\n]*$' - if lastLN then - local result = stringResult - : gsub('\r\n?', '\n') - stringResult = result - end - if finishMark == ']]' and State.version == 'Lua 5.1' then - local nestOffset = sfind(Lua, '[[', start, true) - if nestOffset and nestOffset < finishOffset then - fastForwardToken(nestOffset) - local nestStartPos = getPosition(nestOffset, 'left') - local nestFinishPos = getPosition(nestOffset + 1, 'right') - pushError { - type = 'NESTING_LONG_MARK', - start = nestStartPos, - finish = nestFinishPos, - } - end - end - fastForwardToken(finishOffset + #finishMark) - if miss then - local pos = getPosition(finishOffset - 1, 'right') - pushError { - type = 'MISS_SYMBOL', - start = pos, - finish = pos, - info = { - symbol = finishMark, - }, - fix = { - title = 'ADD_LSTRING_END', - { - start = pos, - finish = pos, - text = finishMark, - } - }, - } - end - return stringResult, getPosition(finishOffset + #finishMark - 1, 'right') -end - -local function parseLongString() - local start, finish, mark = sfind(Lua, '^(%[%=*%[)', Tokens[Index]) - if not mark then - return nil - end - fastForwardToken(finish + 1) - local startPos = getPosition(start, 'left') - local finishMark = sgsub(mark, '%[', ']') - local stringResult, finishPos = resolveLongString(finishMark) - return { - type = 'string', - start = startPos, - finish = finishPos, - [1] = stringResult, - [2] = mark, - } -end - -local function pushCommentHeadError(left) - if State.options.nonstandardSymbol['//'] then - return - end - pushError { - type = 'ERR_COMMENT_PREFIX', - start = left, - finish = left + 2, - fix = { - title = 'FIX_COMMENT_PREFIX', - { - start = left, - finish = left + 2, - text = '--', - }, - } - } -end - -local function pushLongCommentError(left, right) - if State.options.nonstandardSymbol['/**/'] then - return - end - pushError { - type = 'ERR_C_LONG_COMMENT', - start = left, - finish = right, - fix = { - title = 'FIX_C_LONG_COMMENT', - { - start = left, - finish = left + 2, - text = '--[[', - }, - { - start = right - 2, - finish = right, - text = '--]]' - }, - } - } -end - -local function skipComment(isAction) - local token = Tokens[Index + 1] - if token == '--' - or ( - token == '//' - and ( - isAction - or State.options.nonstandardSymbol['//'] - ) - ) then - local start = Tokens[Index] - local left = getPosition(start, 'left') - local chead = false - if token == '//' then - chead = true - pushCommentHeadError(left) - end - Index = Index + 2 - local longComment = start + 2 == Tokens[Index] and parseLongString() - if longComment then - longComment.type = 'comment.long' - longComment.text = longComment[1] - longComment.mark = longComment[2] - longComment[1] = nil - longComment[2] = nil - State.comms[#State.comms+1] = longComment - return true - end - while true do - local nl = Tokens[Index + 1] - if not nl or NLMap[nl] then - break - end - Index = Index + 2 - end - local right = Tokens[Index] and (Tokens[Index] - 1) or #Lua - State.comms[#State.comms+1] = { - type = chead and 'comment.cshort' or 'comment.short', - start = left, - finish = getPosition(right, 'right'), - text = ssub(Lua, start + 2, right), - } - return true - end - if token == '/*' then - local start = Tokens[Index] - local left = getPosition(start, 'left') - Index = Index + 2 - local result, right = resolveLongString '*/' - pushLongCommentError(left, right) - State.comms[#State.comms+1] = { - type = 'comment.long', - start = left, - finish = right, - text = result, - } - return true - end - return false -end - -local function skipSpace(isAction) - repeat until not skipNL() - and not skipComment(isAction) -end - -local function expectAssign(isAction) - local token = Tokens[Index + 1] - if token == '=' then - Index = Index + 2 - return true - end - if token == '==' then - local left = getPosition(Tokens[Index], 'left') - local right = getPosition(Tokens[Index] + #token - 1, 'right') - pushError { - type = 'ERR_ASSIGN_AS_EQ', - start = left, - finish = right, - fix = { - title = 'FIX_ASSIGN_AS_EQ', - { - start = left, - finish = right, - text = '=', - } - } - } - Index = Index + 2 - return true - end - if isAction then - if token == '+=' - or token == '-=' - or token == '*=' - or token == '/=' - or token == '%=' - or token == '^=' - or token == '//=' - or token == '|=' - or token == '&=' - or token == '>>=' - or token == '<<=' then - if not State.options.nonstandardSymbol[token] then - unknownSymbol() - end - Index = Index + 2 - return true - end - end - return false -end - -local function parseLocalAttrs() - local attrs - while true do - skipSpace() - local token = Tokens[Index + 1] - if token ~= '<' then - break - end - if not attrs then - attrs = { - type = 'localattrs', - } - end - local attr = { - type = 'localattr', - parent = attrs, - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index], 'right'), - } - attrs[#attrs+1] = attr - Index = Index + 2 - skipSpace() - local word, wstart, wfinish = peekWord() - if word then - attr[1] = word - attr.finish = wfinish - Index = Index + 2 - if word ~= 'const' - and word ~= 'close' then - pushError { - type = 'UNKNOWN_ATTRIBUTE', - start = wstart, - finish = wfinish, - } - end - else - missName() - end - attr.finish = lastRightPosition() - skipSpace() - if Tokens[Index + 1] == '>' then - attr.finish = getPosition(Tokens[Index], 'right') - Index = Index + 2 - elseif Tokens[Index + 1] == '>=' then - attr.finish = getPosition(Tokens[Index], 'right') - pushError { - type = 'MISS_SPACE_BETWEEN', - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + 1, 'right'), - } - Index = Index + 2 - else - missSymbol '>' - end - if State.version ~= 'Lua 5.4' then - pushError { - type = 'UNSUPPORT_SYMBOL', - start = attr.start, - finish = attr.finish, - version = 'Lua 5.4', - info = { - version = State.version - } - } - end - end - return attrs -end - -local function createLocal(obj, attrs) - obj.type = 'local' - obj.effect = obj.finish - - if attrs then - obj.attrs = attrs - attrs.parent = obj - end - - local chunk = Chunk[#Chunk] - if chunk then - local locals = chunk.locals - if not locals then - locals = {} - chunk.locals = locals - end - locals[#locals+1] = obj - LocalCount = LocalCount + 1 - if not LocalLimited and LocalCount > LocalLimit then - LocalLimited = true - pushError { - type = 'LOCAL_LIMIT', - start = obj.start, - finish = obj.finish, - } - end - end - return obj -end - -local function pushChunk(chunk) - Chunk[#Chunk+1] = chunk -end - -local function resolveLable(label, obj) - if not label.ref then - label.ref = {} - end - label.ref[#label.ref+1] = obj - obj.node = label - - -- 如果有局部变量在 goto 与 label 之间声明, - -- 并在 label 之后使用,则算作语法错误 - - -- 如果 label 在 goto 之前声明,那么不会有中间声明的局部变量 - if obj.start > label.start then - return - end - - local block = guide.getBlock(obj) - local locals = block and block.locals - if not locals then - return - end - - for i = 1, #locals do - local loc = locals[i] - -- 检查局部变量声明位置为 goto 与 label 之间 - if loc.start < obj.start or loc.finish > label.finish then - goto CONTINUE - end - -- 检查局部变量的使用位置在 label 之后 - local refs = loc.ref - if not refs then - goto CONTINUE - end - for j = 1, #refs do - local ref = refs[j] - if ref.finish > label.finish then - pushError { - type = 'JUMP_LOCAL_SCOPE', - start = obj.start, - finish = obj.finish, - info = { - loc = loc[1], - }, - relative = { - { - start = label.start, - finish = label.finish, - }, - { - start = loc.start, - finish = loc.finish, - } - }, - } - return - end - end - ::CONTINUE:: - end -end - -local function resolveGoTo(gotos) - for i = 1, #gotos do - local action = gotos[i] - local label = guide.getLabel(action, action[1]) - if label then - resolveLable(label, action) - else - pushError { - type = 'NO_VISIBLE_LABEL', - start = action.start, - finish = action.finish, - info = { - label = action[1], - } - } - end - end -end - -local function popChunk() - local chunk = Chunk[#Chunk] - if chunk.gotos then - resolveGoTo(chunk.gotos) - chunk.gotos = nil - end - local lastAction = chunk[#chunk] - if lastAction then - chunk.finish = lastAction.finish - end - Chunk[#Chunk] = nil -end - -local function parseNil() - if Tokens[Index + 1] ~= 'nil' then - return nil - end - local offset = Tokens[Index] - Index = Index + 2 - return { - type = 'nil', - start = getPosition(offset, 'left'), - finish = getPosition(offset + 2, 'right'), - } -end - -local function parseBoolean() - local word = Tokens[Index+1] - if word ~= 'true' - and word ~= 'false' then - return nil - end - local start = getPosition(Tokens[Index], 'left') - local finish = getPosition(Tokens[Index] + #word - 1, 'right') - Index = Index + 2 - return { - type = 'boolean', - start = start, - finish = finish, - [1] = word == 'true' and true or false, - } -end - -local function parseStringUnicode() - local offset = Tokens[Index] + 1 - if ssub(Lua, offset, offset) ~= '{' then - local pos = getPosition(offset, 'left') - missSymbol('{', pos) - return nil, offset - end - local leftPos = getPosition(offset, 'left') - local x16 = smatch(Lua, '^%w*', offset + 1) - local rightPos = getPosition(offset + #x16, 'right') - offset = offset + #x16 + 1 - if ssub(Lua, offset, offset) == '}' then - offset = offset + 1 - rightPos = rightPos + 1 - else - missSymbol('}', rightPos) - end - offset = offset + 1 - if #x16 == 0 then - pushError { - type = 'UTF8_SMALL', - start = leftPos, - finish = rightPos, - } - return '', offset - end - if State.version ~= 'Lua 5.3' - and State.version ~= 'Lua 5.4' - and State.version ~= 'LuaJIT' - then - pushError { - type = 'ERR_ESC', - start = leftPos - 2, - finish = rightPos, - version = {'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, - info = { - version = State.version, - } - } - return nil, offset - end - local byte = tonumber(x16, 16) - if not byte then - for i = 1, #x16 do - if not tonumber(ssub(x16, i, i), 16) then - pushError { - type = 'MUST_X16', - start = leftPos + i, - finish = leftPos + i + 1, - } - end - end - return nil, offset - end - if State.version == 'Lua 5.4' then - if byte < 0 or byte > 0x7FFFFFFF then - pushError { - type = 'UTF8_MAX', - start = leftPos, - finish = rightPos, - info = { - min = '00000000', - max = '7FFFFFFF', - } - } - return nil, offset - end - else - if byte < 0 or byte > 0x10FFFF then - pushError { - type = 'UTF8_MAX', - start = leftPos, - finish = rightPos, - version = byte <= 0x7FFFFFFF and 'Lua 5.4' or nil, - info = { - min = '000000', - max = '10FFFF', - } - } - end - end - if byte >= 0 and byte <= 0x10FFFF then - return uchar(byte), offset - end - return '', offset -end - -local stringPool = {} -local function parseShortString() - local mark = Tokens[Index+1] - local startOffset = Tokens[Index] - local startPos = getPosition(startOffset, 'left') - Index = Index + 2 - local stringIndex = 0 - local currentOffset = startOffset + 1 - local escs = {} - while true do - local token = Tokens[Index + 1] - if token == mark then - stringIndex = stringIndex + 1 - stringPool[stringIndex] = ssub(Lua, currentOffset, Tokens[Index] - 1) - Index = Index + 2 - break - end - if NLMap[token] then - stringIndex = stringIndex + 1 - stringPool[stringIndex] = ssub(Lua, currentOffset, Tokens[Index] - 1) - missSymbol(mark) - break - end - if not token then - stringIndex = stringIndex + 1 - stringPool[stringIndex] = ssub(Lua, currentOffset or -1) - missSymbol(mark) - break - end - if token == '\\' then - stringIndex = stringIndex + 1 - stringPool[stringIndex] = ssub(Lua, currentOffset, Tokens[Index] - 1) - currentOffset = Tokens[Index] - Index = Index + 2 - if not Tokens[Index] then - goto CONTINUE - end - local escLeft = getPosition(currentOffset, 'left') - -- has space? - if Tokens[Index] - currentOffset > 1 then - local right = getPosition(currentOffset + 1, 'right') - pushError { - type = 'ERR_ESC', - start = escLeft, - finish = right, - } - escs[#escs+1] = escLeft - escs[#escs+1] = right - escs[#escs+1] = 'err' - goto CONTINUE - end - local nextToken = ssub(Tokens[Index + 1], 1, 1) - if EscMap[nextToken] then - stringIndex = stringIndex + 1 - stringPool[stringIndex] = EscMap[nextToken] - currentOffset = Tokens[Index] + #nextToken - Index = Index + 2 - escs[#escs+1] = escLeft - escs[#escs+1] = escLeft + 2 - escs[#escs+1] = 'normal' - goto CONTINUE - end - if nextToken == mark then - stringIndex = stringIndex + 1 - stringPool[stringIndex] = mark - currentOffset = Tokens[Index] + #nextToken - Index = Index + 2 - escs[#escs+1] = escLeft - escs[#escs+1] = escLeft + 2 - escs[#escs+1] = 'normal' - goto CONTINUE - end - if nextToken == 'z' then - Index = Index + 2 - repeat until not skipNL() - currentOffset = Tokens[Index] - escs[#escs+1] = escLeft - escs[#escs+1] = escLeft + 2 - escs[#escs+1] = 'normal' - goto CONTINUE - end - if CharMapNumber[nextToken] then - local numbers = smatch(Tokens[Index + 1], '^%d+') - if #numbers > 3 then - numbers = ssub(numbers, 1, 3) - end - currentOffset = Tokens[Index] + #numbers - fastForwardToken(currentOffset) - local right = getPosition(currentOffset - 1, 'right') - local byte = tointeger(numbers) - if byte and byte <= 255 then - stringIndex = stringIndex + 1 - stringPool[stringIndex] = schar(byte) - else - pushError { - type = 'ERR_ESC', - start = escLeft, - finish = right, - } - end - escs[#escs+1] = escLeft - escs[#escs+1] = right - escs[#escs+1] = 'byte' - goto CONTINUE - end - if nextToken == 'x' then - local left = getPosition(Tokens[Index] - 1, 'left') - local x16 = ssub(Tokens[Index + 1], 2, 3) - local byte = tonumber(x16, 16) - if byte then - currentOffset = Tokens[Index] + 3 - stringIndex = stringIndex + 1 - stringPool[stringIndex] = schar(byte) - else - currentOffset = Tokens[Index] + 1 - pushError { - type = 'MISS_ESC_X', - start = getPosition(currentOffset, 'left'), - finish = getPosition(currentOffset + 1, 'right'), - } - end - local right = getPosition(currentOffset + 1, 'right') - escs[#escs+1] = escLeft - escs[#escs+1] = right - escs[#escs+1] = 'byte' - if State.version == 'Lua 5.1' then - pushError { - type = 'ERR_ESC', - start = left, - finish = left + 4, - version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, - info = { - version = State.version, - } - } - end - Index = Index + 2 - goto CONTINUE - end - if nextToken == 'u' then - local str, newOffset = parseStringUnicode() - if str then - stringIndex = stringIndex + 1 - stringPool[stringIndex] = str - end - currentOffset = newOffset - fastForwardToken(currentOffset - 1) - local right = getPosition(currentOffset + 1, 'right') - escs[#escs+1] = escLeft - escs[#escs+1] = right - escs[#escs+1] = 'unicode' - goto CONTINUE - end - if NLMap[nextToken] then - stringIndex = stringIndex + 1 - stringPool[stringIndex] = '\n' - currentOffset = Tokens[Index] + #nextToken - skipNL() - local right = getPosition(currentOffset + 1, 'right') - escs[#escs+1] = escLeft - escs[#escs+1] = escLeft + 1 - escs[#escs+1] = 'normal' - goto CONTINUE - end - local right = getPosition(currentOffset + 1, 'right') - pushError { - type = 'ERR_ESC', - start = escLeft, - finish = right, - } - escs[#escs+1] = escLeft - escs[#escs+1] = right - escs[#escs+1] = 'err' - end - Index = Index + 2 - ::CONTINUE:: - end - local stringResult = tconcat(stringPool, '', 1, stringIndex) - local str = { - type = 'string', - start = startPos, - finish = lastRightPosition(), - escs = #escs > 0 and escs or nil, - [1] = stringResult, - [2] = mark, - } - if mark == '`' then - if not State.options.nonstandardSymbol[mark] then - pushError { - type = 'ERR_NONSTANDARD_SYMBOL', - start = startPos, - finish = str.finish, - info = { - symbol = '"', - }, - fix = { - title = 'FIX_NONSTANDARD_SYMBOL', - symbol = '"', - { - start = startPos, - finish = startPos + 1, - text = '"', - }, - { - start = str.finish - 1, - finish = str.finish, - text = '"', - }, - } - } - end - end - return str -end - -local function parseString() - local c = Tokens[Index + 1] - if CharMapStrSH[c] then - return parseShortString() - end - if CharMapStrLH[c] then - return parseLongString() - end - return nil -end - -local function parseNumber10(start) - local integer = true - local integerPart = smatch(Lua, '^%d*', start) - local offset = start + #integerPart - -- float part - if ssub(Lua, offset, offset) == '.' then - local floatPart = smatch(Lua, '^%d*', offset + 1) - integer = false - offset = offset + #floatPart + 1 - end - -- exp part - local echar = ssub(Lua, offset, offset) - if CharMapE10[echar] then - integer = false - offset = offset + 1 - local nextChar = ssub(Lua, offset, offset) - if CharMapSign[nextChar] then - offset = offset + 1 - end - local exp = smatch(Lua, '^%d*', offset) - offset = offset + #exp - if #exp == 0 then - pushError { - type = 'MISS_EXPONENT', - start = getPosition(offset - 1, 'right'), - finish = getPosition(offset - 1, 'right'), - } - end - end - return tonumber(ssub(Lua, start, offset - 1)), offset, integer -end - -local function parseNumber16(start) - local integerPart = smatch(Lua, '^[%da-fA-F]*', start) - local offset = start + #integerPart - local integer = true - -- float part - if ssub(Lua, offset, offset) == '.' then - local floatPart = smatch(Lua, '^[%da-fA-F]*', offset + 1) - integer = false - offset = offset + #floatPart + 1 - if #integerPart == 0 and #floatPart == 0 then - pushError { - type = 'MUST_X16', - start = getPosition(offset - 1, 'right'), - finish = getPosition(offset - 1, 'right'), - } - end - else - if #integerPart == 0 then - pushError { - type = 'MUST_X16', - start = getPosition(offset - 1, 'right'), - finish = getPosition(offset - 1, 'right'), - } - return 0, offset - end - end - -- exp part - local echar = ssub(Lua, offset, offset) - if CharMapE16[echar] then - integer = false - offset = offset + 1 - local nextChar = ssub(Lua, offset, offset) - if CharMapSign[nextChar] then - offset = offset + 1 - end - local exp = smatch(Lua, '^%d*', offset) - offset = offset + #exp - end - local n = tonumber(ssub(Lua, start - 2, offset - 1)) - return n, offset, integer -end - -local function parseNumber2(start) - local bins = smatch(Lua, '^[01]*', start) - local offset = start + #bins - if State.version ~= 'LuaJIT' then - pushError { - type = 'UNSUPPORT_SYMBOL', - start = getPosition(start - 2, 'left'), - finish = getPosition(offset - 1, 'right'), - version = 'LuaJIT', - info = { - version = 'Lua 5.4', - } - } - end - return tonumber(bins, 2), offset -end - -local function dropNumberTail(offset, integer) - local _, finish, word = sfind(Lua, '^([%.%w_\x80-\xff]+)', offset) - if not finish then - return offset - end - if integer then - if supper(ssub(word, 1, 2)) == 'LL' then - if State.version ~= 'LuaJIT' then - pushError { - type = 'UNSUPPORT_SYMBOL', - start = getPosition(offset, 'left'), - finish = getPosition(offset + 1, 'right'), - version = 'LuaJIT', - info = { - version = State.version, - } - } - end - offset = offset + 2 - word = ssub(word, offset) - elseif supper(ssub(word, 1, 3)) == 'ULL' then - if State.version ~= 'LuaJIT' then - pushError { - type = 'UNSUPPORT_SYMBOL', - start = getPosition(offset, 'left'), - finish = getPosition(offset + 2, 'right'), - version = 'LuaJIT', - info = { - version = State.version, - } - } - end - offset = offset + 3 - word = ssub(word, offset) - end - end - if supper(ssub(word, 1, 1)) == 'I' then - if State.version ~= 'LuaJIT' then - pushError { - type = 'UNSUPPORT_SYMBOL', - start = getPosition(offset, 'left'), - finish = getPosition(offset, 'right'), - version = 'LuaJIT', - info = { - version = State.version, - } - } - end - offset = offset + 1 - word = ssub(word, offset) - end - if #word > 0 then - pushError { - type = 'MALFORMED_NUMBER', - start = getPosition(offset, 'left'), - finish = getPosition(finish, 'right'), - } - end - return finish + 1 -end - -local function parseNumber() - local offset = Tokens[Index] - if not offset then - return nil - end - local startPos = getPosition(offset, 'left') - local neg - if ssub(Lua, offset, offset) == '-' then - neg = true - offset = offset + 1 - end - local number, integer - local firstChar = ssub(Lua, offset, offset) - if firstChar == '.' then - number, offset = parseNumber10(offset) - integer = false - elseif firstChar == '0' then - local nextChar = ssub(Lua, offset + 1, offset + 1) - if CharMapN16[nextChar] then - number, offset, integer = parseNumber16(offset + 2) - elseif CharMapN2[nextChar] then - number, offset = parseNumber2(offset + 2) - integer = true - else - number, offset, integer = parseNumber10(offset) - end - elseif CharMapNumber[firstChar] then - number, offset, integer = parseNumber10(offset) - else - return nil - end - if not number then - number = 0 - end - if neg then - number = - number - end - local result = { - type = integer and 'integer' or 'number', - start = startPos, - finish = getPosition(offset - 1, 'right'), - [1] = number, - } - offset = dropNumberTail(offset, integer) - fastForwardToken(offset) - return result -end - -local function isKeyWord(word, nextToken) - if KeyWord[word] then - return true - end - if word == 'goto' then - if State.version == 'Lua 5.1' then - return false - end - if State.version == 'LuaJIT' then - if not nextToken then - return false - end - if CharMapWord[ssub(nextToken, 1, 1)] then - return true - end - return false - end - return true - 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, Tokens[Index + 1]) then - pushError { - type = 'KEYWORD', - start = startPos, - finish = finishPos, - } - end - return { - type = 'name', - start = startPos, - finish = finishPos, - [1] = word, - } -end - -local function parseNameOrList(parent) - local first = parseName() - if not first then - return nil - end - skipSpace() - local list - while true do - if Tokens[Index + 1] ~= ',' then - break - end - Index = Index + 2 - skipSpace() - local name = parseName(true) - if not name then - missName() - break - end - if not list then - list = { - type = 'list', - start = first.start, - finish = first.finish, - parent = parent, - [1] = first - } - end - list[#list+1] = name - list.finish = name.finish - end - return list or first -end - -local function parseExpList(mini) - local list - local wantSep = false - while true do - skipSpace() - local token = Tokens[Index + 1] - if not token then - break - end - if ListFinishMap[token] then - break - end - if token == ',' then - local sepPos = getPosition(Tokens[Index], 'right') - if not wantSep then - pushError { - type = 'UNEXPECT_SYMBOL', - start = getPosition(Tokens[Index], 'left'), - finish = sepPos, - info = { - symbol = ',', - } - } - end - wantSep = false - Index = Index + 2 - goto CONTINUE - else - if mini then - if wantSep then - break - end - local nextToken = peekWord() - if isKeyWord(nextToken, Tokens[Index + 2]) - and nextToken ~= 'function' - and nextToken ~= 'true' - and nextToken ~= 'false' - and nextToken ~= 'nil' - and nextToken ~= 'not' then - break - end - end - local exp = parseExp() - if not exp then - break - end - if wantSep then - missSymbol(',', list[#list].finish, exp.start) - end - wantSep = true - if not list then - list = { - type = 'list', - start = exp.start, - } - end - list[#list+1] = exp - list.finish = exp.finish - exp.parent = list - end - ::CONTINUE:: - end - if not list then - return nil - end - if not wantSep then - missExp() - end - return list -end - -local function parseIndex() - local start = getPosition(Tokens[Index], 'left') - Index = Index + 2 - skipSpace() - local exp = parseExp() - local index = { - type = 'index', - start = start, - finish = exp and exp.finish or (start + 1), - index = exp - } - if exp then - exp.parent = index - else - missExp() - end - skipSpace() - if Tokens[Index + 1] == ']' then - index.finish = getPosition(Tokens[Index], 'right') - Index = Index + 2 - else - missSymbol ']' - end - return index -end - -local function parseTable() - local tbl = { - type = 'table', - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index], 'right'), - } - Index = Index + 2 - local index = 0 - local tindex = 0 - local wantSep = false - while true do - skipSpace(true) - local token = Tokens[Index + 1] - if token == '}' then - Index = Index + 2 - break - end - if CharMapTSep[token] then - if not wantSep then - missExp() - end - wantSep = false - Index = Index + 2 - goto CONTINUE - end - local lastRight = lastRightPosition() - - if peekWord() then - local savePoint = getSavePoint() - local name = parseName() - if name then - skipSpace() - if Tokens[Index + 1] == '=' then - Index = Index + 2 - if wantSep then - pushError { - type = 'MISS_SEP_IN_TABLE', - start = lastRight, - finish = getPosition(Tokens[Index], 'left'), - } - end - wantSep = true - skipSpace() - local fvalue = parseExp() - local tfield = { - type = 'tablefield', - start = name.start, - finish = name.finish, - range = fvalue and fvalue.finish, - node = tbl, - parent = tbl, - field = name, - value = fvalue, - } - name.type = 'field' - name.parent = tfield - if fvalue then - fvalue.parent = tfield - else - missExp() - end - index = index + 1 - tbl[index] = tfield - goto CONTINUE - end - end - savePoint() - end - - local exp = parseExp(true) - if exp then - if wantSep then - pushError { - type = 'MISS_SEP_IN_TABLE', - start = lastRight, - finish = exp.start, - } - end - wantSep = true - if exp.type == 'varargs' then - index = index + 1 - tbl[index] = exp - exp.parent = tbl - goto CONTINUE - end - index = index + 1 - tindex = tindex + 1 - local texp = { - type = 'tableexp', - start = exp.start, - finish = exp.finish, - tindex = tindex, - parent = tbl, - value = exp, - } - exp.parent = texp - tbl[index] = texp - goto CONTINUE - end - - if token == '[' then - if wantSep then - pushError { - type = 'MISS_SEP_IN_TABLE', - start = lastRight, - finish = getPosition(Tokens[Index], 'left'), - } - end - wantSep = true - local tindex = parseIndex() - skipSpace() - tindex.type = 'tableindex' - tindex.node = tbl - tindex.parent = tbl - index = index + 1 - tbl[index] = tindex - if expectAssign() then - skipSpace() - local ivalue = parseExp() - if ivalue then - ivalue.parent = tindex - tindex.range = ivalue.finish - tindex.value = ivalue - else - missExp() - end - else - missSymbol '=' - end - goto CONTINUE - end - - missSymbol '}' - break - ::CONTINUE:: - end - tbl.finish = lastRightPosition() - return tbl -end - -local function addDummySelf(node, call) - if node.type ~= 'getmethod' then - return - end - -- dummy param `self` - if not call.args then - call.args = { - type = 'callargs', - start = call.start, - finish = call.finish, - parent = call, - } - end - local self = { - type = 'self', - start = node.colon.start, - finish = node.colon.finish, - parent = call.args, - [1] = 'self', - } - tinsert(call.args, 1, self) -end - -local function checkAmbiguityCall(call, parenPos) - if State.version ~= 'Lua 5.1' then - return - end - local node = call.node - local nodeRow = guide.rowColOf(node.finish) - local callRow = guide.rowColOf(parenPos) - if nodeRow == callRow then - return - end - pushError { - type = 'AMBIGUOUS_SYNTAX', - start = parenPos, - finish = call.finish, - } -end - -local function bindSpecial(source, name) - if Specials[name] then - addSpecial(name, source) - else - local ospeicals = State.options.special - if ospeicals and ospeicals[name] then - addSpecial(ospeicals[name], source) - end - end -end - -local function parseSimple(node, funcName) - local currentName - if node.type == 'getglobal' - or node.type == 'getlocal' then - currentName = node[1] - end - local lastMethod - while true do - if lastMethod and node.node == lastMethod then - if node.type ~= 'call' then - missSymbol('(', node.node.finish, node.node.finish) - end - lastMethod = nil - end - skipSpace() - local token = Tokens[Index + 1] - if token == '.' then - local dot = { - type = token, - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index], 'right'), - } - Index = Index + 2 - skipSpace() - local field = parseName(true) - local getfield = { - type = 'getfield', - start = node.start, - finish = lastRightPosition(), - node = node, - dot = dot, - field = field - } - if field then - field.parent = getfield - field.type = 'field' - if currentName then - if node.type == 'getlocal' - or node.type == 'getglobal' - or node.type == 'getfield' then - currentName = currentName .. '.' .. field[1] - bindSpecial(getfield, currentName) - else - currentName = nil - end - end - else - pushError { - type = 'MISS_FIELD', - start = lastRightPosition(), - finish = lastRightPosition(), - } - end - node.parent = getfield - node.next = getfield - node = getfield - elseif token == ':' then - local colon = { - type = token, - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index], 'right'), - } - Index = Index + 2 - skipSpace() - local method = parseName(true) - local getmethod = { - type = 'getmethod', - start = node.start, - finish = lastRightPosition(), - node = node, - colon = colon, - method = method - } - if method then - method.parent = getmethod - method.type = 'method' - else - pushError { - type = 'MISS_METHOD', - start = lastRightPosition(), - finish = lastRightPosition(), - } - end - node.parent = getmethod - node.next = getmethod - node = getmethod - if lastMethod then - missSymbol('(', node.node.finish, node.node.finish) - end - lastMethod = getmethod - elseif token == '(' then - if funcName then - break - end - local startPos = getPosition(Tokens[Index], 'left') - local call = { - type = 'call', - start = node.start, - node = node, - } - Index = Index + 2 - local args = parseExpList() - if Tokens[Index + 1] == ')' then - call.finish = getPosition(Tokens[Index], 'right') - Index = Index + 2 - else - call.finish = lastRightPosition() - missSymbol ')' - end - if args then - args.type = 'callargs' - args.start = startPos - args.finish = call.finish - args.parent = call - call.args = args - end - addDummySelf(node, call) - checkAmbiguityCall(call, startPos) - node.parent = call - node = call - elseif token == '{' then - if funcName then - break - end - local tbl = parseTable() - local call = { - type = 'call', - start = node.start, - finish = tbl.finish, - node = node, - } - local args = { - type = 'callargs', - start = tbl.start, - finish = tbl.finish, - parent = call, - [1] = tbl, - } - call.args = args - addDummySelf(node, call) - tbl.parent = args - node.parent = call - node = call - elseif CharMapStrSH[token] then - if funcName then - break - end - local str = parseShortString() - local call = { - type = 'call', - start = node.start, - finish = str.finish, - node = node, - } - local args = { - type = 'callargs', - start = str.start, - finish = str.finish, - parent = call, - [1] = str, - } - call.args = args - addDummySelf(node, call) - str.parent = args - node.parent = call - node = call - elseif CharMapStrLH[token] then - local str = parseLongString() - if str then - if funcName then - break - end - local call = { - type = 'call', - start = node.start, - finish = str.finish, - node = node, - } - local args = { - type = 'callargs', - start = str.start, - finish = str.finish, - parent = call, - [1] = str, - } - call.args = args - addDummySelf(node, call) - str.parent = args - node.parent = call - node = call - else - local index = parseIndex() - local bstart = index.start - index.type = 'getindex' - index.start = node.start - index.node = node - node.next = index - node.parent = index - node = index - if funcName then - pushError { - type = 'INDEX_IN_FUNC_NAME', - start = bstart, - finish = index.finish, - } - end - end - else - break - end - end - if node.type == 'call' - and node.node == lastMethod then - lastMethod = nil - end - if node.type == 'call' then - if node.node.special == 'error' - or node.node.special == 'os.exit' then - node.hasExit = true - end - end - if node == lastMethod then - if funcName then - lastMethod = nil - end - end - if lastMethod then - missSymbol('(', lastMethod.finish) - end - return node -end - -local function parseVarargs() - local varargs = { - type = 'varargs', - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + 2, 'right'), - } - Index = Index + 2 - for i = #Chunk, 1, -1 do - local chunk = Chunk[i] - if chunk.vararg then - if not chunk.vararg.ref then - chunk.vararg.ref = {} - end - chunk.vararg.ref[#chunk.vararg.ref+1] = varargs - varargs.node = chunk.vararg - break - end - if chunk.type == 'main' then - break - end - if chunk.type == 'function' then - pushError { - type = 'UNEXPECT_DOTS', - start = varargs.start, - finish = varargs.finish, - } - break - end - end - return varargs -end - -local function parseParen() - local pl = Tokens[Index] - local paren = { - type = 'paren', - start = getPosition(pl, 'left'), - finish = getPosition(pl, 'right') - } - Index = Index + 2 - skipSpace() - local exp = parseExp() - if exp then - paren.exp = exp - paren.finish = exp.finish - exp.parent = paren - else - missExp() - end - skipSpace() - if Tokens[Index + 1] == ')' then - paren.finish = getPosition(Tokens[Index], 'right') - Index = Index + 2 - else - missSymbol ')' - end - return paren -end - -local function getLocal(name, pos) - for i = #Chunk, 1, -1 do - local chunk = Chunk[i] - local locals = chunk.locals - if locals then - local res - for n = 1, #locals do - local loc = locals[n] - if loc.effect > pos then - break - end - if loc[1] == name then - if not res or res.effect < loc.effect then - res = loc - end - end - end - if res then - return res - end - end - end -end - -local function resolveName(node) - if not node then - return nil - end - local loc = getLocal(node[1], node.start) - if loc then - node.type = 'getlocal' - node.node = loc - if not loc.ref then - loc.ref = {} - end - loc.ref[#loc.ref+1] = node - if loc.special then - addSpecial(loc.special, node) - end - else - node.type = 'getglobal' - local env = getLocal(State.ENVMode, node.start) - if env then - node.node = env - if not env.ref then - env.ref = {} - end - env.ref[#env.ref+1] = node - end - end - local name = node[1] - bindSpecial(node, name) - return node -end - -local function isChunkFinishToken(token) - local currentChunk = Chunk[#Chunk] - if not currentChunk then - return false - end - local tp = currentChunk.type - if tp == 'main' then - return false - end - if tp == 'for' - or tp == 'in' - or tp == 'loop' - or tp == 'function' then - return token == 'end' - end - if tp == 'if' - or tp == 'ifblock' - or tp == 'elseifblock' - or tp == 'elseblock' then - return token == 'then' - or token == 'end' - or token == 'else' - or token == 'elseif' - end - if tp == 'repeat' then - return token == 'until' - end - return true -end - -local function parseActions() - local rtn, last - while true do - skipSpace(true) - local token = Tokens[Index + 1] - if token == ';' then - Index = Index + 2 - goto CONTINUE - end - if ChunkFinishMap[token] - and isChunkFinishToken(token) then - break - end - local action, failed = parseAction() - if failed then - if not skipUnknownSymbol() then - break - end - end - if action then - if not rtn and action.type == 'return' then - rtn = action - end - last = action - end - ::CONTINUE:: - end - if rtn and rtn ~= last then - pushError { - type = 'ACTION_AFTER_RETURN', - start = rtn.start, - finish = rtn.finish, - } - end -end - -local function parseParams(params) - local lastSep - local hasDots - while true do - skipSpace() - local token = Tokens[Index + 1] - if not token or token == ')' then - if lastSep then - missName() - end - break - end - if token == ',' then - if lastSep or lastSep == nil then - missName() - else - lastSep = true - end - Index = Index + 2 - goto CONTINUE - end - if token == '...' then - if lastSep == false then - missSymbol ',' - end - lastSep = false - if not params then - params = {} - end - local vararg = { - type = '...', - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + 2, 'right'), - parent = params, - [1] = '...', - } - local chunk = Chunk[#Chunk] - chunk.vararg = vararg - params[#params+1] = vararg - if hasDots then - pushError { - type = 'ARGS_AFTER_DOTS', - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + 2, 'right'), - } - end - hasDots = true - Index = Index + 2 - goto CONTINUE - end - if CharMapWord[ssub(token, 1, 1)] then - if lastSep == false then - missSymbol ',' - end - lastSep = false - if not params then - params = {} - end - params[#params+1] = createLocal { - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + #token - 1, 'right'), - parent = params, - [1] = token, - } - if hasDots then - pushError { - type = 'ARGS_AFTER_DOTS', - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + #token - 1, 'right'), - } - end - if isKeyWord(token, Tokens[Index + 3]) 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, - bstart = funcRight, - keyword = { - [1] = funcLeft, - [2] = funcRight, - }, - } - Index = Index + 2 - 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 - func.bstart = 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 - local LastLocalCount = LocalCount - LocalCount = 0 - pushChunk(func) - local params - if func.name and func.name.type == 'getmethod' then - if func.name.type == 'getmethod' then - params = {} - params[1] = createLocal { - start = funcRight, - finish = funcRight, - parent = params, - [1] = 'self', - } - params[1].type = 'self' - end - end - if hasLeftParen then - params = params or {} - local parenLeft = getPosition(Tokens[Index], 'left') - Index = Index + 2 - params = parseParams(params) - params.type = 'funcargs' - params.start = parenLeft - params.finish = lastRightPosition() - params.parent = func - func.args = params - skipSpace(true) - if Tokens[Index + 1] == ')' then - local parenRight = getPosition(Tokens[Index], 'right') - func.finish = parenRight - func.bstart = parenRight - if params then - params.finish = parenRight - end - Index = Index + 2 - skipSpace(true) - else - func.finish = lastRightPosition() - func.bstart = func.finish - if params then - params.finish = func.finish - end - missSymbol ')' - end - else - missSymbol '(' - end - parseActions() - popChunk() - if Tokens[Index + 1] == 'end' then - local endLeft = getPosition(Tokens[Index], 'left') - local endRight = getPosition(Tokens[Index] + 2, 'right') - func.keyword[3] = endLeft - func.keyword[4] = endRight - func.finish = endRight - Index = Index + 2 - else - func.finish = lastRightPosition() - missEnd(funcLeft, funcRight) - end - LocalCount = LastLocalCount - return func -end - -local function checkNeedParen(source) - local token = Tokens[Index + 1] - if token ~= '.' - and token ~= ':' then - return source - end - local exp = parseSimple(source, false) - if exp == source then - return exp - end - pushError { - type = 'NEED_PAREN', - start = source.start, - finish = source.finish, - fix = { - title = 'FIX_ADD_PAREN', - { - start = source.start, - finish = source.start, - text = '(', - }, - { - start = source.finish, - finish = source.finish, - text = ')', - } - } - } - return exp -end - -local function parseExpUnit() - local token = Tokens[Index + 1] - if token == '(' then - local paren = parseParen() - return parseSimple(paren, false) - end - - if token == '...' then - local varargs = parseVarargs() - return varargs - end - - if token == '{' then - local table = parseTable() - if not table then - return nil - end - local exp = checkNeedParen(table) - return exp - end - - if CharMapStrSH[token] then - local string = parseShortString() - if not string then - return nil - end - local exp = checkNeedParen(string) - return exp - end - - if CharMapStrLH[token] then - local string = parseLongString() - if not string then - return nil - end - local exp = checkNeedParen(string) - return exp - end - - local number = parseNumber() - if number then - return number - end - - if ChunkFinishMap[token] then - return nil - end - - if token == 'nil' then - return parseNil() - end - - if token == 'true' - or token == 'false' then - return parseBoolean() - end - - if token == 'function' then - return parseFunction() - end - - local node = parseName() - if node then - return parseSimple(resolveName(node), false) - end - - return nil -end - -local function parseUnaryOP() - local token = Tokens[Index + 1] - local symbol = UnarySymbol[token] and token or UnaryAlias[token] - if not symbol then - return nil - end - local myLevel = UnarySymbol[symbol] - local op = { - type = symbol, - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + #symbol - 1, 'right'), - } - Index = Index + 2 - return op, myLevel -end - ----@param level integer # op level must greater than this level -local function parseBinaryOP(asAction, level) - local token = Tokens[Index + 1] - local symbol = (BinarySymbol[token] and token) - or BinaryAlias[token] - or (not asAction and BinaryActionAlias[token]) - if not symbol then - return nil - end - if symbol == '//' and State.options.nonstandardSymbol['//'] then - return nil - end - local myLevel = BinarySymbol[symbol] - if level and myLevel < level then - return nil - end - local op = { - type = symbol, - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + #token - 1, 'right'), - } - if not asAction then - if token == '=' then - pushError { - type = 'ERR_EQ_AS_ASSIGN', - start = op.start, - finish = op.finish, - fix = { - title = 'FIX_EQ_AS_ASSIGN', - { - start = op.start, - finish = op.finish, - text = '==', - } - } - } - end - end - if BinaryAlias[token] then - if not State.options.nonstandardSymbol[token] then - pushError { - type = 'ERR_NONSTANDARD_SYMBOL', - start = op.start, - finish = op.finish, - info = { - symbol = symbol, - }, - fix = { - title = 'FIX_NONSTANDARD_SYMBOL', - symbol = symbol, - { - start = op.start, - finish = op.finish, - text = symbol, - }, - } - } - end - end - if token == '//' - or token == '<<' - or token == '>>' then - if State.version ~= 'Lua 5.3' - and State.version ~= 'Lua 5.4' then - pushError { - type = 'UNSUPPORT_SYMBOL', - version = {'Lua 5.3', 'Lua 5.4'}, - start = op.start, - finish = op.finish, - info = { - version = State.version, - } - } - end - end - Index = Index + 2 - return op, myLevel -end - -function parseExp(asAction, level) - local exp - local uop, uopLevel = parseUnaryOP() - if uop then - skipSpace() - local child = parseExp(asAction, uopLevel) - -- 预计算负数 - if uop.type == '-' - and child - and (child.type == 'number' or child.type == 'integer') then - child.start = uop.start - child[1] = - child[1] - exp = child - else - exp = { - type = 'unary', - op = uop, - start = uop.start, - finish = child and child.finish or uop.finish, - [1] = child, - } - if child then - child.parent = exp - else - missExp() - end - end - else - exp = parseExpUnit() - if not exp then - return nil - end - end - - while true do - skipSpace() - local bop, bopLevel = parseBinaryOP(asAction, level) - if not bop then - break - end - - ::AGAIN:: - skipSpace() - local isForward = SymbolForward[bopLevel] - local child = parseExp(asAction, isForward and (bopLevel + 0.5) or bopLevel) - if not child then - if skipUnknownSymbol() then - goto AGAIN - else - missExp() - end - end - local bin = { - type = 'binary', - start = exp.start, - finish = child and child.finish or bop.finish, - op = bop, - [1] = exp, - [2] = child - } - exp.parent = bin - if child then - child.parent = bin - end - exp = bin - end - - return exp -end - -local function skipSeps() - while true do - skipSpace() - if Tokens[Index + 1] == ',' then - missExp() - Index = Index + 2 - else - break - end - end -end - ----@return parser.object? first ----@return parser.object? second ----@return parser.object[]? rest -local function parseSetValues() - skipSpace() - local first = parseExp() - if not first then - return nil - end - skipSpace() - if Tokens[Index + 1] ~= ',' then - return first - end - Index = Index + 2 - skipSeps() - local second = parseExp() - if not second then - missExp() - return first - end - skipSpace() - if Tokens[Index + 1] ~= ',' then - return first, second - end - Index = Index + 2 - skipSeps() - local third = parseExp() - if not third then - missExp() - return first, second - end - - local rest = { third } - while true do - skipSpace() - if Tokens[Index + 1] ~= ',' then - return first, second, rest - end - Index = Index + 2 - skipSeps() - local exp = parseExp() - if not exp then - missExp() - return first, second, rest - end - rest[#rest+1] = exp - end -end - -local function pushActionIntoCurrentChunk(action) - local chunk = Chunk[#Chunk] - if chunk then - chunk[#chunk+1] = action - action.parent = chunk - end -end - ----@return parser.object? second ----@return parser.object[]? rest -local function parseVarTails(parser, isLocal) - if Tokens[Index + 1] ~= ',' then - return nil - end - Index = Index + 2 - skipSpace() - local second = parser(true) - if not second then - missName() - return nil - end - if isLocal then - createLocal(second, parseLocalAttrs()) - end - skipSpace() - if Tokens[Index + 1] ~= ',' then - return second - end - Index = Index + 2 - skipSeps() - local third = parser(true) - if not third then - missName() - return second - end - if isLocal then - createLocal(third, parseLocalAttrs()) - end - local rest = { third } - while true do - skipSpace() - if Tokens[Index + 1] ~= ',' then - return second, rest - end - Index = Index + 2 - skipSeps() - local name = parser(true) - if not name then - missName() - return second, rest - end - if isLocal then - createLocal(name, parseLocalAttrs()) - end - rest[#rest+1] = name - end -end - -local function bindValue(n, v, index, lastValue, isLocal, isSet) - if isLocal then - if v and v.special then - addSpecial(v.special, n) - end - elseif isSet then - n.type = GetToSetMap[n.type] or n.type - if n.type == 'setlocal' then - local loc = n.node - if loc.attrs then - pushError { - type = 'SET_CONST', - start = n.start, - finish = n.finish, - } - end - end - end - if not v and lastValue then - if lastValue.type == 'call' - or lastValue.type == 'varargs' then - v = lastValue - if not v.extParent then - v.extParent = {} - end - end - end - if v then - if v.type == 'call' - or v.type == 'varargs' then - local select = { - type = 'select', - sindex = index, - start = v.start, - finish = v.finish, - vararg = v - } - if v.parent then - v.extParent[#v.extParent+1] = select - else - v.parent = select - end - v = select - end - n.value = v - n.range = v.finish - v.parent = n - end -end - -local function parseMultiVars(n1, parser, isLocal) - local n2, nrest = parseVarTails(parser, isLocal) - skipSpace() - local v1, v2, vrest - local isSet - local max = 1 - if expectAssign(not isLocal) then - v1, v2, vrest = parseSetValues() - isSet = true - if not v1 then - missExp() - end - end - local index = 1 - bindValue(n1, v1, index, nil, isLocal, isSet) - local lastValue = v1 - local lastVar = n1 - if n2 then - max = 2 - if not v2 then - index = 2 - end - bindValue(n2, v2, index, lastValue, isLocal, isSet) - lastValue = v2 or lastValue - lastVar = n2 - pushActionIntoCurrentChunk(n2) - end - if nrest then - for i = 1, #nrest do - local n = nrest[i] - local v = vrest and vrest[i] - max = i + 2 - if not v then - index = index + 1 - end - bindValue(n, v, index, lastValue, isLocal, isSet) - lastValue = v or lastValue - lastVar = n - pushActionIntoCurrentChunk(n) - end - end - - if isLocal then - local effect = lastValue and lastValue.finish or lastVar.finish - n1.effect = effect - if n2 then - n2.effect = effect - end - if nrest then - for i = 1, #nrest do - nrest[i].effect = effect - end - end - end - - if v2 and not n2 then - v2.redundant = { - max = max, - passed = 2, - } - pushActionIntoCurrentChunk(v2) - end - if vrest then - for i = 1, #vrest do - local v = vrest[i] - if not nrest or not nrest[i] then - v.redundant = { - max = max, - passed = i + 2, - } - pushActionIntoCurrentChunk(v) - end - end - end - - return n1, isSet -end - -local function compileExpAsAction(exp) - pushActionIntoCurrentChunk(exp) - if GetToSetMap[exp.type] then - skipSpace() - local isLocal - if exp.type == 'getlocal' and exp[1] == State.ENVMode then - exp.special = nil - -- TODO: need + 1 at the end - LocalCount = LocalCount - 1 - local loc = createLocal(exp, parseLocalAttrs()) - loc.locPos = exp.start - loc.effect = maxinteger - isLocal = true - skipSpace() - end - local action, isSet = parseMultiVars(exp, parseExp, isLocal) - if isSet - or action.type == 'getmethod' then - return action - end - end - - if exp.type == 'call' then - if exp.hasExit then - for i = #Chunk, 1, -1 do - local block = Chunk[i] - if block.type == 'ifblock' - or block.type == 'elseifblock' - or block.type == 'elseblock' - or block.type == 'function' then - block.hasExit = true - break - end - end - end - return exp - end - - if exp.type == 'binary' then - if GetToSetMap[exp[1].type] then - local op = exp.op - if op.type == '==' then - pushError { - type = 'ERR_ASSIGN_AS_EQ', - start = op.start, - finish = op.finish, - fix = { - title = 'FIX_ASSIGN_AS_EQ', - { - start = op.start, - finish = op.finish, - text = '=', - } - } - } - return - end - end - end - - pushError { - type = 'EXP_IN_ACTION', - start = exp.start, - finish = exp.finish, - } - - return exp -end - -local function parseLocal() - local locPos = getPosition(Tokens[Index], 'left') - Index = Index + 2 - skipSpace() - local word = peekWord() - if not word then - missName() - return nil - end - - if word == 'function' then - local func = parseFunction(true, true) - local name = func.name - if name then - func.name = nil - name.value = func - name.vstart = func.start - name.range = func.finish - name.locPos = locPos - func.parent = name - pushActionIntoCurrentChunk(name) - return name - else - missName(func.keyword[2]) - pushActionIntoCurrentChunk(func) - return func - end - end - - local name = parseName(true) - if not name then - missName() - return nil - end - local loc = createLocal(name, parseLocalAttrs()) - loc.locPos = locPos - loc.effect = maxinteger - pushActionIntoCurrentChunk(loc) - skipSpace() - parseMultiVars(loc, parseName, true) - - return loc -end - -local function parseDo() - local doLeft = getPosition(Tokens[Index], 'left') - local doRight = getPosition(Tokens[Index] + 1, 'right') - local obj = { - type = 'do', - start = doLeft, - finish = doRight, - bstart = doRight, - keyword = { - [1] = doLeft, - [2] = doRight, - }, - } - Index = Index + 2 - pushActionIntoCurrentChunk(obj) - pushChunk(obj) - parseActions() - popChunk() - if Tokens[Index + 1] == 'end' then - obj.finish = getPosition(Tokens[Index] + 2, 'right') - obj.keyword[3] = getPosition(Tokens[Index], 'left') - obj.keyword[4] = getPosition(Tokens[Index] + 2, 'right') - Index = Index + 2 - else - missEnd(doLeft, doRight) - end - if obj.locals then - LocalCount = LocalCount - #obj.locals - end - - return obj -end - -local function parseReturn() - local returnLeft = getPosition(Tokens[Index], 'left') - local returnRight = getPosition(Tokens[Index] + 5, 'right') - Index = Index + 2 - skipSpace() - local rtn = parseExpList(true) - if rtn then - rtn.type = 'return' - rtn.start = returnLeft - else - rtn = { - type = 'return', - start = returnLeft, - finish = returnRight, - } - end - pushActionIntoCurrentChunk(rtn) - for i = #Chunk, 1, -1 do - local block = Chunk[i] - if block.type == 'function' - or block.type == 'main' then - if not block.returns then - block.returns = {} - end - block.returns[#block.returns+1] = rtn - break - end - end - for i = #Chunk, 1, -1 do - local block = Chunk[i] - if block.type == 'ifblock' - or block.type == 'elseifblock' - or block.type == 'elseblock' - or block.type == 'function' then - block.hasReturn = true - break - end - end - - return rtn -end - -local function parseLabel() - local left = getPosition(Tokens[Index], 'left') - Index = Index + 2 - skipSpace() - local label = parseName() - skipSpace() - - if not label then - missName() - end - - if Tokens[Index + 1] == '::' then - Index = Index + 2 - else - if label then - missSymbol '::' - end - end - - if not label then - return nil - end - - label.type = 'label' - pushActionIntoCurrentChunk(label) - - local block = guide.getBlock(label) - if block then - if not block.labels then - block.labels = {} - end - local name = label[1] - local olabel = guide.getLabel(block, name) - if olabel then - if State.version == 'Lua 5.4' - or block == guide.getBlock(olabel) then - pushError { - type = 'REDEFINED_LABEL', - start = label.start, - finish = label.finish, - relative = { - { - olabel.start, - olabel.finish, - } - } - } - end - end - block.labels[name] = label - end - - if State.version == 'Lua 5.1' then - pushError { - type = 'UNSUPPORT_SYMBOL', - start = left, - finish = lastRightPosition(), - version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, - info = { - version = State.version, - } - } - return - end - return label -end - -local function parseGoTo() - local start = getPosition(Tokens[Index], 'left') - Index = Index + 2 - skipSpace() - - local action = parseName() - if not action then - missName() - return nil - end - - action.type = 'goto' - action.keyStart = start - - for i = #Chunk, 1, -1 do - local chunk = Chunk[i] - if chunk.type == 'function' - or chunk.type == 'main' then - if not chunk.gotos then - chunk.gotos = {} - end - chunk.gotos[#chunk.gotos+1] = action - break - end - end - for i = #Chunk, 1, -1 do - local chunk = Chunk[i] - if chunk.type == 'ifblock' - or chunk.type == 'elseifblock' - or chunk.type == 'elseblock' then - chunk.hasGoTo = true - break - end - end - - pushActionIntoCurrentChunk(action) - return action -end - -local function parseIfBlock(parent) - local ifLeft = getPosition(Tokens[Index], 'left') - local ifRight = getPosition(Tokens[Index] + 1, 'right') - Index = Index + 2 - local ifblock = { - type = 'ifblock', - parent = parent, - start = ifLeft, - finish = ifRight, - bstart = ifRight, - keyword = { - [1] = ifLeft, - [2] = ifRight, - } - } - skipSpace() - local filter = parseExp() - if filter then - ifblock.filter = filter - ifblock.finish = filter.finish - ifblock.bstart = ifblock.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.bstart = ifblock.finish - ifblock.keyword[3] = getPosition(Tokens[Index], 'left') - ifblock.keyword[4] = ifblock.finish - if thenToken == 'do' then - pushError { - type = 'ERR_THEN_AS_DO', - start = ifblock.keyword[3], - finish = ifblock.keyword[4], - fix = { - title = 'FIX_THEN_AS_DO', - { - start = ifblock.keyword[3], - finish = ifblock.keyword[4], - text = 'then', - } - } - } - end - Index = Index + 2 - else - missSymbol 'then' - end - pushChunk(ifblock) - parseActions() - popChunk() - ifblock.finish = getPosition(Tokens[Index], 'left') - if ifblock.locals then - LocalCount = LocalCount - #ifblock.locals - end - return ifblock -end - -local function parseElseIfBlock(parent) - local ifLeft = getPosition(Tokens[Index], 'left') - local ifRight = getPosition(Tokens[Index] + 5, 'right') - local elseifblock = { - type = 'elseifblock', - parent = parent, - start = ifLeft, - finish = ifRight, - bstart = ifRight, - keyword = { - [1] = ifLeft, - [2] = ifRight, - } - } - Index = Index + 2 - skipSpace() - local filter = parseExp() - if filter then - elseifblock.filter = filter - elseifblock.finish = filter.finish - elseifblock.bstart = elseifblock.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.bstart = elseifblock.finish - elseifblock.keyword[3] = getPosition(Tokens[Index], 'left') - elseifblock.keyword[4] = elseifblock.finish - if thenToken == 'do' then - pushError { - type = 'ERR_THEN_AS_DO', - start = elseifblock.keyword[3], - finish = elseifblock.keyword[4], - fix = { - title = 'FIX_THEN_AS_DO', - { - start = elseifblock.keyword[3], - finish = elseifblock.keyword[4], - text = 'then', - } - } - } - end - Index = Index + 2 - else - missSymbol 'then' - end - pushChunk(elseifblock) - parseActions() - popChunk() - elseifblock.finish = getPosition(Tokens[Index], 'left') - if elseifblock.locals then - LocalCount = LocalCount - #elseifblock.locals - end - return elseifblock -end - -local function parseElseBlock(parent) - local ifLeft = getPosition(Tokens[Index], 'left') - local ifRight = getPosition(Tokens[Index] + 3, 'right') - local elseblock = { - type = 'elseblock', - parent = parent, - start = ifLeft, - finish = ifRight, - bstart = ifRight, - keyword = { - [1] = ifLeft, - [2] = ifRight, - } - } - Index = Index + 2 - skipSpace() - pushChunk(elseblock) - parseActions() - popChunk() - elseblock.finish = getPosition(Tokens[Index], 'left') - if elseblock.locals then - LocalCount = LocalCount - #elseblock.locals - end - return elseblock -end - -local function parseIf() - local token = Tokens[Index + 1] - local left = getPosition(Tokens[Index], 'left') - local action = { - type = 'if', - start = left, - finish = getPosition(Tokens[Index] + #token - 1, 'right'), - } - pushActionIntoCurrentChunk(action) - if token ~= 'if' then - missSymbol('if', left, left) - end - local hasElse - while true do - local word = Tokens[Index + 1] - local child - if word == 'if' then - child = parseIfBlock(action) - elseif word == 'elseif' then - child = parseElseIfBlock(action) - elseif word == 'else' then - child = parseElseBlock(action) - end - if not child then - break - end - if hasElse then - pushError { - type = 'BLOCK_AFTER_ELSE', - start = child.start, - finish = child.finish, - } - end - if word == 'else' then - hasElse = true - end - action[#action+1] = child - action.finish = child.finish - skipSpace() - end - - if Tokens[Index + 1] == 'end' then - action.finish = getPosition(Tokens[Index] + 2, 'right') - Index = Index + 2 - else - missEnd(action[1].keyword[1], action[1].keyword[2]) - end - - return action -end - -local function parseFor() - local action = { - type = 'for', - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + 2, 'right'), - keyword = {}, - } - action.bstart = action.finish - 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() - local forStateVars - -- 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 - -- for x in ... uses 4 variables - forStateVars = 3 - LocalCount = LocalCount + forStateVars - if name then - local loc = createLocal(name) - loc.parent = action - action.finish = name.finish - action.bstart = action.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 - action.bstart = action.finish - end - local max = expList[2] - if max then - max.parent = expList - action.max = max - action.finish = max.finish - action.bstart = action.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 - action.bstart = action.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.bstart = action.finish - 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 - action.bstart = action.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 State.version == 'Lua 5.4' then - forStateVars = 4 - else - forStateVars = 3 - end - LocalCount = LocalCount + forStateVars - - 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.bstart = action.finish - 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 - if forStateVars then - LocalCount = LocalCount - forStateVars - end - - return action -end - -local function parseWhile() - local action = { - type = 'while', - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + 4, 'right'), - keyword = {}, - } - action.bstart = action.finish - 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.bstart = action.finish - 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.bstart = action.finish - 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', Tokens[Index + 3]) 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 - LocalLimited = false - Chunk = {} - Tokens = tokens(lua) - Index = 1 - ---@class parser.state - ---@field uri uri - ---@field lines integer[] - local state = { - version = version, - lua = lua, - ast = {}, - errs = {}, - comms = {}, - lines = { - [0] = 1, - size = #lua, - }, - options = options or {}, - } - if not state.options.nonstandardSymbol then - state.options.nonstandardSymbol = {} - end - State = state - if version == 'Lua 5.1' or version == 'LuaJIT' then - state.ENVMode = '@fenv' - else - state.ENVMode = '_ENV' - end - - pushError = function (err) - local errs = state.errs - if err.finish < err.start then - err.finish = err.start - end - local last = errs[#errs] - if last then - if last.start <= err.start and last.finish >= err.finish then - return - end - end - err.level = err.level or 'Error' - errs[#errs+1] = err - return err - end -end - -return function (lua, mode, version, options) - Mode = mode - initState(lua, version, options) - skipSpace() - if mode == 'Lua' then - State.ast = parseLua() - elseif mode == 'Nil' then - State.ast = parseNil() - elseif mode == 'Boolean' then - State.ast = parseBoolean() - elseif mode == 'String' then - State.ast = parseString() - elseif mode == 'Number' then - State.ast = parseNumber() - elseif mode == 'Name' then - State.ast = parseName() - elseif mode == 'Exp' then - State.ast = parseExp() - elseif mode == 'Action' then - State.ast = parseAction() - end - - if State.ast then - State.ast.state = State - end - - while true do - if Index <= #Tokens then - unknownSymbol() - Index = Index + 2 - else - break - end - end - - return State +local class = require 'class' + +---@class LuaParser +local M = class.get 'LuaParser' + +---@alias LuaParser.LuaVersion +---| '5.1' +---| '5.2' +---| '5.3' +---| '5.4' + +---@alias LuaParser.NonestandardSymbol +---|'//' | '/**/' +---|'`' +---|'+=' | '-=' | '*=' | '/=' | '%=' | '^=' | '//=' +---|'|=' | '&=' | '<<=' | '>>=' +---|'||' | '&&' | '!' | '!=' +---|'continue', + +---@class LuaParser.CompileOptions +---@field jit? boolean # 是否为LuaJIT,默认为 false +---@field nonstandardSymbols? LuaParser.NonestandardSymbol[] # 支持的非标准符号 +---@field unicodeName? boolean # 是否支持Unicode标识符,默认为 false + +-- 编译lua代码 +---@param lua string # lua代码 +---@param version? LuaParser.LuaVersion # 默认为 '5.4' +---@param options? any +function M.compile(lua, version, options) + end diff --git a/src/parser/init.lua b/src/parser/init.lua index bc004f7..7c6df81 100644 --- a/src/parser/init.lua +++ b/src/parser/init.lua @@ -1,8 +1,8 @@ -local api = { - compile = require 'parser.compile', - lines = require 'parser.lines', - guide = require 'parser.guide', - luadoc = require 'parser.luadoc', -} +local class = require 'class' -return api +---@class LuaParser +local M = class.declare 'LuaParser' + +require 'parser.compile' + +return M diff --git a/src/parser/node.lua b/src/parser/node.lua new file mode 100644 index 0000000..2b5ec69 --- /dev/null +++ b/src/parser/node.lua @@ -0,0 +1,10 @@ +local class = require 'class' + +---@class LuaParser.Ast.Node: Class.Base +local Node = class.declare 'LuaParser.Ast.Node' + +Node.rowcolMulti = 10000 + +Node.__getter.type = function () + error('No type') +end From 64e2285ede3290483b291afc2e936f8cbc9a57f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Tue, 5 Sep 2023 19:55:11 +0800 Subject: [PATCH 02/94] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser.old/compile.lua | 2 +- src/parser/compile.lua | 61 +++++++- src/parser/lexer.lua | 117 +++++++++++++++ test/{ast => ast.old}/Action.lua | 0 test/{ast => ast.old}/Boolean.lua | 0 test/{ast => ast.old}/Comment.lua | 0 test/{ast => ast.old}/Dirty.lua | 0 test/{ast => ast.old}/Exp.lua | 0 test/{ast => ast.old}/Lua.lua | 0 test/{ast => ast.old}/LuaDoc.lua | 0 test/ast.old/Nil.lua | 12 ++ test/{ast => ast.old}/Number.lua | 0 test/{ast => ast.old}/String.lua | 0 test/ast.old/init.lua | 230 ++++++++++++++++++++++++++++++ test/ast/Nil.lua | 8 ++ test/ast/init.lua | 2 + 16 files changed, 426 insertions(+), 6 deletions(-) create mode 100644 src/parser/lexer.lua rename test/{ast => ast.old}/Action.lua (100%) rename test/{ast => ast.old}/Boolean.lua (100%) rename test/{ast => ast.old}/Comment.lua (100%) rename test/{ast => ast.old}/Dirty.lua (100%) rename test/{ast => ast.old}/Exp.lua (100%) rename test/{ast => ast.old}/Lua.lua (100%) rename test/{ast => ast.old}/LuaDoc.lua (100%) create mode 100644 test/ast.old/Nil.lua rename test/{ast => ast.old}/Number.lua (100%) rename test/{ast => ast.old}/String.lua (100%) create mode 100644 test/ast.old/init.lua diff --git a/src/parser.old/compile.lua b/src/parser.old/compile.lua index 1c7d6a0..697c9ce 100644 --- a/src/parser.old/compile.lua +++ b/src/parser.old/compile.lua @@ -1,4 +1,4 @@ -local tokens = require 'parser.tokens' +local tokens = require 'src.parser.lexer' local guide = require 'parser.guide' local sbyte = string.byte diff --git a/src/parser/compile.lua b/src/parser/compile.lua index d0c2d05..a23a486 100644 --- a/src/parser/compile.lua +++ b/src/parser/compile.lua @@ -1,4 +1,5 @@ local class = require 'class' +local lexer = require 'src.parser.lexer' ---@class LuaParser local M = class.get 'LuaParser' @@ -19,13 +20,63 @@ local M = class.get 'LuaParser' ---@class LuaParser.CompileOptions ---@field jit? boolean # 是否为LuaJIT,默认为 false ----@field nonstandardSymbols? LuaParser.NonestandardSymbol[] # 支持的非标准符号 +---@field nonestandardSymbols? LuaParser.NonestandardSymbol[] # 支持的非标准符号 ---@field unicodeName? boolean # 是否支持Unicode标识符,默认为 false +---@class LuaParser.Status +---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Status +local S = class.declare 'LuaParser.Status' + +-- Lua版本 +---@type LuaParser.LuaVersion +S.version = '5.4' +-- 是否为LuaJIT +S.jit = false +-- 是否支持Unicode标识符 +S.unicodeName = false + +---@param code string # lua代码 +---@param version? LuaParser.LuaVersion +---@param options? LuaParser.CompileOptions +function S:__init(code, version, options) + -- 代码内容 + self.code = code + self.version = version or '5.4' + -- 非标准符号的映射表 + self.nssymbolMap = {} + -- 词法分析结果 + self.lexer = lexer.parseLua(code) + self.index = 1 + + if options then + if options.nonestandardSymbols then + for _, s in ipairs(options.nonestandardSymbols) do + self.nssymbolMap[s] = true + end + end + self.jit = options.jit + self.unicodeName = options.unicodeName + end +end + +-- 跳过空白符 +---@param inState? boolean # 是否是语句 +function S:skipSpace(inState) + repeat until not self:skipNL() + and not self:skipComment(inState) +end + +-- 编译Lua代码 +function S:compile() + self:skipSpace() +end + -- 编译lua代码 ----@param lua string # lua代码 +---@param code string # lua代码 ---@param version? LuaParser.LuaVersion # 默认为 '5.4' ----@param options? any -function M.compile(lua, version, options) - +---@param options? LuaParser.CompileOptions +function M.compile(code, version, options) + local status = class.new 'LuaParser.Status' (code, version, options) + status:compile() + return status end diff --git a/src/parser/lexer.lua b/src/parser/lexer.lua new file mode 100644 index 0000000..578bf6e --- /dev/null +++ b/src/parser/lexer.lua @@ -0,0 +1,117 @@ +local l = require 'lpeglabel' +local class = require 'class' + +local Sp = l.S' \t\v\f' +local Nl = l.P'\r\n' + l.S'\r\n' +local Number = l.R'09'^1 +local Word = l.R('AZ', 'az', '__', '\x80\xff') * l.R('AZ', 'az', '09', '__', '\x80\xff')^0 +local Symbol = l.P'==' + + l.P'~=' + + l.P'--' + -- non-standard: + + l.P'<<=' + + l.P'>>=' + + l.P'//=' + -- end non-standard + + l.P'<<' + + l.P'>>' + + l.P'<=' + + l.P'>=' + + l.P'//' + + l.P'...' + + l.P'..' + + l.P'::' + -- non-standard: + + l.P'!=' + + l.P'&&' + + l.P'||' + + l.P'/*' + + l.P'*/' + + l.P'+=' + + l.P'-=' + + l.P'*=' + + l.P'%=' + + l.P'&=' + + l.P'|=' + + l.P'^=' + + l.P'/=' + -- end non-standard + -- singles + + l.S'+-*/!#%^&()={}[]|\\\'":;<>,.?~`' +local Unknown = (1 - Number - Word - Symbol - Sp - Nl)^1 +local Token = l.Cp() * l.C( + Nl * l.Cc 'NL' + + Number * l.Cc 'Num' + + Word * l.Cc 'Word' + + Symbol * l.Cc 'Symbol' + + Unknown * l.Cc 'Unknown' +) + +local Parser = l.Ct((Sp^1 + Token)^0) + +---@alias LuaParser.Lexer.Type +---| 'NL' +---| 'Num' +---| 'Word' +---| 'Symbol' +---| 'Unknown' + +---@class Lexer +---@overload fun(code: string, mode: 'Lua' | 'Cat'): Lexer +local M = class.declare 'Lexer' + +---@param code string +---@param mode 'Lua' | 'Cat' +function M:__init(code, mode) + local results = Parser:match(code) + self.tokens = {} -- 分离出来的词 + self.poses = {} -- 每个词的开始位置(偏移) + self.types = {} -- 每个词的类型 + self.nls = {} -- 每个换行符的开始位置(偏移) + self.ci = 1 -- 当前词的索引 + for i, res in ipairs(results) do + if i % 3 == 1 then + self.poses[#self.poses+1] = res + elseif i % 3 == 2 then + self.tokens[#self.tokens+1] = res + elseif i % 3 == 0 then + self.types[#self.types+1] = res + if res == 'NL' then + self.nls[#self.nls+1] = results[i-2] + end + end + end +end + +-- 看看当前的词 +---@param next? integer # 默认为0表示当前的词,1表示下一个词,以此类推 +---@return string +---@return LuaParser.Lexer.Type +function M:peek(next) + local i = self.ci + (next or 0) + local token = self.tokens[i] + local tp = self.types[i] + return token, tp +end + +-- 消耗一个词,返回这个词 +---@param count? integer # 默认为1表示消耗一个词,2表示消耗两个词,以此类推 +function M:next(count) + local i = self.ci + (count or 1) + local token = self.tokens[i] + local tp = self.types[i] + self.ci = i + return token, tp +end + +local API = {} + +-- 对Lua代码进行分词 +---@param code string +---@return Lexer +function API.parseLua(code) + local lexer = class.new 'Lexer' (code, 'Lua') + return lexer +end + +return API diff --git a/test/ast/Action.lua b/test/ast.old/Action.lua similarity index 100% rename from test/ast/Action.lua rename to test/ast.old/Action.lua diff --git a/test/ast/Boolean.lua b/test/ast.old/Boolean.lua similarity index 100% rename from test/ast/Boolean.lua rename to test/ast.old/Boolean.lua diff --git a/test/ast/Comment.lua b/test/ast.old/Comment.lua similarity index 100% rename from test/ast/Comment.lua rename to test/ast.old/Comment.lua diff --git a/test/ast/Dirty.lua b/test/ast.old/Dirty.lua similarity index 100% rename from test/ast/Dirty.lua rename to test/ast.old/Dirty.lua diff --git a/test/ast/Exp.lua b/test/ast.old/Exp.lua similarity index 100% rename from test/ast/Exp.lua rename to test/ast.old/Exp.lua diff --git a/test/ast/Lua.lua b/test/ast.old/Lua.lua similarity index 100% rename from test/ast/Lua.lua rename to test/ast.old/Lua.lua diff --git a/test/ast/LuaDoc.lua b/test/ast.old/LuaDoc.lua similarity index 100% rename from test/ast/LuaDoc.lua rename to test/ast.old/LuaDoc.lua diff --git a/test/ast.old/Nil.lua b/test/ast.old/Nil.lua new file mode 100644 index 0000000..70744e2 --- /dev/null +++ b/test/ast.old/Nil.lua @@ -0,0 +1,12 @@ +CHECK [[nil]] +{ + type = "nil", + start = 0, + finish = 3, +} +CHECK [[ nil]] +{ + type = "nil", + start = 3, + finish = 6, +} diff --git a/test/ast/Number.lua b/test/ast.old/Number.lua similarity index 100% rename from test/ast/Number.lua rename to test/ast.old/Number.lua diff --git a/test/ast/String.lua b/test/ast.old/String.lua similarity index 100% rename from test/ast/String.lua rename to test/ast.old/String.lua diff --git a/test/ast.old/init.lua b/test/ast.old/init.lua new file mode 100644 index 0000000..fa4eed8 --- /dev/null +++ b/test/ast.old/init.lua @@ -0,0 +1,230 @@ +local parser = require 'parser' +local fs = require 'bee.filesystem' +local utility = require 'utility' + +EXISTS = {} + +local function eq(a, b) + local tp1, tp2 = type(a), type(b) + if tp1 ~= tp2 then + return false + end + if a == '' and tp1 == 'table' then + return true + end + if b == '' and tp2 == 'table' then + return true + end + if tp1 == 'table' then + local checked = {} + for k in pairs(a) do + if not eq(a[k], b[k]) then + return false + end + checked[k] = true + end + for k in pairs(b) do + if not checked[k] then + return false + end + end + return true + end + if tp1 == 'number' then + return ('%q'):format(a) == ('%q'):format(b) + end + return a == b +end + +---@type {[string]: integer, [integer]: string} +local sortList = { + 'specials', + 'type', 'start', 'vstart', 'bstart', 'finish', 'effect', 'range', 'tindex', + 'tag', 'special', 'keyword', + 'parent', 'extParent', 'child', + 'filter', + 'vararg', + 'node', 'locPos', + 'op', 'args', + 'loc', 'init', 'max', 'step', 'keys', 'exps', 'call', 'func', + 'dot', 'colon', + 'field', 'index', 'method', + 'exp', 'value', 'vref', + 'attrs', 'escs', + 'locals', 'ref', 'returns', 'breaks', +} +for i, v in ipairs(sortList) do + sortList[v] = i +end +local ignoreList = { + 'specials', 'locals', 'ref', 'node', 'parent', 'extParent', 'returns', 'state', 'mirror', 'next', 'vararg', 'originalComment', 'typeCache', 'eachCache', 'bindSource' +} +local ignoreMap = {} +for i, v in ipairs(ignoreList) do + ignoreMap[v] = true +end + +IGNORE_MAP = ignoreMap + +local myOption = { + alignment = true, + sorter = function (keys, keymap) + table.sort(keys, function (a, b) + local tp1 = type(a) + local tp2 = type(b) + if tp1 == 'number' and tp2 ~= 'number' then + return false + end + if tp1 ~= 'number' and tp2 == 'number' then + return true + end + if tp1 == 'number' and tp2 == 'number' then + return a < b + end + local s1 = sortList[a] or 9999 + local s2 = sortList[b] or 9999 + if s1 == s2 then + return a < b + else + return s1 < s2 + end + end) + end, + loop = ('%q'):format(''), + number = function (n) + return ('%q'):format(n) + end, + format = setmetatable({}, { __index = function (_, key) + return function (value, _, _, _) + if ignoreMap[key] then + return '' + end + if type(key) == 'string' and key:sub(1, 1) == '_' then + return nil + end + return value + end + end}), +} + +local targetOption = { + alignment = true, + sorter = function (keys, keymap) + table.sort(keys, function (a, b) + local tp1 = type(a) + local tp2 = type(b) + if tp1 == 'number' and tp2 ~= 'number' then + return false + end + if tp1 ~= 'number' and tp2 == 'number' then + return true + end + if tp1 == 'number' and tp2 == 'number' then + return a < b + end + local s1 = sortList[a] or 9999 + local s2 = sortList[b] or 9999 + if s1 == s2 then + return a < b + else + return s1 < s2 + end + end) + end, + loop = ('%q'):format(''), + number = function (n) + return ('%q'):format(n) + end, +} + +local function autoFix(myBuf, targetBuf) + local info = debug.getinfo(3, 'Sl') + local filename = info.source:sub(2) + local fileBuf = utility.loadFile(filename) + assert(fileBuf) + local pos = fileBuf:find(targetBuf, 1, true) + local newFileBuf = fileBuf:sub(1, pos-1) .. myBuf .. fileBuf:sub(pos + #targetBuf) + utility.saveFile(filename, newFileBuf) +end + +local function test(type) + local mode = type + if mode == 'Dirty' then + mode = 'Lua' + end + CHECK = function (buf, opt) + return function (target_ast) + local state, err = parser.compile(buf, mode, 'Lua 5.4', opt) + if not state then + error(('语法树生成失败:%s'):format(err)) + end + state.ast.state = nil + local result = utility.dump(state.ast, myOption) + local expect = utility.dump(target_ast, targetOption) + if result ~= expect then + fs.create_directory(ROOT / 'test' / 'log') + utility.saveFile((ROOT / 'test' / 'log' / 'my_ast.ast'):string(), result) + utility.saveFile((ROOT / 'test' / 'log' / 'target_ast.ast'):string(), expect) + autoFix(result, expect) + error(('语法树不相等:%s\n%s'):format(type, buf)) + end + end + end + LuaDoc = function (buf) + return function (target_doc) + local state, err = parser.compile(buf, 'Lua', 'Lua 5.4') + if not state then + error(('语法树生成失败:%s'):format(err)) + end + parser.luadoc(state) + for _, doc in ipairs(state.ast.docs) do + doc.bindGroup = nil + doc.bindSources = nil + end + state.ast.docs.groups = nil + local result = utility.dump(state.ast.docs, myOption) + local expect = utility.dump(target_doc, targetOption) + if result ~= expect then + fs.create_directory(ROOT / 'test' / 'log') + utility.saveFile((ROOT / 'test' / 'log' / 'my_doc.ast'):string(), result) + utility.saveFile((ROOT / 'test' / 'log' / 'target_doc.ast'):string(), expect) + autoFix(result, expect) + error(('语法树不相等:%s\n%s'):format(type, buf)) + end + end + end + Comment = function (buf) + return function (target_comment) + local state, err = parser.compile(buf, mode, 'Lua 5.4', { + ['nonstandardSymbol'] = { + ['//'] = true, + }, + }) + if not state then + error(('语法树生成失败:%s'):format(err)) + end + state.ast.state = nil + local result = utility.dump(state.comms, myOption) + local expect = utility.dump(target_comment, targetOption) + if result ~= expect then + fs.create_directory(ROOT / 'test' / 'log') + utility.saveFile((ROOT / 'test' / 'log' / 'my_ast.ast'):string(), result) + utility.saveFile((ROOT / 'test' / 'log' / 'target_ast.ast'):string(), expect) + --autoFix(result, expect) + error(('语法树不相等:%s\n%s'):format(type, buf)) + end + end + end + require('ast.' .. type) +end + +test 'Nil' +test 'Boolean' +test 'String' +test 'Number' +test 'Exp' +test 'Action' +test 'Lua' +test 'Dirty' +test 'LuaDoc' +test 'Comment' diff --git a/test/ast/Nil.lua b/test/ast/Nil.lua index 70744e2..81809b6 100644 --- a/test/ast/Nil.lua +++ b/test/ast/Nil.lua @@ -1,3 +1,11 @@ +local parser = require 'parser' + +local function TEST(code) + return function (expect) + local status = parser.compile(code) + end +end + CHECK [[nil]] { type = "nil", diff --git a/test/ast/init.lua b/test/ast/init.lua index fa4eed8..e92a4cd 100644 --- a/test/ast/init.lua +++ b/test/ast/init.lua @@ -218,6 +218,8 @@ local function test(type) require('ast.' .. type) end +require 'test.ast.nil' + test 'Nil' test 'Boolean' test 'String' From f12e939eb698b66822674b9b7177583ca2f1b514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 6 Sep 2023 15:11:47 +0800 Subject: [PATCH 03/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90nil?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .luarc.json | 19 +++- .vscode/launch.json | 5 +- src/class.lua | 14 ++- src/parser/compile.lua | 42 +++++++- src/parser/init.lua | 1 + src/parser/lexer.lua | 56 +++++++++- src/parser/node.lua | 76 +++++++++++++- test/ast/Nil.lua | 21 ++-- test/ast/init.lua | 233 +---------------------------------------- test/main.lua | 8 +- 10 files changed, 216 insertions(+), 259 deletions(-) diff --git a/.luarc.json b/.luarc.json index 8222162..9dc2f02 100644 --- a/.luarc.json +++ b/.luarc.json @@ -5,12 +5,29 @@ "root", "log", "tracy" - ] + ], + "groupFileStatus": { + "ambiguity": "Any", + "await": "Any", + "duplicate": "Any", + "global": "Any", + "luadoc": "Any", + "redefined": "Any", + "strict": "Any", + "type-check": "Any", + "unbalanced": "Any", + "unused": "Any" + } }, "runtime": { "version": "Lua 5.4" }, "workspace": { "preloadFileSize": 1000 + }, + "type": { + "weakNilCheck": false, + "weakUnionCheck": false, + "castNumberToInteger": false } } diff --git a/.vscode/launch.json b/.vscode/launch.json index d549ea5..e990ed8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -16,9 +16,10 @@ "-e", "DEBUG=true", ], - "arg": [ + "outputCapture": [ + "print" ], - "console": "internalConsole", + "internalConsoleOptions": "openOnSessionStart", "sourceCoding": "utf8", "luaVersion": "5.4", "luaArch": "x86_64", diff --git a/src/class.lua b/src/class.lua index 2cbc4b9..2d6969e 100644 --- a/src/class.lua +++ b/src/class.lua @@ -63,11 +63,17 @@ function M.declare(name, super) ---@param k any ---@return any local function getterFunc(self, k) - local f = getter[k] - if f then - return f(self) + local r = class[k] + if r == nil then + local f = getter[k] + if f then + return f(self) + else + return nil + end else - return class[k] + self[k] = r + return r end end diff --git a/src/parser/compile.lua b/src/parser/compile.lua index a23a486..b613e5f 100644 --- a/src/parser/compile.lua +++ b/src/parser/compile.lua @@ -1,5 +1,5 @@ local class = require 'class' -local lexer = require 'src.parser.lexer' +local lexer = require 'parser.lexer' ---@class LuaParser local M = class.get 'LuaParser' @@ -59,6 +59,30 @@ function S:__init(code, version, options) end end +-- 跳过换行符 +---@return boolean # 是否成功跳过换行符 +function S:skipNL() + local _, tp = self.lexer:peek() + if tp == 'NL' then + self.lexer:next() + return true + end + return false +end + +-- 跳过注释 +---@param inState? boolean # 是否是语句 +---@return boolean # 是否成功跳过注释 +function S:skipComment(inState) + local token, tp = self.lexer:peek() + if token == '--' + or (token == '//' and (inState or self.nssymbolMap['//'])) then + + end +end + +-- 跳过注释 + -- 跳过空白符 ---@param inState? boolean # 是否是语句 function S:skipSpace(inState) @@ -66,6 +90,21 @@ function S:skipSpace(inState) and not self:skipComment(inState) end +---@return LuaParser.Node.Nil? +function S:parseNil() + local token = self.lexer:peek() + if token ~= 'nil' then + return nil + end + local start, finish = self.lexer:getPos() + self.lexer:next() + return class.new('LuaParser.Node.Nil', { + status = self, + start = start, + finish = finish, + }) +end + -- 编译Lua代码 function S:compile() self:skipSpace() @@ -75,6 +114,7 @@ end ---@param code string # lua代码 ---@param version? LuaParser.LuaVersion # 默认为 '5.4' ---@param options? LuaParser.CompileOptions +---@return LuaParser.Status function M.compile(code, version, options) local status = class.new 'LuaParser.Status' (code, version, options) status:compile() diff --git a/src/parser/init.lua b/src/parser/init.lua index 7c6df81..fffe77d 100644 --- a/src/parser/init.lua +++ b/src/parser/init.lua @@ -3,6 +3,7 @@ local class = require 'class' ---@class LuaParser local M = class.declare 'LuaParser' +require 'parser.node' require 'parser.compile' return M diff --git a/src/parser/lexer.lua b/src/parser/lexer.lua index 578bf6e..3239a41 100644 --- a/src/parser/lexer.lua +++ b/src/parser/lexer.lua @@ -64,10 +64,14 @@ local M = class.declare 'Lexer' ---@param mode 'Lua' | 'Cat' function M:__init(code, mode) local results = Parser:match(code) + ---@type string[] self.tokens = {} -- 分离出来的词 + ---@type integer[] self.poses = {} -- 每个词的开始位置(偏移) + ---@type LuaParser.Lexer.Type[] self.types = {} -- 每个词的类型 - self.nls = {} -- 每个换行符的开始位置(偏移) + ---@type integer[] + self.nls = {} -- 每个换行符的结束位置(偏移) self.ci = 1 -- 当前词的索引 for i, res in ipairs(results) do if i % 3 == 1 then @@ -77,7 +81,7 @@ function M:__init(code, mode) elseif i % 3 == 0 then self.types[#self.types+1] = res if res == 'NL' then - self.nls[#self.nls+1] = results[i-2] + self.nls[#self.nls+1] = results[i-2] + #results[i-1] end end end @@ -96,12 +100,58 @@ end -- 消耗一个词,返回这个词 ---@param count? integer # 默认为1表示消耗一个词,2表示消耗两个词,以此类推 +---@return string +---@return LuaParser.Lexer.Type +---@return integer function M:next(count) local i = self.ci + (count or 1) local token = self.tokens[i] local tp = self.types[i] + local pos = self.poses[i] self.ci = i - return token, tp + return token, tp, pos +end + +-- 获取当前词的2侧光标位置 +---@param offset? integer # 偏移量,默认为0 +---@return integer +---@return integer +function M:getPos(offset) + local i = self.ci + (offset or 0) + local token = self.tokens[i] + local pos = self.poses[i] + return pos - 1, pos + #token - 1 +end + +-- 根据偏移量获取行列 +---@param offset integer +---@return integer row # 第一行是0 +---@return integer col # 第一列是0 +function M:rowcol(offset) + local nls = self.nls + + if #nls == 0 then + return 0, offset + end + + if offset < nls[1] then + return 0, offset + end + + -- 使用二分法查找 + local low, high = 1, #nls + while low <= high do + local mid = (low + high) // 2 + if offset < nls[mid] then + high = mid - 1 + elseif offset >= nls[mid+1] then + low = mid + 1 + else + return mid, offset - nls[mid] + end + end + + return 0, offset end local API = {} diff --git a/src/parser/node.lua b/src/parser/node.lua index 2b5ec69..1fc13ba 100644 --- a/src/parser/node.lua +++ b/src/parser/node.lua @@ -1,10 +1,78 @@ local class = require 'class' ----@class LuaParser.Ast.Node: Class.Base -local Node = class.declare 'LuaParser.Ast.Node' +---@alias LuaParser.Node.Type +---| 'nil' -Node.rowcolMulti = 10000 +---@class LuaParser.Node.Base: Class.Base +---@field status LuaParser.Status +---@field type LuaParser.Node.Type +---@field start integer # 开始位置(偏移) +---@field finish integer # 结束位置(偏移) +---@field left integer # 开始位置(行号与列号合并) +---@field right integer # 结束位置(行号与列号合并) +---@field startRow integer # 开始行号 +---@field startCol integer # 开始列号 +---@field finishRow integer # 结束行号 +---@field finishCol integer # 结束列号 +local Base = class.declare 'LuaParser.Node.Base' -Node.__getter.type = function () +Base.rowcolMulti = 10000 + +Base.__getter.type = function () error('No type') end + +---@param self LuaParser.Node.Base +---@return integer +Base.__getter.left = function (self) + local row, col = self.status.lexer:rowcol(self.start) + local start = row * self.rowcolMulti + col + self.left = start + return start +end + +---@param self LuaParser.Node.Base +---@return integer +Base.__getter.right = function (self) + local row, col = self.status.lexer:rowcol(self.finish) + local finish = row * self.rowcolMulti + col + self.right = finish + return finish +end + +---@param self LuaParser.Node.Base +---@return integer +Base.__getter.startRow = function (self) + local startRow = self.left // self.rowcolMulti + self.startRow = startRow + return startRow +end + +---@param self LuaParser.Node.Base +---@return integer +Base.__getter.startCol = function (self) + local startCol = self.left % self.rowcolMulti + self.startCol = startCol + return startCol +end + +---@param self LuaParser.Node.Base +---@return integer +Base.__getter.finishRow = function (self) + local finishRow = self.right // self.rowcolMulti + self.finishRow = finishRow + return finishRow +end + +---@param self LuaParser.Node.Base +---@return integer +Base.__getter.finishCol = function (self) + local finishCol = self.right % self.rowcolMulti + self.finishCol = finishCol + return finishCol +end + +---@class LuaParser.Node.Nil +local Nil = class.declare('LuaParser.Node.Nil', 'LuaParser.Node.Base') + +Nil.type = 'nil' diff --git a/test/ast/Nil.lua b/test/ast/Nil.lua index 81809b6..5933609 100644 --- a/test/ast/Nil.lua +++ b/test/ast/Nil.lua @@ -3,18 +3,23 @@ local parser = require 'parser' local function TEST(code) return function (expect) local status = parser.compile(code) + local node = status:parseNil() + assert(node) + for k, v in pairs(expect) do + assert(node[k] == v) + end end end -CHECK [[nil]] +TEST [[nil]] { - type = "nil", - start = 0, - finish = 3, + type = "nil", + left = 0, + right = 3, } -CHECK [[ nil]] +TEST [[ nil]] { - type = "nil", - start = 3, - finish = 6, + type = "nil", + left = 3, + right = 6, } diff --git a/test/ast/init.lua b/test/ast/init.lua index e92a4cd..4f19dd7 100644 --- a/test/ast/init.lua +++ b/test/ast/init.lua @@ -1,232 +1 @@ -local parser = require 'parser' -local fs = require 'bee.filesystem' -local utility = require 'utility' - -EXISTS = {} - -local function eq(a, b) - local tp1, tp2 = type(a), type(b) - if tp1 ~= tp2 then - return false - end - if a == '' and tp1 == 'table' then - return true - end - if b == '' and tp2 == 'table' then - return true - end - if tp1 == 'table' then - local checked = {} - for k in pairs(a) do - if not eq(a[k], b[k]) then - return false - end - checked[k] = true - end - for k in pairs(b) do - if not checked[k] then - return false - end - end - return true - end - if tp1 == 'number' then - return ('%q'):format(a) == ('%q'):format(b) - end - return a == b -end - ----@type {[string]: integer, [integer]: string} -local sortList = { - 'specials', - 'type', 'start', 'vstart', 'bstart', 'finish', 'effect', 'range', 'tindex', - 'tag', 'special', 'keyword', - 'parent', 'extParent', 'child', - 'filter', - 'vararg', - 'node', 'locPos', - 'op', 'args', - 'loc', 'init', 'max', 'step', 'keys', 'exps', 'call', 'func', - 'dot', 'colon', - 'field', 'index', 'method', - 'exp', 'value', 'vref', - 'attrs', 'escs', - 'locals', 'ref', 'returns', 'breaks', -} -for i, v in ipairs(sortList) do - sortList[v] = i -end -local ignoreList = { - 'specials', 'locals', 'ref', 'node', 'parent', 'extParent', 'returns', 'state', 'mirror', 'next', 'vararg', 'originalComment', 'typeCache', 'eachCache', 'bindSource' -} -local ignoreMap = {} -for i, v in ipairs(ignoreList) do - ignoreMap[v] = true -end - -IGNORE_MAP = ignoreMap - -local myOption = { - alignment = true, - sorter = function (keys, keymap) - table.sort(keys, function (a, b) - local tp1 = type(a) - local tp2 = type(b) - if tp1 == 'number' and tp2 ~= 'number' then - return false - end - if tp1 ~= 'number' and tp2 == 'number' then - return true - end - if tp1 == 'number' and tp2 == 'number' then - return a < b - end - local s1 = sortList[a] or 9999 - local s2 = sortList[b] or 9999 - if s1 == s2 then - return a < b - else - return s1 < s2 - end - end) - end, - loop = ('%q'):format(''), - number = function (n) - return ('%q'):format(n) - end, - format = setmetatable({}, { __index = function (_, key) - return function (value, _, _, _) - if ignoreMap[key] then - return '' - end - if type(key) == 'string' and key:sub(1, 1) == '_' then - return nil - end - return value - end - end}), -} - -local targetOption = { - alignment = true, - sorter = function (keys, keymap) - table.sort(keys, function (a, b) - local tp1 = type(a) - local tp2 = type(b) - if tp1 == 'number' and tp2 ~= 'number' then - return false - end - if tp1 ~= 'number' and tp2 == 'number' then - return true - end - if tp1 == 'number' and tp2 == 'number' then - return a < b - end - local s1 = sortList[a] or 9999 - local s2 = sortList[b] or 9999 - if s1 == s2 then - return a < b - else - return s1 < s2 - end - end) - end, - loop = ('%q'):format(''), - number = function (n) - return ('%q'):format(n) - end, -} - -local function autoFix(myBuf, targetBuf) - local info = debug.getinfo(3, 'Sl') - local filename = info.source:sub(2) - local fileBuf = utility.loadFile(filename) - assert(fileBuf) - local pos = fileBuf:find(targetBuf, 1, true) - local newFileBuf = fileBuf:sub(1, pos-1) .. myBuf .. fileBuf:sub(pos + #targetBuf) - utility.saveFile(filename, newFileBuf) -end - -local function test(type) - local mode = type - if mode == 'Dirty' then - mode = 'Lua' - end - CHECK = function (buf, opt) - return function (target_ast) - local state, err = parser.compile(buf, mode, 'Lua 5.4', opt) - if not state then - error(('语法树生成失败:%s'):format(err)) - end - state.ast.state = nil - local result = utility.dump(state.ast, myOption) - local expect = utility.dump(target_ast, targetOption) - if result ~= expect then - fs.create_directory(ROOT / 'test' / 'log') - utility.saveFile((ROOT / 'test' / 'log' / 'my_ast.ast'):string(), result) - utility.saveFile((ROOT / 'test' / 'log' / 'target_ast.ast'):string(), expect) - autoFix(result, expect) - error(('语法树不相等:%s\n%s'):format(type, buf)) - end - end - end - LuaDoc = function (buf) - return function (target_doc) - local state, err = parser.compile(buf, 'Lua', 'Lua 5.4') - if not state then - error(('语法树生成失败:%s'):format(err)) - end - parser.luadoc(state) - for _, doc in ipairs(state.ast.docs) do - doc.bindGroup = nil - doc.bindSources = nil - end - state.ast.docs.groups = nil - local result = utility.dump(state.ast.docs, myOption) - local expect = utility.dump(target_doc, targetOption) - if result ~= expect then - fs.create_directory(ROOT / 'test' / 'log') - utility.saveFile((ROOT / 'test' / 'log' / 'my_doc.ast'):string(), result) - utility.saveFile((ROOT / 'test' / 'log' / 'target_doc.ast'):string(), expect) - autoFix(result, expect) - error(('语法树不相等:%s\n%s'):format(type, buf)) - end - end - end - Comment = function (buf) - return function (target_comment) - local state, err = parser.compile(buf, mode, 'Lua 5.4', { - ['nonstandardSymbol'] = { - ['//'] = true, - }, - }) - if not state then - error(('语法树生成失败:%s'):format(err)) - end - state.ast.state = nil - local result = utility.dump(state.comms, myOption) - local expect = utility.dump(target_comment, targetOption) - if result ~= expect then - fs.create_directory(ROOT / 'test' / 'log') - utility.saveFile((ROOT / 'test' / 'log' / 'my_ast.ast'):string(), result) - utility.saveFile((ROOT / 'test' / 'log' / 'target_ast.ast'):string(), expect) - --autoFix(result, expect) - error(('语法树不相等:%s\n%s'):format(type, buf)) - end - end - end - require('ast.' .. type) -end - -require 'test.ast.nil' - -test 'Nil' -test 'Boolean' -test 'String' -test 'Number' -test 'Exp' -test 'Action' -test 'Lua' -test 'Dirty' -test 'LuaDoc' -test 'Comment' +require 'ast.nil' diff --git a/test/main.lua b/test/main.lua index feecb3f..80e8798 100644 --- a/test/main.lua +++ b/test/main.lua @@ -18,12 +18,12 @@ end local function main() --collectgarbage 'stop' unitTest 'ast' - unitTest 'grammar' + --unitTest 'grammar' --unitTest 'lines' - unitTest 'grammar_check' - unitTest 'syntax_check' + --unitTest 'grammar_check' + --unitTest 'syntax_check' --unitTest 'guide' - unitTest 'perform' + --unitTest 'perform' print('测试完成') end From 1a9f300294b0bf715fa10f22efb1739ec8795212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 6 Sep 2023 16:14:54 +0800 Subject: [PATCH 04/94] =?UTF-8?q?=E6=9B=B4=E6=96=B0boolean?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .luarc.json | 3 ++ src/parser.old/compile.lua | 2 +- src/parser/ast/ast.lua | 81 +++++++++++++++++++++++++++++++ src/parser/ast/boolean.lua | 31 ++++++++++++ src/parser/ast/nil.lua | 26 ++++++++++ src/parser/ast/number.lua | 0 src/parser/ast/string.lua | 0 src/parser/compile.lua | 98 +++----------------------------------- src/parser/node.lua | 16 +++---- src/utility.lua | 95 +++++++++++++++++++++++++++++++++--- test/ast/Nil.lua | 4 +- test/ast/boolean.lua | 27 +++++++++++ test/ast/init.lua | 5 +- test/ast/number.lua | 0 test/ast/string.lua | 0 test/main.lua | 6 +-- 16 files changed, 280 insertions(+), 114 deletions(-) create mode 100644 src/parser/ast/ast.lua create mode 100644 src/parser/ast/boolean.lua create mode 100644 src/parser/ast/nil.lua create mode 100644 src/parser/ast/number.lua create mode 100644 src/parser/ast/string.lua create mode 100644 test/ast/boolean.lua create mode 100644 test/ast/number.lua create mode 100644 test/ast/string.lua diff --git a/.luarc.json b/.luarc.json index 9dc2f02..184877a 100644 --- a/.luarc.json +++ b/.luarc.json @@ -1,6 +1,9 @@ { "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", "diagnostics": { + "disable": [ + "duplicate-doc-alias" + ], "globals": [ "root", "log", diff --git a/src/parser.old/compile.lua b/src/parser.old/compile.lua index 697c9ce..3b5799b 100644 --- a/src/parser.old/compile.lua +++ b/src/parser.old/compile.lua @@ -1,4 +1,4 @@ -local tokens = require 'src.parser.lexer' +local tokens = require 'parser.lexer' local guide = require 'parser.guide' local sbyte = string.byte diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua new file mode 100644 index 0000000..5e40ce2 --- /dev/null +++ b/src/parser/ast/ast.lua @@ -0,0 +1,81 @@ +local class = require 'class' +local lexer = require 'parser.lexer' + +require 'parser.ast.nil' +require 'parser.ast.boolean' +require 'parser.ast.number' +require 'parser.ast.string' + +---@class LuaParser.Ast +---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast +local M = class.declare 'LuaParser.Ast' + +-- Lua版本 +---@type LuaParser.LuaVersion +M.version = '5.4' +-- 是否为LuaJIT +M.jit = false +-- 是否支持Unicode标识符 +M.unicodeName = false + +---@param code string # lua代码 +---@param version? LuaParser.LuaVersion +---@param options? LuaParser.CompileOptions +function M:__init(code, version, options) + -- 代码内容 + self.code = code + self.version = version or '5.4' + -- 非标准符号的映射表 + self.nssymbolMap = {} + -- 词法分析结果 + self.lexer = lexer.parseLua(code) + self.index = 1 + + if options then + if options.nonestandardSymbols then + for _, s in ipairs(options.nonestandardSymbols) do + self.nssymbolMap[s] = true + end + end + self.jit = options.jit + self.unicodeName = options.unicodeName + end +end + +-- 跳过换行符 +---@return boolean # 是否成功跳过换行符 +function M:skipNL() + local _, tp = self.lexer:peek() + if tp == 'NL' then + self.lexer:next() + return true + end + return false +end + +-- 跳过注释 +---@param inState? boolean # 是否是语句 +---@return boolean # 是否成功跳过注释 +function M:skipComment(inState) + local token, tp = self.lexer:peek() + if token == '--' + or (token == '//' and (inState or self.nssymbolMap['//'])) then + + end +end + +-- 跳过注释 + +-- 跳过空白符 +---@param inState? boolean # 是否是语句 +function M:skipSpace(inState) + repeat until not self:skipNL() + and not self:skipComment(inState) +end + +-- 编译Lua代码 +function M:compile() + self:skipSpace() +end + +return M diff --git a/src/parser/ast/boolean.lua b/src/parser/ast/boolean.lua new file mode 100644 index 0000000..6b66025 --- /dev/null +++ b/src/parser/ast/boolean.lua @@ -0,0 +1,31 @@ +local class = require 'class' + +---@alias LuaParser.Node.Type 'boolean' + +---@class LuaParser.Ast +local M = class.declare 'LuaParser.Ast' + +---@return LuaParser.Node.Boolean? +function M:parseBoolean() + local token = self.lexer:peek() + if token == 'true' then + local start, finish = self.lexer:getPos() + self.lexer:next() + return class.new('LuaParser.Node.Boolean', { + ast = self, + start = start, + finish = finish, + value = true, + }) + end + if token == 'false' then + local start, finish = self.lexer:getPos() + self.lexer:next() + return class.new('LuaParser.Node.Boolean', { + ast = self, + start = start, + finish = finish, + value = false, + }) + end +end diff --git a/src/parser/ast/nil.lua b/src/parser/ast/nil.lua new file mode 100644 index 0000000..43a87ba --- /dev/null +++ b/src/parser/ast/nil.lua @@ -0,0 +1,26 @@ +local class = require 'class' + +---@alias LuaParser.Node.Type 'nil' + +---@class LuaParser.Node.Nil +local Nil = class.declare('LuaParser.Node.Nil', 'LuaParser.Node.Base') + +Nil.type = 'nil' + +---@class LuaParser.Ast +local M = class.declare 'LuaParser.Ast' + +---@return LuaParser.Node.Nil? +function M:parseNil() + local token = self.lexer:peek() + if token ~= 'nil' then + return nil + end + local start, finish = self.lexer:getPos() + self.lexer:next() + return class.new('LuaParser.Node.Nil', { + ast = self, + start = start, + finish = finish, + }) +end diff --git a/src/parser/ast/number.lua b/src/parser/ast/number.lua new file mode 100644 index 0000000..e69de29 diff --git a/src/parser/ast/string.lua b/src/parser/ast/string.lua new file mode 100644 index 0000000..e69de29 diff --git a/src/parser/compile.lua b/src/parser/compile.lua index b613e5f..a64a0f7 100644 --- a/src/parser/compile.lua +++ b/src/parser/compile.lua @@ -1,5 +1,6 @@ local class = require 'class' -local lexer = require 'parser.lexer' + +require 'parser.ast.ast' ---@class LuaParser local M = class.get 'LuaParser' @@ -23,100 +24,13 @@ local M = class.get 'LuaParser' ---@field nonestandardSymbols? LuaParser.NonestandardSymbol[] # 支持的非标准符号 ---@field unicodeName? boolean # 是否支持Unicode标识符,默认为 false ----@class LuaParser.Status ----@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Status -local S = class.declare 'LuaParser.Status' - --- Lua版本 ----@type LuaParser.LuaVersion -S.version = '5.4' --- 是否为LuaJIT -S.jit = false --- 是否支持Unicode标识符 -S.unicodeName = false - ----@param code string # lua代码 ----@param version? LuaParser.LuaVersion ----@param options? LuaParser.CompileOptions -function S:__init(code, version, options) - -- 代码内容 - self.code = code - self.version = version or '5.4' - -- 非标准符号的映射表 - self.nssymbolMap = {} - -- 词法分析结果 - self.lexer = lexer.parseLua(code) - self.index = 1 - - if options then - if options.nonestandardSymbols then - for _, s in ipairs(options.nonestandardSymbols) do - self.nssymbolMap[s] = true - end - end - self.jit = options.jit - self.unicodeName = options.unicodeName - end -end - --- 跳过换行符 ----@return boolean # 是否成功跳过换行符 -function S:skipNL() - local _, tp = self.lexer:peek() - if tp == 'NL' then - self.lexer:next() - return true - end - return false -end - --- 跳过注释 ----@param inState? boolean # 是否是语句 ----@return boolean # 是否成功跳过注释 -function S:skipComment(inState) - local token, tp = self.lexer:peek() - if token == '--' - or (token == '//' and (inState or self.nssymbolMap['//'])) then - - end -end - --- 跳过注释 - --- 跳过空白符 ----@param inState? boolean # 是否是语句 -function S:skipSpace(inState) - repeat until not self:skipNL() - and not self:skipComment(inState) -end - ----@return LuaParser.Node.Nil? -function S:parseNil() - local token = self.lexer:peek() - if token ~= 'nil' then - return nil - end - local start, finish = self.lexer:getPos() - self.lexer:next() - return class.new('LuaParser.Node.Nil', { - status = self, - start = start, - finish = finish, - }) -end - --- 编译Lua代码 -function S:compile() - self:skipSpace() -end - -- 编译lua代码 ---@param code string # lua代码 ---@param version? LuaParser.LuaVersion # 默认为 '5.4' ---@param options? LuaParser.CompileOptions ----@return LuaParser.Status +---@return LuaParser.Ast function M.compile(code, version, options) - local status = class.new 'LuaParser.Status' (code, version, options) - status:compile() - return status + local ast = class.new 'LuaParser.Ast' (code, version, options) + ast:compile() + return ast end diff --git a/src/parser/node.lua b/src/parser/node.lua index 1fc13ba..67b0289 100644 --- a/src/parser/node.lua +++ b/src/parser/node.lua @@ -1,10 +1,7 @@ local class = require 'class' ----@alias LuaParser.Node.Type ----| 'nil' - ---@class LuaParser.Node.Base: Class.Base ----@field status LuaParser.Status +---@field ast LuaParser.Ast ---@field type LuaParser.Node.Type ---@field start integer # 开始位置(偏移) ---@field finish integer # 结束位置(偏移) @@ -25,7 +22,7 @@ end ---@param self LuaParser.Node.Base ---@return integer Base.__getter.left = function (self) - local row, col = self.status.lexer:rowcol(self.start) + local row, col = self.ast.lexer:rowcol(self.start) local start = row * self.rowcolMulti + col self.left = start return start @@ -34,7 +31,7 @@ end ---@param self LuaParser.Node.Base ---@return integer Base.__getter.right = function (self) - local row, col = self.status.lexer:rowcol(self.finish) + local row, col = self.ast.lexer:rowcol(self.finish) local finish = row * self.rowcolMulti + col self.right = finish return finish @@ -72,7 +69,8 @@ Base.__getter.finishCol = function (self) return finishCol end ----@class LuaParser.Node.Nil -local Nil = class.declare('LuaParser.Node.Nil', 'LuaParser.Node.Base') +---@class LuaParser.Node.Boolean +---@field value boolean +local Boolean = class.declare('LuaParser.Node.Boolean', 'LuaParser.Node.Base') -Nil.type = 'nil' +Boolean.type = 'boolean' diff --git a/src/utility.lua b/src/utility.lua index 55965cf..55a00d5 100644 --- a/src/utility.lua +++ b/src/utility.lua @@ -283,6 +283,9 @@ end --- 读取文件 ---@param path string +---@param keepBom? boolean +---@return string? text +---@return string? errMsg function m.loadFile(path, keepBom) local f, e = ioOpen(path, 'rb') if not f then @@ -308,6 +311,8 @@ end --- 写入文件 ---@param path string ---@param content string +---@return boolean ok +---@return string? errMsg function m.saveFile(path, content) local f, e = ioOpen(path, "wb") @@ -336,7 +341,10 @@ function m.counter(init, step) end --- 排序后遍历 ----@param t table +---@generic K, V +---@param t table +---@param sorter? fun(a: K, b: K): boolean +---@return fun(): K, V function m.sortPairs(t, sorter) local keys = {} for k in pairs(t) do @@ -354,6 +362,7 @@ end --- 深拷贝(不处理元表) ---@param source table ---@param target? table +---@return table function m.deepCopy(source, target) local mark = {} local function copy(a, b) @@ -376,6 +385,8 @@ function m.deepCopy(source, target) end --- 序列化 +---@param t table +---@return table function m.unpack(t) local result = {} local tid = 0 @@ -403,6 +414,8 @@ function m.unpack(t) end --- 反序列化 +---@param t table +---@return table function m.pack(t) local cache = {} local function pack(id) @@ -515,18 +528,25 @@ function m.utf8Len(str, start, finish) return len end -function m.revertTable(t) - local len = #t +-- 把数组中的元素顺序*原地*反转 +---@param arr any[] +---@return any[] +function m.revertArray(arr) + local len = #arr if len <= 1 then - return t + return arr end for x = 1, len // 2 do local y = len - x + 1 - t[x], t[y] = t[y], t[x] + arr[x], arr[y] = arr[y], arr[x] end - return t + return arr end +-- 创建一个value-key表 +---@generic K, V +---@param t table +---@return table function m.revertMap(t) local nt = {} for k, v in pairs(t) do @@ -621,6 +641,11 @@ function m.eachLine(text, keepNL) end end +---@alias SortByScoreCallback fun(o: any): integer + +-- 按照分数排序,分数越高越靠前 +---@param tbl any[] +---@param callbacks SortByScoreCallback | SortByScoreCallback[] function m.sortByScore(tbl, callbacks) if type(callbacks) ~= 'table' then callbacks = { callbacks } @@ -648,6 +673,31 @@ function m.sortByScore(tbl, callbacks) end) end +---@param arr any[] +---@return SortByScoreCallback +function m.sortCallbackOfIndex(arr) + ---@type table + local indexMap = m.revertMap(arr) + return function (v) + return - indexMap[v] + end +end + +---@param datas any[] +---@param scores integer[] +---@return SortByScoreCallback +function m.sortCallbackOfScore(datas, scores) + local map = {} + for i = 1, #datas do + local data = datas[i] + local score = scores[i] + map[data] = score + end + return function (v) + return map[v] + end +end + ---裁剪字符串 ---@param str string ---@param mode? '"left"'|'"right"' @@ -730,6 +780,7 @@ function switchMT:has(name) end ---@param name string +---@param ... any ---@return ... function switchMT:__call(name, ...) local callback = self.map[name] or self._default @@ -749,6 +800,8 @@ function m.switch() end ---@param f async fun() +---@param name string +---@return any, boolean function m.getUpvalue(f, name) for i = 1, 999 do local uname, value = getupvalue(f, i) @@ -816,6 +869,7 @@ end ---@param t table ---@param sorter boolean|function +---@return any[] function m.getTableKeys(t, sorter) local keys = {} for k in pairs(t) do @@ -838,6 +892,15 @@ function m.arrayHas(array, value) return false end +function m.arrayIndexOf(array, value) + for i = 1, #array do + if array[i] == value then + return i + end + end + return nil +end + function m.arrayInsert(array, value) if not m.arrayHas(array, value) then array[#array+1] = value @@ -870,4 +933,24 @@ function m.cacheReturn(func) end end +---@param a table +---@param b table +---@return table +function m.tableMerge(a, b) + for k, v in pairs(b) do + a[k] = v + end + return a +end + +---@param a any[] +---@param b any[] +---@return any[] +function m.arrayMerge(a, b) + for i = 1, #b do + a[#a+1] = b[i] + end + return a +end + return m diff --git a/test/ast/Nil.lua b/test/ast/Nil.lua index 5933609..c7b92de 100644 --- a/test/ast/Nil.lua +++ b/test/ast/Nil.lua @@ -2,8 +2,8 @@ local parser = require 'parser' local function TEST(code) return function (expect) - local status = parser.compile(code) - local node = status:parseNil() + local ast = parser.compile(code) + local node = ast:parseNil() assert(node) for k, v in pairs(expect) do assert(node[k] == v) diff --git a/test/ast/boolean.lua b/test/ast/boolean.lua new file mode 100644 index 0000000..28c9db6 --- /dev/null +++ b/test/ast/boolean.lua @@ -0,0 +1,27 @@ +local parser = require 'parser' + +local function TEST(code) + return function (expect) + local ast = parser.compile(code) + local node = ast:parseBoolean() + assert(node) + for k, v in pairs(expect) do + assert(node[k] == v) + end + end +end + +TEST [[true]] +{ + type = "boolean", + left = 0, + finish = 4, + value = true, +} +TEST [[false]] +{ + type = "boolean", + left = 0, + right = 5, + value = false, +} diff --git a/test/ast/init.lua b/test/ast/init.lua index 4f19dd7..7e6762f 100644 --- a/test/ast/init.lua +++ b/test/ast/init.lua @@ -1 +1,4 @@ -require 'ast.nil' +require 'test.ast.nil' +require 'test.ast.boolean' +require 'test.ast.string' +require 'test.ast.number' diff --git a/test/ast/number.lua b/test/ast/number.lua new file mode 100644 index 0000000..e69de29 diff --git a/test/ast/string.lua b/test/ast/string.lua new file mode 100644 index 0000000..e69de29 diff --git a/test/main.lua b/test/main.lua index 80e8798..104b0b6 100644 --- a/test/main.lua +++ b/test/main.lua @@ -1,8 +1,8 @@ local root = arg[0] .. '\\..\\..' package.path = package.path .. ';' .. root .. '\\src\\?.lua' .. ';' .. root .. '\\src\\?\\init.lua' - .. ';' .. root .. '\\test\\?.lua' - .. ';' .. root .. '\\test\\?\\init.lua' + .. ';' .. root .. '\\?.lua' + .. ';' .. root .. '\\?\\init.lua' local fs = require 'bee.filesystem' @@ -11,7 +11,7 @@ rawset(_G, 'ROOT', fs.path(root)) local function unitTest(name) local clock = os.clock() print(('测试[%s]...'):format(name)) - require(name) + require('test.' .. name) print(('测试[%s]用时[%.3f]'):format(name, os.clock() - clock)) end From 0ab8732122cd1f73aa10a3a4a5c686fb60ec76e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 6 Sep 2023 18:36:38 +0800 Subject: [PATCH 05/94] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/class.lua | 6 +- src/parser/ast/ast.lua | 2 + src/parser/ast/boolean.lua | 11 ++- src/parser/ast/error.lua | 28 ++++++++ src/parser/ast/nil.lua | 5 +- src/parser/ast/number.lua | 140 +++++++++++++++++++++++++++++++++++++ src/parser/lexer.lua | 56 +++++++++++---- src/parser/node.lua | 37 +++++----- test/ast/number.lua | 105 ++++++++++++++++++++++++++++ 9 files changed, 354 insertions(+), 36 deletions(-) create mode 100644 src/parser/ast/error.lua diff --git a/src/class.lua b/src/class.lua index 2d6969e..0313e38 100644 --- a/src/class.lua +++ b/src/class.lua @@ -67,7 +67,11 @@ function M.declare(name, super) if r == nil then local f = getter[k] if f then - return f(self) + local res, needCache = f(self) + if needCache then + self[k] = res + end + return res else return nil end diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 5e40ce2..765ffaa 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -30,6 +30,8 @@ function M:__init(code, version, options) -- 词法分析结果 self.lexer = lexer.parseLua(code) self.index = 1 + -- 错误信息 + self.errors = {} if options then if options.nonestandardSymbols then diff --git a/src/parser/ast/boolean.lua b/src/parser/ast/boolean.lua index 6b66025..330b283 100644 --- a/src/parser/ast/boolean.lua +++ b/src/parser/ast/boolean.lua @@ -2,14 +2,21 @@ local class = require 'class' ---@alias LuaParser.Node.Type 'boolean' +---@class LuaParser.Node.Boolean: LuaParser.Node.Base +---@field value boolean +local Boolean = class.declare('LuaParser.Node.Boolean', 'LuaParser.Node.Base') + +Boolean.type = 'boolean' + ---@class LuaParser.Ast local M = class.declare 'LuaParser.Ast' +-- 解析布尔值 ---@return LuaParser.Node.Boolean? function M:parseBoolean() local token = self.lexer:peek() if token == 'true' then - local start, finish = self.lexer:getPos() + local start, finish = self.lexer:range() self.lexer:next() return class.new('LuaParser.Node.Boolean', { ast = self, @@ -19,7 +26,7 @@ function M:parseBoolean() }) end if token == 'false' then - local start, finish = self.lexer:getPos() + local start, finish = self.lexer:range() self.lexer:next() return class.new('LuaParser.Node.Boolean', { ast = self, diff --git a/src/parser/ast/error.lua b/src/parser/ast/error.lua new file mode 100644 index 0000000..dded9c9 --- /dev/null +++ b/src/parser/ast/error.lua @@ -0,0 +1,28 @@ +local class = require 'class' + +---@alias LuaParser.Node.Type 'error' + +---@class LuaParser.Node.Error +local Error = class.declare('LuaParser.Node.Error', 'LuaParser.Node.Base') + +Error.type = 'error' +Error.code = 'UNKNOWN' + +---@class LuaParser.Ast +---@field extra? table +local M = class.declare 'LuaParser.Ast' + +-- 添加错误信息 +---@param errorCode string +---@param start integer +---@param finish integer +---@param extra table? +function M:pushError(errorCode, start, finish, extra) + self.errors[#self.errors+1] = class.new('LuaParser.Node.Error', { + code = errorCode, + ast = self, + start = start, + finish = finish, + extra = extra, + }) +end diff --git a/src/parser/ast/nil.lua b/src/parser/ast/nil.lua index 43a87ba..16b9812 100644 --- a/src/parser/ast/nil.lua +++ b/src/parser/ast/nil.lua @@ -2,7 +2,7 @@ local class = require 'class' ---@alias LuaParser.Node.Type 'nil' ----@class LuaParser.Node.Nil +---@class LuaParser.Node.Nil: LuaParser.Node.Base local Nil = class.declare('LuaParser.Node.Nil', 'LuaParser.Node.Base') Nil.type = 'nil' @@ -10,13 +10,14 @@ Nil.type = 'nil' ---@class LuaParser.Ast local M = class.declare 'LuaParser.Ast' +-- 解析 nil ---@return LuaParser.Node.Nil? function M:parseNil() local token = self.lexer:peek() if token ~= 'nil' then return nil end - local start, finish = self.lexer:getPos() + local start, finish = self.lexer:range() self.lexer:next() return class.new('LuaParser.Node.Nil', { ast = self, diff --git a/src/parser/ast/number.lua b/src/parser/ast/number.lua index e69de29..d8535d0 100644 --- a/src/parser/ast/number.lua +++ b/src/parser/ast/number.lua @@ -0,0 +1,140 @@ +local class = require 'class' + +---@alias LuaParser.Node.Type 'Number' | 'Integer' + +---@class LuaParser.Node.Number: LuaParser.Node.Base +---@field value number +---@field numBase 2 | 10 | 16 +local Number = class.declare('LuaParser.Node.Number', 'LuaParser.Node.Base') + +Number.type = 'number' + +---@param self LuaParser.Node.Number +---@return number +---@return true +Number.__getter.value = function (self) + local value = tonumber(self.code) or 0.0 + return value, true +end + +---@param self LuaParser.Node.Number +---@return 2 | 10 | 16 +---@return true +Number.__getter.numBase = function (self) + local mark = self.code:sub(1, 2) + if mark == '0b' or mark == '0B' then + return 2, true + elseif mark == '0x' or mark == '0X' then + return 16, true + else + return 10, true + end +end + +---@class LuaParser.Node.Integer: LuaParser.Node.Base +---@field value integer +---@field numBase 2 | 10 | 16 +local Integer = class.declare('LuaParser.Node.Integer', 'LuaParser.Node.Base') + +Integer.type = 'integer' + +---@param self LuaParser.Node.Integer +---@return integer +---@return true +Integer.__getter.value = function (self) + local value = tonumber(self.code) or 0 --[[@as integer]] + return value, true +end + +---@param self LuaParser.Node.Integer +---@return 2 | 10 | 16 +---@return true +Integer.__getter.numBase = function (self) + local mark = self.code:sub(1, 2) + if mark == '0b' or mark == '0B' then + return 2, true + elseif mark == '0x' or mark == '0X' then + return 16, true + else + return 10, true + end +end + +---@class LuaParser.Ast +local M = class.declare 'LuaParser.Ast' + +-- 解析数字(可以带负号) +---@return LuaParser.Node.Number | LuaParser.Node.Integer | nil +function M:parseNumber() + local start = self.lexer:range() + self.lexer:skipToken '-' + + local node = self:parseNumber16() + or self:parseNumber2() + or self:parseNumber10() + if not node then + return nil + end + + ---@cast start -? + + node.start = start + + return node +end + +-- 解析十六进制数字(不支持负号) +---@return LuaParser.Node.Number | LuaParser.Node.Integer | nil +function M:parseNumber16() + local token, _, pos = self.lexer:peek() + + if token ~= '0' then + return nil + end + ---@cast pos -? + + local nextToken, _, nextPos = self.lexer:peek(1) + if nextToken ~= 'x' and nextToken ~= 'X' then + return nil + end + ---@cast nextPos -? + + -- 0x 必须连在一起 + if pos + 1 ~= nextPos then + return nil + end + + local curOffset = nextPos + 2 + + local intPart, intOffset = self.code:match('^([%da-fA-F]+)()', curOffset) + if intOffset then + curOffset = intOffset + end + + local numPart, numOffset = self.code:match('^%.([%da-fA-F]*)()', curOffset) + if numOffset then + curOffset = numOffset + end + + local expPart, expOffset = self.code:match('^[pP][+-]?(%d+)()') + if expOffset then + curOffset = expOffset + end + + if not intPart then + if not numPart then + self:pushError('MUST_X16', nextPos + 1, nextPos + 1) + end + if numPart == '' then + self:pushError('MUST_X16', numOffset - 1, numOffset - 1) + end + end + + if numPart or expPart then + return class.new('LuaParser.Node.Number', { + ast = self, + start = pos, + finish = curOffset - 1, + }) + end +end diff --git a/src/parser/lexer.lua b/src/parser/lexer.lua index 3239a41..e7efe83 100644 --- a/src/parser/lexer.lua +++ b/src/parser/lexer.lua @@ -67,21 +67,21 @@ function M:__init(code, mode) ---@type string[] self.tokens = {} -- 分离出来的词 ---@type integer[] - self.poses = {} -- 每个词的开始位置(偏移) + self.poses = {} -- 每个词的字节开始位置(光标位置,第一个字符为0) ---@type LuaParser.Lexer.Type[] self.types = {} -- 每个词的类型 ---@type integer[] - self.nls = {} -- 每个换行符的结束位置(偏移) + self.nls = {} -- 每个换行符的字节结束位置(下一行的开始位置) self.ci = 1 -- 当前词的索引 for i, res in ipairs(results) do if i % 3 == 1 then - self.poses[#self.poses+1] = res + self.poses[#self.poses+1] = res - 1 elseif i % 3 == 2 then self.tokens[#self.tokens+1] = res elseif i % 3 == 0 then self.types[#self.types+1] = res if res == 'NL' then - self.nls[#self.nls+1] = results[i-2] + #results[i-1] + self.nls[#self.nls+1] = results[i-2] + #results[i-1] + 1 end end end @@ -89,20 +89,22 @@ end -- 看看当前的词 ---@param next? integer # 默认为0表示当前的词,1表示下一个词,以此类推 ----@return string ----@return LuaParser.Lexer.Type +---@return string? +---@return LuaParser.Lexer.Type? +---@return integer? function M:peek(next) local i = self.ci + (next or 0) local token = self.tokens[i] local tp = self.types[i] - return token, tp + local pos = self.poses[i] + return token, tp, pos end -- 消耗一个词,返回这个词 ---@param count? integer # 默认为1表示消耗一个词,2表示消耗两个词,以此类推 ----@return string ----@return LuaParser.Lexer.Type ----@return integer +---@return string? +---@return LuaParser.Lexer.Type? +---@return integer? function M:next(count) local i = self.ci + (count or 1) local token = self.tokens[i] @@ -114,13 +116,39 @@ end -- 获取当前词的2侧光标位置 ---@param offset? integer # 偏移量,默认为0 ----@return integer ----@return integer -function M:getPos(offset) +---@return integer? +---@return integer? +function M:range(offset) local i = self.ci + (offset or 0) local token = self.tokens[i] local pos = self.poses[i] - return pos - 1, pos + #token - 1 + if not token then + return nil, nil + end + return pos, pos + #token +end + +-- 跳过一个指定的词,返回是否成功 +---@param token string +---@return boolean +function M:skipToken(token) + if self.tokens[self.ci] == token then + self.ci = self.ci + 1 + return true + end + return false +end + +-- 快进到某个光标位置 +---@param offset integer +function M:fastForward(offset) + for i = self.ci + 1, #self.poses do + if self.poses[i] >= offset then + self.ci = i + return + end + end + self.ci = #self.poses + 1 end -- 根据偏移量获取行列 diff --git a/src/parser/node.lua b/src/parser/node.lua index 67b0289..3852e4f 100644 --- a/src/parser/node.lua +++ b/src/parser/node.lua @@ -11,6 +11,7 @@ local class = require 'class' ---@field startCol integer # 开始列号 ---@field finishRow integer # 结束行号 ---@field finishCol integer # 结束列号 +---@field code string # 对应的代码 local Base = class.declare 'LuaParser.Node.Base' Base.rowcolMulti = 10000 @@ -21,56 +22,58 @@ end ---@param self LuaParser.Node.Base ---@return integer +---@return true Base.__getter.left = function (self) local row, col = self.ast.lexer:rowcol(self.start) local start = row * self.rowcolMulti + col - self.left = start - return start + return start, true end ---@param self LuaParser.Node.Base ---@return integer +---@return true Base.__getter.right = function (self) local row, col = self.ast.lexer:rowcol(self.finish) local finish = row * self.rowcolMulti + col - self.right = finish - return finish + return finish, true end ---@param self LuaParser.Node.Base ---@return integer +---@return true Base.__getter.startRow = function (self) local startRow = self.left // self.rowcolMulti - self.startRow = startRow - return startRow + return startRow, true end ---@param self LuaParser.Node.Base ---@return integer +---@return true Base.__getter.startCol = function (self) local startCol = self.left % self.rowcolMulti - self.startCol = startCol - return startCol + return startCol, true end ---@param self LuaParser.Node.Base ---@return integer +---@return true Base.__getter.finishRow = function (self) local finishRow = self.right // self.rowcolMulti - self.finishRow = finishRow - return finishRow + return finishRow, true end ---@param self LuaParser.Node.Base ---@return integer +---@return true Base.__getter.finishCol = function (self) local finishCol = self.right % self.rowcolMulti - self.finishCol = finishCol - return finishCol + return finishCol, true end ----@class LuaParser.Node.Boolean ----@field value boolean -local Boolean = class.declare('LuaParser.Node.Boolean', 'LuaParser.Node.Base') - -Boolean.type = 'boolean' +---@param self LuaParser.Node.Base +---@return string +---@return true +Base.__getter.code = function (self) + local code = self.ast.code:sub(self.start + 1, self.finish) + return code, true +end diff --git a/test/ast/number.lua b/test/ast/number.lua index e69de29..affbf07 100644 --- a/test/ast/number.lua +++ b/test/ast/number.lua @@ -0,0 +1,105 @@ +local parser = require 'parser' + +local function TEST(code) + return function (expect) + local ast = parser.compile(code) + local node = ast:parseBoolean() + assert(node) + for k, v in pairs(expect) do + assert(node[k] == v) + end + end +end + +TEST '345' +{ + type = "integer", + left = 0, + right = 3, + value = 345, + numBase = 10, +} +TEST '345.0' +{ + type = "number", + left = 0, + right = 5, + value = 0x1.59p+8, + numBase = 10, +} +TEST '0xff' +{ + type = "integer", + left = 0, + right = 4, + value = 255, + numBase = 16, +} +TEST '314.16e-2' +{ + type = "number", + left = 0, + right = 9, + value = 3.1416, + numBase = 10, +} +TEST '0.31416E1' +{ + type = "number", + left = 0, + right = 9, + value = 0x1.921ff2e48e8a7p+1, + numBase = 10, +} +TEST '.31416E1' +{ + type = "number", + left = 0, + right = 8, + value = 0x1.921ff2e48e8a7p+1, + numBase = 10, +} +TEST '34e1' +{ + type = "number", + left = 0, + right = 4, + value = 0x1.54p+8, + numBase = 10, +} +TEST '0x0.1E' +{ + type = "number", + left = 0, + right = 6, + value = 0x1.ep-4, + numBase = 16, +} +TEST '0xA23p-4' +{ + type = "number", + left = 0, + right = 8, + value = 0x1.446p+7, +} +TEST '0X1.921FB54442D18P+1' +{ + type = "number", + left = 0, + right = 20, + value = 0x1.921fb54442d18p+1, +} +TEST '-345' +{ + type = "integer", + left = 0, + right = 4, + value = -345, +} +TEST '0b110110' +{ + type = "integer", + left = 0, + right = 8, + value = 54, +} From 5ca7320dba6da200a2ff698f8c2302e297cb416f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 7 Sep 2023 15:22:34 +0800 Subject: [PATCH 06/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=E6=95=B0=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 11 +- src/parser/ast/number.lua | 258 ++++++++++++++++++++++++++++++++------ src/parser/compile.lua | 8 +- src/parser/lexer.lua | 9 ++ test/ast/number.lua | 81 ++++++++++-- 5 files changed, 310 insertions(+), 57 deletions(-) diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 765ffaa..2a16c03 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -1,6 +1,7 @@ local class = require 'class' local lexer = require 'parser.lexer' +require 'parser.ast.error' require 'parser.ast.nil' require 'parser.ast.boolean' require 'parser.ast.number' @@ -12,7 +13,7 @@ local M = class.declare 'LuaParser.Ast' -- Lua版本 ---@type LuaParser.LuaVersion -M.version = '5.4' +M.version = 'Lua 5.4' -- 是否为LuaJIT M.jit = false -- 是否支持Unicode标识符 @@ -24,7 +25,7 @@ M.unicodeName = false function M:__init(code, version, options) -- 代码内容 self.code = code - self.version = version or '5.4' + self.version = version or 'Lua 5.4' -- 非标准符号的映射表 self.nssymbolMap = {} -- 词法分析结果 @@ -45,6 +46,7 @@ function M:__init(code, version, options) end -- 跳过换行符 +---@private ---@return boolean # 是否成功跳过换行符 function M:skipNL() local _, tp = self.lexer:peek() @@ -56,19 +58,20 @@ function M:skipNL() end -- 跳过注释 +---@private ---@param inState? boolean # 是否是语句 ---@return boolean # 是否成功跳过注释 function M:skipComment(inState) - local token, tp = self.lexer:peek() + local token = self.lexer:peek() if token == '--' or (token == '//' and (inState or self.nssymbolMap['//'])) then end end --- 跳过注释 -- 跳过空白符 +---@private ---@param inState? boolean # 是否是语句 function M:skipSpace(inState) repeat until not self:skipNL() diff --git a/src/parser/ast/number.lua b/src/parser/ast/number.lua index d8535d0..b0b69d6 100644 --- a/src/parser/ast/number.lua +++ b/src/parser/ast/number.lua @@ -1,26 +1,22 @@ local class = require 'class' ----@alias LuaParser.Node.Type 'Number' | 'Integer' +---@alias LuaParser.Node.Type 'Float' | 'Integer' ----@class LuaParser.Node.Number: LuaParser.Node.Base +---@class LuaParser.Node.Float: LuaParser.Node.Base ---@field value number +---@field valuei? number # 虚数 ---@field numBase 2 | 10 | 16 -local Number = class.declare('LuaParser.Node.Number', 'LuaParser.Node.Base') +---@field view string +local Float = class.declare('LuaParser.Node.Float', 'LuaParser.Node.Base') -Number.type = 'number' +Float.type = 'float' ----@param self LuaParser.Node.Number ----@return number ----@return true -Number.__getter.value = function (self) - local value = tonumber(self.code) or 0.0 - return value, true -end +Float.value = 0.0 ----@param self LuaParser.Node.Number +---@param self LuaParser.Node.Float ---@return 2 | 10 | 16 ---@return true -Number.__getter.numBase = function (self) +Float.__getter.numBase = function (self) local mark = self.code:sub(1, 2) if mark == '0b' or mark == '0B' then return 2, true @@ -31,20 +27,32 @@ Number.__getter.numBase = function (self) end end +---@param self LuaParser.Node.Float +---@return string +---@return true +Float.__getter.view = function (self) + local num = self.valuei or self.value + local view = ('%.10f'):format(num):gsub('0+$', '') + if view:sub(-1) == '.' then + view = view .. '0' + end + if self.valuei then + view = ('0+%si'):format(view) + end + return view, true +end + ---@class LuaParser.Node.Integer: LuaParser.Node.Base ---@field value integer +---@field valuei? number # 虚数 ---@field numBase 2 | 10 | 16 +---@field intTail? 'LL' | 'ULL' +---@field view string local Integer = class.declare('LuaParser.Node.Integer', 'LuaParser.Node.Base') Integer.type = 'integer' ----@param self LuaParser.Node.Integer ----@return integer ----@return true -Integer.__getter.value = function (self) - local value = tonumber(self.code) or 0 --[[@as integer]] - return value, true -end +Integer.value = 0 ---@param self LuaParser.Node.Integer ---@return 2 | 10 | 16 @@ -60,31 +68,133 @@ Integer.__getter.numBase = function (self) end end +---@param self LuaParser.Node.Integer +---@return string +---@return true +Integer.__getter.view = function (self) + local view = tostring(self.valuei or self.value) + if self.intTail then + view = view .. self.intTail + end + if self.valuei then + view = ('0+%si'):format(view) + end + return view, true +end + ---@class LuaParser.Ast local M = class.declare 'LuaParser.Ast' -- 解析数字(可以带负号) ----@return LuaParser.Node.Number | LuaParser.Node.Integer | nil +---@return LuaParser.Node.Float | LuaParser.Node.Integer | nil function M:parseNumber() + local savePoint = self.lexer:savePoint() local start = self.lexer:range() - self.lexer:skipToken '-' + local neg = self.lexer:skipToken '-' local node = self:parseNumber16() or self:parseNumber2() or self:parseNumber10() if not node then + savePoint() return nil end ---@cast start -? node.start = start + if neg then + node.value = - node.value + if node.valuei then + node.valuei = - node.valuei + end + end return node end +---@private +---@param curOffset integer +function M:fastForwardNumber(curOffset) + local word = self.code:match('^[%.%w_\x80-\xff]+', curOffset) + if not word then + self.lexer:fastForward(curOffset - 1) + return + end + self:pushError('MALFORMED_NUMBER', curOffset - 1, curOffset - 1 + #word) + self.lexer:fastForward(curOffset - 1 + #word) +end + +---@private +---@param curOffset integer +---@return boolean +function M:parseNumberI(curOffset) + if self.code:find('^[iI]', curOffset) then + if not self.jit then + self:pushError('UNSUPPORT_SYMBOL', curOffset, curOffset, { + version = self.version, + needVer = 'LuaJIT', + }) + end + return true + end + return false +end + +---@private +---@param value number? +---@param startPos integer +---@param curOffset integer +---@return LuaParser.Node.Float +function M:buildFloat(value, startPos, curOffset) + local valuei + if self:parseNumberI(curOffset) then + curOffset = curOffset + 1 + valuei = value + end + self:fastForwardNumber(curOffset) + return class.new('LuaParser.Node.Float', { + ast = self, + start = startPos, + finish = curOffset - 1, + value = valuei and 0.0 or value, + valuei = valuei, + }) +end + +---@private +---@param value integer? +---@param startPos integer +---@param curOffset integer +---@return LuaParser.Node.Integer +function M:buildInteger(value, startPos, curOffset) + local valuei, intTail + if self:parseNumberI(curOffset) then + curOffset = curOffset + 1 + valuei = value + else + if self.code:find('^[uU][lL][lL]', curOffset) + or self.code:find('^[lL][lL][uU]', curOffset) then + curOffset = curOffset + 3 + intTail = 'ULL' + elseif self.code:find('^[lL][lL]', curOffset) then + curOffset = curOffset + 2 + intTail = 'LL' + end + end + self:fastForwardNumber(curOffset) + return class.new('LuaParser.Node.Integer', { + ast = self, + start = startPos, + finish = curOffset - 1, + value = valuei and 0 or value, + valuei = valuei, + intTail = intTail, + }) +end + -- 解析十六进制数字(不支持负号) ----@return LuaParser.Node.Number | LuaParser.Node.Integer | nil +---@return LuaParser.Node.Float | LuaParser.Node.Integer | nil function M:parseNumber16() local token, _, pos = self.lexer:peek() @@ -92,19 +202,11 @@ function M:parseNumber16() return nil end ---@cast pos -? - - local nextToken, _, nextPos = self.lexer:peek(1) - if nextToken ~= 'x' and nextToken ~= 'X' then - return nil - end - ---@cast nextPos -? - - -- 0x 必须连在一起 - if pos + 1 ~= nextPos then - return nil + if not self.code:match('^[xX]', pos + 2) then + return end - local curOffset = nextPos + 2 + local curOffset = pos + 3 local intPart, intOffset = self.code:match('^([%da-fA-F]+)()', curOffset) if intOffset then @@ -116,14 +218,17 @@ function M:parseNumber16() curOffset = numOffset end - local expPart, expOffset = self.code:match('^[pP][+-]?(%d+)()') + local expPart, expOffset = self.code:match('^[pP][+-]?(%d*)()', curOffset) if expOffset then curOffset = expOffset + if #expPart == 0 then + self:pushError('MISS_EXPONENT', curOffset - 1, curOffset - 1) + end end if not intPart then if not numPart then - self:pushError('MUST_X16', nextPos + 1, nextPos + 1) + self:pushError('MUST_X16', pos + 2, pos + 2) end if numPart == '' then self:pushError('MUST_X16', numOffset - 1, numOffset - 1) @@ -131,10 +236,83 @@ function M:parseNumber16() end if numPart or expPart then - return class.new('LuaParser.Node.Number', { - ast = self, - start = pos, - finish = curOffset - 1, + local value = tonumber(self.code:sub(pos + 1, curOffset - 1)) + return self:buildFloat(value, pos, curOffset) + else + local value = math.tointeger(self.code:sub(pos + 1, curOffset - 1)) + return self:buildInteger(value, pos, curOffset) + end +end + +-- 解析二进制数字(不支持负号) +---@return LuaParser.Node.Integer | nil +function M:parseNumber2() + local token, _, pos = self.lexer:peek() + + if token ~= '0' then + return nil + end + ---@cast pos -? + if not self.code:match('^[bB]', pos + 2) then + return + end + + local bins = self.code:match('^([01]*)', pos + 3) + local curOffset = pos + 3 + #bins + local value = tonumber(bins, 2) + + if not self.jit then + self:pushError('UNSUPPORT_SYMBOL', pos + 2, curOffset - 1, { + version = self.version, + needVer = 'LuaJIT', }) end + + return self:buildInteger(value, pos, curOffset) +end + +-- 解析十进制数字(不支持负号) +---@return LuaParser.Node.Float | LuaParser.Node.Integer | nil +function M:parseNumber10() + local token, tp, pos = self.lexer:peek() + if token ~= '.' and tp ~= 'Num' then + return nil + end + ---@cast pos -? + + local curOffset = pos + 1 + + local intPart, intOffset = self.code:match('^(%d+)()', curOffset) + if intOffset then + curOffset = intOffset + end + + local numPart, numOffset = self.code:match('^%.(%d*)()', curOffset) + if numOffset then + curOffset = numOffset + end + + local expPart, expOffset = self.code:match('^[eE][+-]?(%d*)()', curOffset) + if expOffset then + curOffset = expOffset + if #expPart == 0 then + self:pushError('MISS_EXPONENT', curOffset - 1, curOffset - 1) + end + end + + if not intPart then + if numPart == '' then + self.lexer:next() + self:pushError('UNKNOWN_SYMBOL', pos, pos + 1) + return nil + end + end + + if numPart or expPart then + local value = tonumber(self.code:sub(pos + 1, curOffset - 1)) + return self:buildFloat(value, pos, curOffset) + else + local value = math.tointeger(self.code:sub(pos + 1, curOffset - 1)) + return self:buildInteger(value, pos, curOffset) + end end diff --git a/src/parser/compile.lua b/src/parser/compile.lua index a64a0f7..c947e31 100644 --- a/src/parser/compile.lua +++ b/src/parser/compile.lua @@ -6,10 +6,10 @@ require 'parser.ast.ast' local M = class.get 'LuaParser' ---@alias LuaParser.LuaVersion ----| '5.1' ----| '5.2' ----| '5.3' ----| '5.4' +---| 'Lua 5.1' +---| 'Lua 5.2' +---| 'Lua 5.3' +---| 'Lua 5.4' ---@alias LuaParser.NonestandardSymbol ---|'//' | '/**/' diff --git a/src/parser/lexer.lua b/src/parser/lexer.lua index e7efe83..f8a1f85 100644 --- a/src/parser/lexer.lua +++ b/src/parser/lexer.lua @@ -182,6 +182,15 @@ function M:rowcol(offset) return 0, offset end +-- 设置保存点 +---@return fun() +function M:savePoint() + local ci = self.ci + return function () + self.ci = ci + end +end + local API = {} -- 对Lua代码进行分词 diff --git a/test/ast/number.lua b/test/ast/number.lua index affbf07..460ef28 100644 --- a/test/ast/number.lua +++ b/test/ast/number.lua @@ -3,7 +3,7 @@ local parser = require 'parser' local function TEST(code) return function (expect) local ast = parser.compile(code) - local node = ast:parseBoolean() + local node = ast:parseNumber() assert(node) for k, v in pairs(expect) do assert(node[k] == v) @@ -18,14 +18,16 @@ TEST '345' right = 3, value = 345, numBase = 10, + view = '345', } TEST '345.0' { - type = "number", + type = "float", left = 0, right = 5, value = 0x1.59p+8, numBase = 10, + view = '345.0', } TEST '0xff' { @@ -34,60 +36,70 @@ TEST '0xff' right = 4, value = 255, numBase = 16, + view = '255', } TEST '314.16e-2' { - type = "number", + type = "float", left = 0, right = 9, value = 3.1416, numBase = 10, + view = '3.1416', } TEST '0.31416E1' { - type = "number", + type = "float", left = 0, right = 9, value = 0x1.921ff2e48e8a7p+1, numBase = 10, + view = '3.1416', } TEST '.31416E1' { - type = "number", + type = "float", left = 0, right = 8, value = 0x1.921ff2e48e8a7p+1, numBase = 10, + view = '3.1416', } TEST '34e1' { - type = "number", + type = "float", left = 0, right = 4, value = 0x1.54p+8, numBase = 10, + view = '340.0', } TEST '0x0.1E' { - type = "number", + type = "float", left = 0, right = 6, value = 0x1.ep-4, numBase = 16, + view = '0.1171875', } TEST '0xA23p-4' { - type = "number", + type = "float", left = 0, right = 8, value = 0x1.446p+7, + numBase = 16, + view = '162.1875' } TEST '0X1.921FB54442D18P+1' { - type = "number", + type = "float", left = 0, right = 20, value = 0x1.921fb54442d18p+1, + numBase = 16, + view = '3.1415926536' } TEST '-345' { @@ -95,6 +107,8 @@ TEST '-345' left = 0, right = 4, value = -345, + numBase = 10, + view = '-345', } TEST '0b110110' { @@ -102,4 +116,53 @@ TEST '0b110110' left = 0, right = 8, value = 54, + numBase = 2, + view = '54', +} +TEST '123ll' +{ + type = "integer", + left = 0, + right = 5, + value = 123, + numBase = 10, + view = '123LL', +} +TEST '123ull' +{ + type = "integer", + left = 0, + right = 6, + value = 123, + numBase = 10, + view = '123ULL', +} +TEST '123llu' +{ + type = "integer", + left = 0, + right = 6, + value = 123, + numBase = 10, + view = '123ULL', +} +TEST '123i' +{ + type = "integer", + left = 0, + right = 4, + value = 0, + valuei = 123, + numBase = 10, + view = '0+123i', +} +TEST '123.45i' +{ + type = "float", + left = 0, + right = 7, + value = 0, + valuei = 123.45, + numBase = 10, + view = '0+123.45i', } From b72bd9f689a805ae6a339cfe8040b845549bf056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 7 Sep 2023 19:44:09 +0800 Subject: [PATCH 07/94] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 1 - src/parser/ast/boolean.lua | 5 + src/parser/ast/error.lua | 9 ++ src/parser/ast/nil.lua | 2 + src/parser/ast/number.lua | 15 ++- src/parser/ast/string.lua | 177 +++++++++++++++++++++++++++++ src/parser/lexer.lua | 12 ++ test/ast/Nil.lua | 4 +- test/ast/boolean.lua | 4 +- test/ast/init.lua | 12 ++ test/ast/number.lua | 4 +- test/ast/string.lua | 226 +++++++++++++++++++++++++++++++++++++ 12 files changed, 459 insertions(+), 12 deletions(-) diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 2a16c03..d8721f7 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -69,7 +69,6 @@ function M:skipComment(inState) end end - -- 跳过空白符 ---@private ---@param inState? boolean # 是否是语句 diff --git a/src/parser/ast/boolean.lua b/src/parser/ast/boolean.lua index 330b283..d8ddf9a 100644 --- a/src/parser/ast/boolean.lua +++ b/src/parser/ast/boolean.lua @@ -4,10 +4,15 @@ local class = require 'class' ---@class LuaParser.Node.Boolean: LuaParser.Node.Base ---@field value boolean +---@field view string local Boolean = class.declare('LuaParser.Node.Boolean', 'LuaParser.Node.Base') Boolean.type = 'boolean' +Boolean.__getter.view = function (self) + return tostring(self.value), true +end + ---@class LuaParser.Ast local M = class.declare 'LuaParser.Ast' diff --git a/src/parser/ast/error.lua b/src/parser/ast/error.lua index dded9c9..6d00ad7 100644 --- a/src/parser/ast/error.lua +++ b/src/parser/ast/error.lua @@ -26,3 +26,12 @@ function M:pushError(errorCode, start, finish, extra) extra = extra, }) end + +-- 添加错误“缺少符号” +---@param start integer +---@param symbol string +function M:pushErrorMissSymbol(start, symbol) + self:pushError('MISS_SYMBOL', start, start, { + symbol = symbol, + }) +end diff --git a/src/parser/ast/nil.lua b/src/parser/ast/nil.lua index 16b9812..1d576d9 100644 --- a/src/parser/ast/nil.lua +++ b/src/parser/ast/nil.lua @@ -7,6 +7,8 @@ local Nil = class.declare('LuaParser.Node.Nil', 'LuaParser.Node.Base') Nil.type = 'nil' +Nil.view = 'nil' + ---@class LuaParser.Ast local M = class.declare 'LuaParser.Ast' diff --git a/src/parser/ast/number.lua b/src/parser/ast/number.lua index b0b69d6..622a9e9 100644 --- a/src/parser/ast/number.lua +++ b/src/parser/ast/number.lua @@ -88,7 +88,19 @@ local M = class.declare 'LuaParser.Ast' -- 解析数字(可以带负号) ---@return LuaParser.Node.Float | LuaParser.Node.Integer | nil function M:parseNumber() - local savePoint = self.lexer:savePoint() + -- 快速判断是否为数字 + if self.lexer:peek() == '-' then + local token, tp = self.lexer:peek(1) + if token ~= '.' and tp ~= 'Num' then + return + end + else + local token, tp = self.lexer:peek() + if token ~= '.' and tp ~= 'Num' then + return + end + end + local start = self.lexer:range() local neg = self.lexer:skipToken '-' @@ -96,7 +108,6 @@ function M:parseNumber() or self:parseNumber2() or self:parseNumber10() if not node then - savePoint() return nil end diff --git a/src/parser/ast/string.lua b/src/parser/ast/string.lua index e69de29..fd10373 100644 --- a/src/parser/ast/string.lua +++ b/src/parser/ast/string.lua @@ -0,0 +1,177 @@ +local class = require 'class' +local util = require 'utility' + +---@alias LuaParser.Node.Type 'String' + +---@class LuaParser.Node.String: LuaParser.Node.Base +---@field value string +---@field view string +---@field quo string +---@field escs table|false +---@field missQuo? true +local String = class.declare('LuaParser.Node.String', 'LuaParser.Node.Base') + +String.type = 'string' + +---@param self LuaParser.Node.String +---@return string +---@return true +String.__getter.value = function (self) + if self.quo == "'" + or self.quo == '"' then + local value + if self.missQuo then + value = self.ast.code:sub(self.start + 2, self.finish) + else + value = self.ast.code:sub(self.start + 2, self.finish - 1) + end + if not value:find '\\' then + return value, true + end + local pieces = {} + + return value, true + else + local value + if self.missQuo then + value = self.ast.code:sub(self.start + #self.quo + 1, self.finish - 1) + else + value = self.ast.code:sub(self.start + #self.quo + 1, self.finish - #self.quo) + end + value = self.code + : gsub('\r\n', '\n') + : gsub('\r', '\n') + : gsub('^\n', '') + return value, true + end +end + +---@param self LuaParser.Node.String +---@return string +---@return true +String.__getter.view = function (self) + return util.viewString(self.value, self.quo), true +end + +---@class LuaParser.Ast +local M = class.declare 'LuaParser.Ast' + +-- 解析字符串 +---@return LuaParser.Node.String? +function M:parseString() + local token = self.lexer:peek() + if token == '"' + or token == "'" then + return self:parseShortString() + end + if token == '[' then + return self:parseLongString() + end + return nil +end + +-- 解析短字符串 +---@return LuaParser.Node.String? +function M:parseShortString() + local quo, _, pos = self.lexer:peek() + if quo ~= '"' and quo ~= "'" and quo ~= '`' then + return nil + end + ---@cast quo -? + ---@cast pos -? + + if quo == '`' and not self.nssymbolMap['`'] then + self:pushError('ERR_NONSTANDARD_SYMBOL', pos, pos + 1) + end + + local curOffset = pos + 2 + local missQuo + while true do + local char, offset = self.code:match([[([\\\r\n'"`])()]], curOffset) + if char == quo then + curOffset = offset + break + end + if not char then + curOffset = #self.code + 1 + missQuo = true + self:pushErrorMissSymbol(curOffset - 1, quo) + break + end + if char == '\r' + or char == '\n' then + curOffset = offset + missQuo = true + self:pushErrorMissSymbol(curOffset - 1, quo) + break + end + if char == '\\' then + self.lexer:fastForward(offset - 1) + local curToken, curType, curPos = self.lexer:peek() + if curType == 'NL' then + curOffset = curPos + #curToken + 1 + goto continue + end + if curToken == 'z' then + repeat until not self.lexer:skipType 'NL' + local _, _, afterPos = self.lexer:peek() + curOffset = afterPos and (afterPos + 1) or (#self.code + 1) + goto continue + end + end + curOffset = offset + ::continue:: + end + + local finishPos = curOffset - 1 + if quo == '`' then + quo = '"' + end + + return class.new('LuaParser.Node.String', { + ast = self, + start = pos, + finish = finishPos, + quo = quo, + missQuo = missQuo, + }) +end + +-- 解析长字符串 +---@return LuaParser.Node.String? +function M:parseLongString() + local _, _, pos = self.lexer:peek() + local quo = self.code:match('^(%[=*%[)', pos + 1) + if not quo then + return nil + end + + local missQuo + local finishQuo = quo:gsub('%[', ']') + local finishPos + local finishOffset = self.code:find(finishQuo, pos + #quo + 1, true) + if finishOffset then + finishPos = finishOffset + #finishQuo - 1 + else + finishPos = #self.code + missQuo = true + self:pushErrorMissSymbol(finishPos, finishQuo) + end + + if quo == '[[' and self.version == 'Lua 5.1' then + local nestOffset = self.code:find('[[', pos + #quo + 1, true) + if nestOffset and nestOffset < finishOffset then + self:pushError('NESTING_LONG_MARK', nestOffset - 1, nestOffset + 1) + end + end + + self.lexer:fastForward(finishPos) + + return class.new('LuaParser.Node.String', { + ast = self, + start = pos, + finish = finishPos, + quo = quo, + missQuo = missQuo, + }) +end diff --git a/src/parser/lexer.lua b/src/parser/lexer.lua index f8a1f85..f0f4253 100644 --- a/src/parser/lexer.lua +++ b/src/parser/lexer.lua @@ -64,6 +64,7 @@ local M = class.declare 'Lexer' ---@param mode 'Lua' | 'Cat' function M:__init(code, mode) local results = Parser:match(code) + self.len = #code -- 总长度 ---@type string[] self.tokens = {} -- 分离出来的词 ---@type integer[] @@ -139,6 +140,17 @@ function M:skipToken(token) return false end +-- 跳过一个指定的类型,返回是否成功 +---@param tp LuaParser.Lexer.Type +---@return boolean +function M:skipType(tp) + if self.types[self.ci] == tp then + self.ci = self.ci + 1 + return true + end + return false +end + -- 快进到某个光标位置 ---@param offset integer function M:fastForward(offset) diff --git a/test/ast/Nil.lua b/test/ast/Nil.lua index c7b92de..27263c1 100644 --- a/test/ast/Nil.lua +++ b/test/ast/Nil.lua @@ -5,9 +5,7 @@ local function TEST(code) local ast = parser.compile(code) local node = ast:parseNil() assert(node) - for k, v in pairs(expect) do - assert(node[k] == v) - end + Match(node, expect) end end diff --git a/test/ast/boolean.lua b/test/ast/boolean.lua index 28c9db6..68fd7e2 100644 --- a/test/ast/boolean.lua +++ b/test/ast/boolean.lua @@ -5,9 +5,7 @@ local function TEST(code) local ast = parser.compile(code) local node = ast:parseBoolean() assert(node) - for k, v in pairs(expect) do - assert(node[k] == v) - end + Match(node, expect) end end diff --git a/test/ast/init.lua b/test/ast/init.lua index 7e6762f..8c7c455 100644 --- a/test/ast/init.lua +++ b/test/ast/init.lua @@ -1,3 +1,15 @@ +---@param result any +---@param expect any +function Match(result, expect) + for k, v in pairs(expect) do + if type(v) == 'table' then + Match(result[k], v) + else + assert(result[k] == v) + end + end +end + require 'test.ast.nil' require 'test.ast.boolean' require 'test.ast.string' diff --git a/test/ast/number.lua b/test/ast/number.lua index 460ef28..9d667fe 100644 --- a/test/ast/number.lua +++ b/test/ast/number.lua @@ -5,9 +5,7 @@ local function TEST(code) local ast = parser.compile(code) local node = ast:parseNumber() assert(node) - for k, v in pairs(expect) do - assert(node[k] == v) - end + Match(node, expect) end end diff --git a/test/ast/string.lua b/test/ast/string.lua index e69de29..52ba6bc 100644 --- a/test/ast/string.lua +++ b/test/ast/string.lua @@ -0,0 +1,226 @@ +local parser = require 'parser' + +local function TEST(code) + return function (expect) + local ast = parser.compile(code) + local node = ast:parseString() + assert(node) + Match(node, expect) + end +end + +TEST [['123']] +{ + type = "string", + left = 0, + right = 5, + value = "123", + quo = "'", +} +TEST [['123\'']] +{ + type = "string", + left = 0, + right = 7, + escs = { + [1] = 4, + [2] = 6, + [3] = "normal", + }, + value = "123'", + quo = "'", +} +TEST [['123\z + 345']] +{ + type = "string", + left = 0, + right = 10008, + escs = { + [1] = 4, + [2] = 6, + [3] = "normal", + }, + value = "123345", + quo = "'", +} + +TEST [['123\ +345']] +{ + type = "string", + left = 0, + right = 10004, + escs = { + [1] = 4, + [2] = 5, + [3] = "normal", + }, + value = "123\ +345", + quo = "'", +} +TEST [===[[[123]]]===] +{ + type = "string", + left = 0, + right = 7, + value = "123", + quo = "[[", +} +TEST [===[[[123 +345]]]===] +{ + type = "string", + left = 0, + right = 10005, + value = "123\ +345", + quo = "[[", +} +TEST [['alo\n123"']] +{ + type = "string", + left = 0, + right = 11, + escs = { + [1] = 4, + [2] = 6, + [3] = "normal", + }, + value = "alo\ +123\"", + quo = "'", +} +TEST [['\97lo\10\04923"']] +{ + type = "string", + left = 0, + right = 17, + escs = { + [1] = 1, + [2] = 4, + [3] = "byte", + [4] = 6, + [5] = 9, + [6] = "byte", + [7] = 9, + [8] = 13, + [9] = "byte", + }, + value = "alo\ +123\"", + quo = "'", +} +TEST [['\xff']] +{ + type = "string", + left = 0, + right = 6, + escs = { + [1] = 1, + [2] = 6, + [3] = "byte", + }, + value = "\xff", + quo = "'", +} +TEST [['\x1A']] +{ + type = "string", + left = 0, + right = 6, + escs = { + [1] = 1, + [2] = 6, + [3] = "byte", + }, + value = "\26", + quo = "'", +} +TEST [['\492']] +{ + type = "string", + left = 0, + right = 6, + escs = { + [1] = 1, + [2] = 5, + [3] = "byte", + }, + value = "", + quo = "'", +} +TEST [['\u{3b1}']] +{ + type = "string", + left = 0, + right = 9, + escs = { + [1] = 1, + [2] = 9, + [3] = "unicode", + }, + value = "α", + quo = "'", +} +TEST [['\u{0}']] +{ + type = "string", + left = 0, + right = 7, + escs = { + [1] = 1, + [2] = 7, + [3] = "unicode", + }, + value = "\0", + quo = "'", +} +TEST [['\u{ffffff}']] +{ + type = "string", + left = 0, + right = 12, + escs = { + [1] = 1, + [2] = 12, + [3] = "unicode", + }, + value = "", + quo = "'", +} +TEST [=[[[ +abcdef]]]=] +{ + type = "string", + left = 0, + right = 10008, + value = "abcdef", + quo = "[[", +} +TEST [['aaa]] +{ + type = "string", + left = 0, + right = 4, + value = "aaa", + quo = "'", +} +TEST [['aaa +]] +{ + type = "string", + left = 0, + right = 4, + value = "aaa", + quo = "'", +} +TEST [[`12345`]] +{ + type = "string", + left = 0, + right = 7, + value = "12345", + quo = "`", +} From 1b06052eea6c63fd724ea98985eec00ef952deb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Fri, 8 Sep 2023 16:03:58 +0800 Subject: [PATCH 08/94] =?UTF-8?q?=E5=AD=97=E7=AC=A6=E4=B8=B2=E8=A7=A3?= =?UTF-8?q?=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 16 ++--- src/parser/ast/string.lua | 148 +++++++++++++++++++++++++++++++------- src/parser/lexer.lua | 12 ++-- src/parser/node.lua | 14 ++-- test/ast/string.lua | 5 +- 5 files changed, 148 insertions(+), 47 deletions(-) diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index d8721f7..51f365e 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -11,20 +11,14 @@ require 'parser.ast.string' ---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast local M = class.declare 'LuaParser.Ast' --- Lua版本 ----@type LuaParser.LuaVersion -M.version = 'Lua 5.4' --- 是否为LuaJIT -M.jit = false --- 是否支持Unicode标识符 -M.unicodeName = false - ---@param code string # lua代码 ---@param version? LuaParser.LuaVersion ---@param options? LuaParser.CompileOptions function M:__init(code, version, options) -- 代码内容 self.code = code + -- Lua版本 + ---@type LuaParser.LuaVersion self.version = version or 'Lua 5.4' -- 非标准符号的映射表 self.nssymbolMap = {} @@ -34,13 +28,19 @@ function M:__init(code, version, options) -- 错误信息 self.errors = {} + local major, minor = self.version:match 'Lua (%d+)%.(%d+)' + ---@type integer + self.versionNum = major * 10 + minor + if options then if options.nonestandardSymbols then for _, s in ipairs(options.nonestandardSymbols) do self.nssymbolMap[s] = true end end + -- 是否为LuaJIT self.jit = options.jit + -- 是否支持Unicode标识符 self.unicodeName = options.unicodeName end end diff --git a/src/parser/ast/string.lua b/src/parser/ast/string.lua index fd10373..a1b04f8 100644 --- a/src/parser/ast/string.lua +++ b/src/parser/ast/string.lua @@ -7,38 +7,40 @@ local util = require 'utility' ---@field value string ---@field view string ---@field quo string ----@field escs table|false +---@field escs? table ---@field missQuo? true local String = class.declare('LuaParser.Node.String', 'LuaParser.Node.Base') String.type = 'string' +local escMap = { + ['a'] = '\a', + ['b'] = '\b', + ['f'] = '\f', + ['n'] = '\n', + ['r'] = '\r', + ['t'] = '\t', + ['v'] = '\v', + ['\\'] = '\\', + ['\''] = '\'', + ['\"'] = '\"', +} + ---@param self LuaParser.Node.String ---@return string ---@return true String.__getter.value = function (self) if self.quo == "'" or self.quo == '"' then - local value - if self.missQuo then - value = self.ast.code:sub(self.start + 2, self.finish) - else - value = self.ast.code:sub(self.start + 2, self.finish - 1) - end - if not value:find '\\' then - return value, true - end - local pieces = {} - - return value, true + error('短字符串应该在解析时求值!') else - local value + local raw if self.missQuo then - value = self.ast.code:sub(self.start + #self.quo + 1, self.finish - 1) + raw = self.ast.code:sub(self.start + #self.quo + 1, self.finish - 1) else - value = self.ast.code:sub(self.start + #self.quo + 1, self.finish - #self.quo) + raw = self.ast.code:sub(self.start + #self.quo + 1, self.finish - #self.quo) end - value = self.code + local value = raw : gsub('\r\n', '\n') : gsub('\r', '\n') : gsub('^\n', '') @@ -61,7 +63,8 @@ local M = class.declare 'LuaParser.Ast' function M:parseString() local token = self.lexer:peek() if token == '"' - or token == "'" then + or token == "'" + or token == '`' then return self:parseShortString() end if token == '[' then @@ -84,46 +87,139 @@ function M:parseShortString() self:pushError('ERR_NONSTANDARD_SYMBOL', pos, pos + 1) end + local pieces = {} local curOffset = pos + 2 - local missQuo while true do - local char, offset = self.code:match([[([\\\r\n'"`])()]], curOffset) + local char, offset = self.code:match('([\\\r\n\'"`])()', curOffset) if char == quo then + pieces[#pieces+1] = self.code:sub(curOffset, offset - 2) curOffset = offset break end if not char then + pieces[#pieces+1] = self.code:sub(curOffset) curOffset = #self.code + 1 - missQuo = true self:pushErrorMissSymbol(curOffset - 1, quo) break end if char == '\r' or char == '\n' then - curOffset = offset - missQuo = true + pieces[#pieces+1] = self.code:sub(curOffset, offset - 2) + curOffset = offset - 1 self:pushErrorMissSymbol(curOffset - 1, quo) break end if char == '\\' then + if not pieces then + pieces = {} + end + pieces[#pieces+1] = self.code:sub(curOffset, offset - 2) self.lexer:fastForward(offset - 1) local curToken, curType, curPos = self.lexer:peek() + if not curToken then + curOffset = offset + goto continue + end + ---@cast curPos -? if curType == 'NL' then curOffset = curPos + #curToken + 1 + pieces[#pieces+1] = '\n' goto continue end - if curToken == 'z' then + if curType == 'Num' then + local numWord = curToken:sub(1, 3) + curOffset = offset + #numWord + local num = math.tointeger(numWord) + if num and num >= 0 and num <= 255 then + pieces[#pieces+1] = string.char(num) + else + self:pushError('ERR_ESC_DEC', curPos, curPos + #numWord, { + min = '000', + max = '255', + }) + end + goto continue + end + local escChar = self.code:sub(offset, offset) + if escChar == 'z' then + self.lexer:fastForward(offset) repeat until not self.lexer:skipType 'NL' local _, _, afterPos = self.lexer:peek() curOffset = afterPos and (afterPos + 1) or (#self.code + 1) goto continue end + if escChar == 'x' then + local code = self.code:match('^%x%x', offset + 1) + if code then + curOffset = offset + 3 + local num = tonumber(code, 16) + pieces[#pieces+1] = string.char(num) + else + curOffset = offset + 1 + self:pushError('ERR_ESC_X', curPos, curPos + 1) + end + if self.versionNum <= 51 then + self:pushError('ERR_ESC', curPos, curPos + 3) + end + goto continue + end + if escChar == 'u' then + local leftP, word, rightP, tailOffset = self.code:match('^({)(%w*)(}?)()', offset + 1) + if not leftP then + self:pushErrorMissSymbol(offset - 1, '{') + curOffset = offset + goto continue + end + if #word == 0 then + self:pushError('UTF8_SMALL', offset + 1, offset + 1) + else + local num = tonumber(word, 16) + if num then + if self.versionNum >= 54 then + if num < 0 or num > 0x7FFFFFFF then + self:pushError('UTF8_MAX', offset + 1, offset + #word, { + min = '00000000', + max = '7FFFFFFF', + }) + end + else + self:pushError('UTF8_MAX', offset + 1, offset + #word, { + min = '000000', + max = '10FFFF', + needVer = num <= 0x7FFFFFFF and 'Lua 5.4' or nil, + }) + end + if num >= 0 and num <= 0x7FFFFFFF then + pieces[#pieces+1] = utf8.char(num) + end + else + self:pushError('MUST_X16', offset + 1, offset + #word) + end + end + if rightP == '' then + self:pushErrorMissSymbol(tailOffset - 1, '}') + end + curOffset = tailOffset + goto continue + end + if escMap[escChar] then + curOffset = offset + 1 + pieces[#pieces+1] = escMap[escChar] + goto continue + else + curOffset = offset + 1 + self:pushError('ERR_ESC', offset - 2, offset) + goto continue + end + goto continue end + pieces[#pieces+1] = self.code:sub(curOffset, offset - 1) curOffset = offset ::continue:: end local finishPos = curOffset - 1 + self.lexer:fastForward(finishPos) if quo == '`' then quo = '"' end @@ -133,7 +229,7 @@ function M:parseShortString() start = pos, finish = finishPos, quo = quo, - missQuo = missQuo, + value = table.concat(pieces), }) end @@ -158,7 +254,7 @@ function M:parseLongString() self:pushErrorMissSymbol(finishPos, finishQuo) end - if quo == '[[' and self.version == 'Lua 5.1' then + if quo == '[[' and self.versionNum <= 51 then local nestOffset = self.code:find('[[', pos + #quo + 1, true) if nestOffset and nestOffset < finishOffset then self:pushError('NESTING_LONG_MARK', nestOffset - 1, nestOffset + 1) diff --git a/src/parser/lexer.lua b/src/parser/lexer.lua index f0f4253..d097ce6 100644 --- a/src/parser/lexer.lua +++ b/src/parser/lexer.lua @@ -82,7 +82,7 @@ function M:__init(code, mode) elseif i % 3 == 0 then self.types[#self.types+1] = res if res == 'NL' then - self.nls[#self.nls+1] = results[i-2] + #results[i-1] + 1 + self.nls[#self.nls+1] = results[i-2] + #results[i-1] - 1 end end end @@ -152,10 +152,10 @@ function M:skipType(tp) end -- 快进到某个光标位置 ----@param offset integer -function M:fastForward(offset) +---@param pos integer +function M:fastForward(pos) for i = self.ci + 1, #self.poses do - if self.poses[i] >= offset then + if self.poses[i] >= pos then self.ci = i return end @@ -178,6 +178,10 @@ function M:rowcol(offset) return 0, offset end + if offset >= nls[#nls] then + return #nls, offset - nls[#nls] + end + -- 使用二分法查找 local low, high = 1, #nls while low <= high do diff --git a/src/parser/node.lua b/src/parser/node.lua index 3852e4f..e204060 100644 --- a/src/parser/node.lua +++ b/src/parser/node.lua @@ -14,7 +14,7 @@ local class = require 'class' ---@field code string # 对应的代码 local Base = class.declare 'LuaParser.Node.Base' -Base.rowcolMulti = 10000 +local rowcolMulti = 10000 Base.__getter.type = function () error('No type') @@ -25,7 +25,7 @@ end ---@return true Base.__getter.left = function (self) local row, col = self.ast.lexer:rowcol(self.start) - local start = row * self.rowcolMulti + col + local start = row * rowcolMulti + col return start, true end @@ -34,7 +34,7 @@ end ---@return true Base.__getter.right = function (self) local row, col = self.ast.lexer:rowcol(self.finish) - local finish = row * self.rowcolMulti + col + local finish = row * rowcolMulti + col return finish, true end @@ -42,7 +42,7 @@ end ---@return integer ---@return true Base.__getter.startRow = function (self) - local startRow = self.left // self.rowcolMulti + local startRow = self.left // rowcolMulti return startRow, true end @@ -50,7 +50,7 @@ end ---@return integer ---@return true Base.__getter.startCol = function (self) - local startCol = self.left % self.rowcolMulti + local startCol = self.left % rowcolMulti return startCol, true end @@ -58,7 +58,7 @@ end ---@return integer ---@return true Base.__getter.finishRow = function (self) - local finishRow = self.right // self.rowcolMulti + local finishRow = self.right // rowcolMulti return finishRow, true end @@ -66,7 +66,7 @@ end ---@return integer ---@return true Base.__getter.finishCol = function (self) - local finishCol = self.right % self.rowcolMulti + local finishCol = self.right % rowcolMulti return finishCol, true end diff --git a/test/ast/string.lua b/test/ast/string.lua index 52ba6bc..486bb98 100644 --- a/test/ast/string.lua +++ b/test/ast/string.lua @@ -5,6 +5,7 @@ local function TEST(code) local ast = parser.compile(code) local node = ast:parseString() assert(node) + expect.escs = nil Match(node, expect) end end @@ -187,7 +188,7 @@ TEST [['\u{ffffff}']] [2] = 12, [3] = "unicode", }, - value = "", + value = "\u{ffffff}", quo = "'", } TEST [=[[[ @@ -222,5 +223,5 @@ TEST [[`12345`]] left = 0, right = 7, value = "12345", - quo = "`", + quo = '"', } From 9a8a40ce0e8dad0cf27cf17c88a71488c64e5c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Fri, 8 Sep 2023 16:27:31 +0800 Subject: [PATCH 09/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=E8=BD=AC=E4=B9=89?= =?UTF-8?q?=E5=8C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/string.lua | 37 ++++++++++++++++++++++++---- test/ast/string.lua | 52 +++++++++++++++++++++++++++++++++------ 2 files changed, 77 insertions(+), 12 deletions(-) diff --git a/src/parser/ast/string.lua b/src/parser/ast/string.lua index a1b04f8..a4a34f4 100644 --- a/src/parser/ast/string.lua +++ b/src/parser/ast/string.lua @@ -3,6 +3,8 @@ local util = require 'utility' ---@alias LuaParser.Node.Type 'String' +---@alias LuaParser.EscMode 'normal' | 'unicode' | 'err' | 'byte' + ---@class LuaParser.Node.String: LuaParser.Node.Base ---@field value string ---@field view string @@ -88,6 +90,22 @@ function M:parseShortString() end local pieces = {} + local escs + + ---@param mode LuaParser.EscMode + ---@param start integer + ---@param finish integer + local function pushEsc(mode, start, finish) + if not escs then + escs = {} + end + + local i = #escs + escs[i+1] = start + escs[i+2] = finish + escs[i+3] = mode + end + local curOffset = pos + 2 while true do local char, offset = self.code:match('([\\\r\n\'"`])()', curOffset) @@ -110,29 +128,30 @@ function M:parseShortString() break end if char == '\\' then - if not pieces then - pieces = {} - end pieces[#pieces+1] = self.code:sub(curOffset, offset - 2) self.lexer:fastForward(offset - 1) local curToken, curType, curPos = self.lexer:peek() if not curToken then + pushEsc('err', offset - 2, offset - 1) curOffset = offset goto continue end ---@cast curPos -? - if curType == 'NL' then + if curType == 'NL' and curPos == offset - 1 then + pushEsc('normal', offset - 2, offset - 1) curOffset = curPos + #curToken + 1 pieces[#pieces+1] = '\n' goto continue end - if curType == 'Num' then + if curType == 'Num' and curPos == offset - 1 then local numWord = curToken:sub(1, 3) curOffset = offset + #numWord local num = math.tointeger(numWord) if num and num >= 0 and num <= 255 then + pushEsc('byte', offset - 2, offset - 1 + #numWord) pieces[#pieces+1] = string.char(num) else + pushEsc('err', offset - 2, offset - 1 + #numWord) self:pushError('ERR_ESC_DEC', curPos, curPos + #numWord, { min = '000', max = '255', @@ -142,6 +161,7 @@ function M:parseShortString() end local escChar = self.code:sub(offset, offset) if escChar == 'z' then + pushEsc('normal', offset - 2, offset) self.lexer:fastForward(offset) repeat until not self.lexer:skipType 'NL' local _, _, afterPos = self.lexer:peek() @@ -151,10 +171,12 @@ function M:parseShortString() if escChar == 'x' then local code = self.code:match('^%x%x', offset + 1) if code then + pushEsc('byte', offset - 2, offset + 2) curOffset = offset + 3 local num = tonumber(code, 16) pieces[#pieces+1] = string.char(num) else + pushEsc('err', offset - 2, offset - 1) curOffset = offset + 1 self:pushError('ERR_ESC_X', curPos, curPos + 1) end @@ -166,10 +188,12 @@ function M:parseShortString() if escChar == 'u' then local leftP, word, rightP, tailOffset = self.code:match('^({)(%w*)(}?)()', offset + 1) if not leftP then + pushEsc('err', offset - 2, offset - 1) self:pushErrorMissSymbol(offset - 1, '{') curOffset = offset goto continue end + pushEsc('unicode', offset - 2, tailOffset - 1) if #word == 0 then self:pushError('UTF8_SMALL', offset + 1, offset + 1) else @@ -203,10 +227,12 @@ function M:parseShortString() goto continue end if escMap[escChar] then + pushEsc('normal', offset - 2, offset) curOffset = offset + 1 pieces[#pieces+1] = escMap[escChar] goto continue else + pushEsc('err', offset - 2, offset) curOffset = offset + 1 self:pushError('ERR_ESC', offset - 2, offset) goto continue @@ -230,6 +256,7 @@ function M:parseShortString() finish = finishPos, quo = quo, value = table.concat(pieces), + escs = escs, }) end diff --git a/test/ast/string.lua b/test/ast/string.lua index 486bb98..7cef45e 100644 --- a/test/ast/string.lua +++ b/test/ast/string.lua @@ -5,7 +5,6 @@ local function TEST(code) local ast = parser.compile(code) local node = ast:parseString() assert(node) - expect.escs = nil Match(node, expect) end end @@ -120,7 +119,7 @@ TEST [['\xff']] right = 6, escs = { [1] = 1, - [2] = 6, + [2] = 5, [3] = "byte", }, value = "\xff", @@ -133,13 +132,26 @@ TEST [['\x1A']] right = 6, escs = { [1] = 1, - [2] = 6, + [2] = 5, [3] = "byte", }, value = "\26", quo = "'", } -TEST [['\492']] +TEST [['\32']] +{ + type = "string", + left = 0, + right = 5, + escs = { + [1] = 1, + [2] = 4, + [3] = "byte", + }, + value = " ", + quo = "'", +} +TEST [['\123']] { type = "string", left = 0, @@ -149,6 +161,32 @@ TEST [['\492']] [2] = 5, [3] = "byte", }, + value = "{", + quo = "'", +} +TEST [['\0123']] +{ + type = "string", + left = 0, + right = 7, + escs = { + [1] = 1, + [2] = 5, + [3] = "byte", + }, + value = "\0123", + quo = "'", +} +TEST [['\492']] +{ + type = "string", + left = 0, + right = 6, + escs = { + [1] = 1, + [2] = 5, + [3] = "err", + }, value = "", quo = "'", } @@ -159,7 +197,7 @@ TEST [['\u{3b1}']] right = 9, escs = { [1] = 1, - [2] = 9, + [2] = 8, [3] = "unicode", }, value = "α", @@ -172,7 +210,7 @@ TEST [['\u{0}']] right = 7, escs = { [1] = 1, - [2] = 7, + [2] = 6, [3] = "unicode", }, value = "\0", @@ -185,7 +223,7 @@ TEST [['\u{ffffff}']] right = 12, escs = { [1] = 1, - [2] = 12, + [2] = 11, [3] = "unicode", }, value = "\u{ffffff}", From 4d816a99b35a622c2b0d26cc1638163e1081b8c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Fri, 8 Sep 2023 16:47:54 +0800 Subject: [PATCH 10/94] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E4=B8=B2=E7=9A=84view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/string.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/parser/ast/string.lua b/src/parser/ast/string.lua index a4a34f4..4472a9e 100644 --- a/src/parser/ast/string.lua +++ b/src/parser/ast/string.lua @@ -1,5 +1,4 @@ local class = require 'class' -local util = require 'utility' ---@alias LuaParser.Node.Type 'String' @@ -54,7 +53,7 @@ end ---@return string ---@return true String.__getter.view = function (self) - return util.viewString(self.value, self.quo), true + return self.value, true end ---@class LuaParser.Ast From bdbd43f2c335ba48d9030e54c1cc8b0a6464134e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Fri, 8 Sep 2023 18:24:05 +0800 Subject: [PATCH 11/94] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 1 + src/parser/ast/comment.lua | 10 ++++++++ test/ast/comment.lua | 50 ++++++++++++++++++++++++++++++++++++++ test/ast/init.lua | 1 + 4 files changed, 62 insertions(+) create mode 100644 src/parser/ast/comment.lua create mode 100644 test/ast/comment.lua diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 51f365e..2d93865 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -6,6 +6,7 @@ require 'parser.ast.nil' require 'parser.ast.boolean' require 'parser.ast.number' require 'parser.ast.string' +require 'parser.ast.comment' ---@class LuaParser.Ast ---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast diff --git a/src/parser/ast/comment.lua b/src/parser/ast/comment.lua new file mode 100644 index 0000000..5ee50ce --- /dev/null +++ b/src/parser/ast/comment.lua @@ -0,0 +1,10 @@ +local class = require 'class' + +---@alias LuaParser.LuaParser.Node.Type 'comment' + +---@class LuaParser.Node.Comment: LuaParser.Node.Base +---@field subtype 'short' | 'long' +---@field nonStandard? true +local Comment = class.declare('LuaParser.Node.Comment', 'LuaParser.Node.Base') + +Comment.type = 'comment' diff --git a/test/ast/comment.lua b/test/ast/comment.lua new file mode 100644 index 0000000..0edd733 --- /dev/null +++ b/test/ast/comment.lua @@ -0,0 +1,50 @@ +local parser = require 'parser' + +local function TEST(code) + return function (expect) + local ast = parser.compile(code) + local node = ast:parseNil() + assert(node) + Match(node, expect) + end +end + +TEST [[--AAA]] +{ + [1] = { + type = "comment", + left = 0, + right = 5, + }, +} + +TEST [[ +--AAA +]] +{ + [1] = { + type = "comment", + left = 0, + right = 5, + }, +} + +TEST [[//AAA]] +{ + [1] = { + type = "comment", + left = 0, + right = 5, + }, +} + +TEST [[ +//AAA +]] +{ + [1] = { + type = "comment", + left = 0, + right = 5, + }, +} diff --git a/test/ast/init.lua b/test/ast/init.lua index 8c7c455..bf8dc96 100644 --- a/test/ast/init.lua +++ b/test/ast/init.lua @@ -14,3 +14,4 @@ require 'test.ast.nil' require 'test.ast.boolean' require 'test.ast.string' require 'test.ast.number' +require 'test.ast.comment' From e997c2da869a5100e3927421148ebf05a38450f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Mon, 11 Sep 2023 12:18:33 +0800 Subject: [PATCH 12/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/comment.lua | 91 +++++++++++++++++++++++++++++++++++++- test/ast/comment.lua | 56 +++++++++++------------ 2 files changed, 118 insertions(+), 29 deletions(-) diff --git a/src/parser/ast/comment.lua b/src/parser/ast/comment.lua index 5ee50ce..388970f 100644 --- a/src/parser/ast/comment.lua +++ b/src/parser/ast/comment.lua @@ -4,7 +4,96 @@ local class = require 'class' ---@class LuaParser.Node.Comment: LuaParser.Node.Base ---@field subtype 'short' | 'long' ----@field nonStandard? true +---@field value string local Comment = class.declare('LuaParser.Node.Comment', 'LuaParser.Node.Base') Comment.type = 'comment' + +---@param self LuaParser.Node.Comment +---@return string +---@return true +Comment.__getter.value = function (self) + if self.subtype == 'short' then + return self.code:sub(3), true + else + if self.code:sub(1, 2) == '--' then + local quo = self.code:match('^(%[=*%[)', 3) + return self.code:sub(3 + #quo, - 1 - #quo), true + else + -- 格式为`/**/` + return self.code:sub(3, -3), true + end + end +end + +---@class LuaParser.Ast +local M = class.declare 'LuaParser.Ast' + +---@param inState? boolean +---@return LuaParser.Node.Comment? +function M:parseComment(inState) + return self:parseLongComment() + or self:parseShortComment(inState) +end + +---@param inState? boolean +---@return LuaParser.Node.Comment? +function M:parseShortComment(inState) + local token, _, pos = self.lexer:peek() + if not token then + return nil + end + ---@cast pos -? + if token == '--' + or (token == '//' and (inState or self.nssymbolMap['//'])) then + if token == '//' and not self.nssymbolMap['//'] then + self:pushError('ERR_COMMENT_PREFIX', pos, pos + 2) + end + local offset = self.code:find('[\r\n]', pos + 2) or (#self.code + 1) + self.lexer:fastForward(offset) + return class.new('LuaParser.Node.Comment', { + ast = self, + subtype = 'short', + start = pos, + finish = offset - 1, + }) + end + return nil +end + +---@return LuaParser.Node.Comment? +function M:parseLongComment() + local token, _, pos = self.lexer:peek() + if not token then + return nil + end + ---@cast pos -? + local finishQuo + if token == '--' then + local quo = self.code:match('^(%[=*%[)', pos + 3) + if not quo then + return nil + end + finishQuo = quo:gsub('%[', ']') + elseif token == '/*' then + finishQuo = '*/' + if not self.nssymbolMap['/**/'] then + self:pushError('ERR_C_LONG_COMMENT', pos, pos + 1) + end + else + return nil + end + local offset = self.code:find(finishQuo, pos + 3, true) + if offset then + self.lexer:fastForward(offset + #finishQuo - 1) + else + self:pushErrorMissSymbol(#self.code, finishQuo) + self.lexer:fastForward(#self.code) + end + return class.new('LuaParser.Node.Comment', { + ast = self, + subtype = 'long', + start = pos, + finish = offset and (offset + #finishQuo - 1) or #self.code, + }) +end diff --git a/test/ast/comment.lua b/test/ast/comment.lua index 0edd733..f97ae5c 100644 --- a/test/ast/comment.lua +++ b/test/ast/comment.lua @@ -3,7 +3,7 @@ local parser = require 'parser' local function TEST(code) return function (expect) local ast = parser.compile(code) - local node = ast:parseNil() + local node = ast:parseComment(true) assert(node) Match(node, expect) end @@ -11,40 +11,40 @@ end TEST [[--AAA]] { - [1] = { - type = "comment", - left = 0, - right = 5, - }, + type = "comment", + left = 0, + right = 5, + subtype = 'short', + value = 'AAA', } -TEST [[ ---AAA -]] +TEST [[//AAA]] { - [1] = { - type = "comment", - left = 0, - right = 5, - }, + type = "comment", + left = 0, + right = 5, + subtype = 'short', + value = 'AAA', } -TEST [[//AAA]] +TEST [===[--[[ +1234 +]]]===] { - [1] = { - type = "comment", - left = 0, - right = 5, - }, + type = "comment", + left = 0, + right = 20002, + subtype = 'long', + value = '\n1234\n', } -TEST [[ -//AAA -]] +TEST [===[/* +1234 +*/]===] { - [1] = { - type = "comment", - left = 0, - right = 5, - }, + type = "comment", + left = 0, + right = 20002, + subtype = 'long', + value = '\n1234\n', } From 5309416b937c01ff9f98eb05e8cff70d36dced89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Tue, 12 Sep 2023 17:03:39 +0800 Subject: [PATCH 13/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=B1=80=E9=83=A8?= =?UTF-8?q?=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .luarc.json | 3 + src/parser/ast/ast.lua | 33 +++++++++-- src/parser/ast/chunk.lua | 7 +++ src/parser/ast/error.lua | 8 +++ src/parser/ast/exp.lua | 16 +++++ src/parser/ast/id.lua | 107 ++++++++++++++++++++++++++++++++++ src/parser/ast/local.lua | 45 ++++++++++++++ src/parser/{ => ast}/node.lua | 7 +++ src/parser/ast/number.lua | 2 + src/parser/ast/state.lua | 11 ++++ src/parser/compile.lua | 1 - src/parser/init.lua | 1 - src/parser/lexer.lua | 11 ++++ test/ast/init.lua | 1 + test/ast/local.lua | 51 ++++++++++++++++ 15 files changed, 297 insertions(+), 7 deletions(-) create mode 100644 src/parser/ast/chunk.lua create mode 100644 src/parser/ast/exp.lua create mode 100644 src/parser/ast/id.lua create mode 100644 src/parser/ast/local.lua rename src/parser/{ => ast}/node.lua (92%) create mode 100644 src/parser/ast/state.lua create mode 100644 test/ast/local.lua diff --git a/.luarc.json b/.luarc.json index 184877a..cf9a458 100644 --- a/.luarc.json +++ b/.luarc.json @@ -32,5 +32,8 @@ "weakNilCheck": false, "weakUnionCheck": false, "castNumberToInteger": false + }, + "hover": { + "expandAlias": false } } diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 2d93865..1bbe46d 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -1,12 +1,17 @@ local class = require 'class' local lexer = require 'parser.lexer' +require 'parser.ast.node' require 'parser.ast.error' require 'parser.ast.nil' require 'parser.ast.boolean' require 'parser.ast.number' require 'parser.ast.string' require 'parser.ast.comment' +require 'parser.ast.exp' +require 'parser.ast.state' +require 'parser.ast.id' +require 'parser.ast.local' ---@class LuaParser.Ast ---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast @@ -22,12 +27,17 @@ function M:__init(code, version, options) ---@type LuaParser.LuaVersion self.version = version or 'Lua 5.4' -- 非标准符号的映射表 + ---@type table self.nssymbolMap = {} -- 词法分析结果 self.lexer = lexer.parseLua(code) self.index = 1 -- 错误信息 + ---@type LuaParser.Node.Error[] self.errors = {} + -- 注释 + ---@type LuaParser.Node.Comment[] + self.comments = {} local major, minor = self.version:match 'Lua (%d+)%.(%d+)' ---@type integer @@ -63,23 +73,36 @@ end ---@param inState? boolean # 是否是语句 ---@return boolean # 是否成功跳过注释 function M:skipComment(inState) - local token = self.lexer:peek() - if token == '--' - or (token == '//' and (inState or self.nssymbolMap['//'])) then - + local comment = self:parseComment(inState) + if comment then + self.comments[#self.comments+1] = comment + return true end + return false end -- 跳过空白符 ---@private ---@param inState? boolean # 是否是语句 function M:skipSpace(inState) + self.lastRightCI = self.lexer.ci repeat until not self:skipNL() and not self:skipComment(inState) end +-- 获取上个词的右侧位置(不包括换行符和注释) +---@return integer +function M:getLastPos() + if not self.lastRightCI then + return 0 + end + local token = self.lexer.tokens[self.lastRightCI] + local pos = self.lexer.poses[self.lastRightCI] + return pos + #token +end + -- 编译Lua代码 -function M:compile() +function M:parseMain() self:skipSpace() end diff --git a/src/parser/ast/chunk.lua b/src/parser/ast/chunk.lua new file mode 100644 index 0000000..7e1d052 --- /dev/null +++ b/src/parser/ast/chunk.lua @@ -0,0 +1,7 @@ +local class = require 'class' + +---@class LuaParser.Ast +local M = class.declare 'LuaParser.Ast' + +---@class LuaParser.Node.Chunk: LuaParser.Node.Base +local Chunk = class.declare('LuaParser.Node.Chunk', 'LuaParser.Node.Base') diff --git a/src/parser/ast/error.lua b/src/parser/ast/error.lua index 6d00ad7..391d9ae 100644 --- a/src/parser/ast/error.lua +++ b/src/parser/ast/error.lua @@ -35,3 +35,11 @@ function M:pushErrorMissSymbol(start, symbol) symbol = symbol, }) end + +-- 断言下个符号,如果成功则消耗,否则报错 +---@param symbol string +function M:assertSymbol(symbol) + if not self.lexer:consume(symbol) then + self:pushErrorMissSymbol(self:getLastPos(), symbol) + end +end diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua new file mode 100644 index 0000000..2a30f06 --- /dev/null +++ b/src/parser/ast/exp.lua @@ -0,0 +1,16 @@ +local class = require 'class' + +---@class LuaParser.Ast +local M = class.declare 'LuaParser.Ast' + +---@alias LuaParser.Node.Exp +---| LuaParser.Node.Nil +---| LuaParser.Node.Boolean +---| LuaParser.Node.Number +---| LuaParser.Node.String + +---@return LuaParser.Node.Exp? +function M:parseExp() + -- TODO + return self:parseNumber() +end diff --git a/src/parser/ast/id.lua b/src/parser/ast/id.lua new file mode 100644 index 0000000..0096406 --- /dev/null +++ b/src/parser/ast/id.lua @@ -0,0 +1,107 @@ +local class = require 'class' + +---@class LuaParser.Ast +local M = class.declare 'LuaParser.Ast' + +---@generic T: LuaParser.Node.ID +---@param nodeType `T` +---@param required? true +---@return T? +function M:parseID(nodeType, required) + local token, tp, pos = self.lexer:peek() + if tp ~= 'Word' then + if required then + self:pushError('MISS_NAME', self:getLastPos(), self:getLastPos()) + end + return nil + end + ---@cast token -? + ---@cast pos -? + if not self.unicodeName and token:find '[\x80-\xff]' then + self:pushError('UNICODE_NAME', pos, pos + #token) + end + if self:isKeyWord(token) then + self:pushError('KEYWORD', pos, pos + #token) + end + self.lexer:next() + return class.new(nodeType, { + id = token, + start = pos, + finish = pos + #token, + }) +end + +---@generic T: LuaParser.Node.ID +---@param nodeType `T` +---@param atLeastOne? true +---@return T[] +function M:parseIDList(nodeType, atLeastOne) + local list = {} + local first = self:parseID(nodeType, atLeastOne) + list[#list+1] = first + while true do + self:skipSpace() + local token, tp = self.lexer:peek() + if not token then + break + end + if tp == 'Symbol' then + if token == ',' then + self.lexer:next() + self:skipSpace() + else + break + end + else + self:pushErrorMissSymbol(self:getLastPos(), ',') + end + local id = self:parseID(nodeType, true) + if id then + list[#list+1] = id + end + end + return list +end + +-- goto 单独处理 +M.keyWordMap = { + ['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, +} + +---@param word string +---@return boolean +function M:isKeyWord(word) + if self.keyWordMap[word] then + return true + end + if word == 'goto' then + if self.jit then + -- LuaJIT 中只有 `goto Word` 才认为 goto 是关键字 + local _, nextType = self.lexer:peek(1) + return nextType == 'Word' + end + -- Lua 5.2 开始 goto 是关键字 + return self.versionNum >= 52 + end + return false +end diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua new file mode 100644 index 0000000..78fb820 --- /dev/null +++ b/src/parser/ast/local.lua @@ -0,0 +1,45 @@ +local class = require 'class' + +---@class LuaParser.Ast +local M = class.declare 'LuaParser.Ast' + +---@class LuaParser.Node.LocalDef: LuaParser.Node.Base +---@field vars LuaParser.Node.Local[] +---@field values? LuaParser.Node.Exp[] +local LocalDef = class.declare('LuaParser.Node.LocalDef', 'LuaParser.Node.Base') + +LocalDef.type = 'localdef' + +---@alias LuaParser.Node.ID LuaParser.Node.Local + +---@class LuaParser.Node.Local: LuaParser.Node.Base +---@field parent LuaParser.Node.LocalDef +---@field value? LuaParser.Node.Exp +local Local = class.declare('LuaParser.Node.Local', 'LuaParser.Node.Base') + +Local.type = 'local' + +---@return LuaParser.Node.LocalDef? +function M:parseLocal() + local token, _, pos = self.lexer:peek() + if token ~= 'local' then + return nil + end + self.lexer:next() + + self:skipSpace() + local vars = self:parseIDList('LuaParser.Node.Local', true) + local localdef = class.new('LuaParser.Node.LocalDef', { + ast = self, + vars = vars, + start = pos, + finish = #vars > 0 and vars[#vars].finish or pos + 5, + }) + + for i = 1, #vars do + local var = vars[i] + var.parent = localdef + end + + return localdef +end diff --git a/src/parser/node.lua b/src/parser/ast/node.lua similarity index 92% rename from src/parser/node.lua rename to src/parser/ast/node.lua index e204060..e61bb28 100644 --- a/src/parser/node.lua +++ b/src/parser/ast/node.lua @@ -77,3 +77,10 @@ Base.__getter.code = function (self) local code = self.ast.code:sub(self.start + 1, self.finish) return code, true end + +---@param self LuaParser.Node.Base +---@return string +---@return true +Base.__getter.parent = function (self) + error('未设置父节点:' .. tostring(self.type)) +end diff --git a/src/parser/ast/number.lua b/src/parser/ast/number.lua index 622a9e9..e8d81b3 100644 --- a/src/parser/ast/number.lua +++ b/src/parser/ast/number.lua @@ -2,6 +2,8 @@ local class = require 'class' ---@alias LuaParser.Node.Type 'Float' | 'Integer' +---@alias LuaParser.Node.Number LuaParser.Node.Float | LuaParser.Node.Integer + ---@class LuaParser.Node.Float: LuaParser.Node.Base ---@field value number ---@field valuei? number # 虚数 diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua new file mode 100644 index 0000000..ad8ebaf --- /dev/null +++ b/src/parser/ast/state.lua @@ -0,0 +1,11 @@ +local class = require 'class' + +---@class LuaParser.Ast +local M = class.declare 'LuaParser.Ast' + +---@alias LuaParser.Node.State +---| LuaParser.Node.LocalDef + +function M:parseState() + return self:parseLocal() +end diff --git a/src/parser/compile.lua b/src/parser/compile.lua index c947e31..1fb808f 100644 --- a/src/parser/compile.lua +++ b/src/parser/compile.lua @@ -31,6 +31,5 @@ local M = class.get 'LuaParser' ---@return LuaParser.Ast function M.compile(code, version, options) local ast = class.new 'LuaParser.Ast' (code, version, options) - ast:compile() return ast end diff --git a/src/parser/init.lua b/src/parser/init.lua index fffe77d..7c6df81 100644 --- a/src/parser/init.lua +++ b/src/parser/init.lua @@ -3,7 +3,6 @@ local class = require 'class' ---@class LuaParser local M = class.declare 'LuaParser' -require 'parser.node' require 'parser.compile' return M diff --git a/src/parser/lexer.lua b/src/parser/lexer.lua index d097ce6..cc17a85 100644 --- a/src/parser/lexer.lua +++ b/src/parser/lexer.lua @@ -115,6 +115,17 @@ function M:next(count) return token, tp, pos end +-- 消耗一个指定的词,返回是否成功 +---@param token string +---@return boolean +function M:consume(token) + if self.tokens[self.ci] == token then + self.ci = self.ci + 1 + return true + end + return false +end + -- 获取当前词的2侧光标位置 ---@param offset? integer # 偏移量,默认为0 ---@return integer? diff --git a/test/ast/init.lua b/test/ast/init.lua index bf8dc96..126d65d 100644 --- a/test/ast/init.lua +++ b/test/ast/init.lua @@ -15,3 +15,4 @@ require 'test.ast.boolean' require 'test.ast.string' require 'test.ast.number' require 'test.ast.comment' +require 'test.ast.local' diff --git a/test/ast/local.lua b/test/ast/local.lua new file mode 100644 index 0000000..cdf8f44 --- /dev/null +++ b/test/ast/local.lua @@ -0,0 +1,51 @@ +local parser = require 'parser' + +local function TEST(code) + return function (expect) + local ast = parser.compile(code) + local node = ast:parseLocal() + assert(node) + Match(node, expect) + end +end + +TEST [[local x]] +{ + type = 'localdef', + left = 0, + finish = 7, + vars = { + [1] = { + type = 'local', + start = 6, + finish = 7, + id = 'x', + }, + }, +} +TEST [[local x, y, z]] +{ + type = 'localdef', + left = 0, + finish = 13, + vars = { + [1] = { + type = 'local', + start = 6, + finish = 7, + id = 'x', + }, + [2] = { + type = 'local', + start = 9, + finish = 10, + id = 'y', + }, + [3] = { + type = 'local', + start = 12, + finish = 13, + id = 'z', + }, + }, +} From 2ba137fd16386477bced9c83e337a8a53f24fb2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Tue, 12 Sep 2023 17:43:52 +0800 Subject: [PATCH 14/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=B1=80=E9=83=A8?= =?UTF-8?q?=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 4 +- src/parser/ast/{node.lua => base.lua} | 1 + src/parser/ast/comment.lua | 6 +- src/parser/ast/error.lua | 8 +-- src/parser/ast/exp.lua | 45 ++++++++++++- src/parser/ast/id.lua | 12 ++-- src/parser/ast/local.lua | 27 ++++++-- src/parser/ast/number.lua | 16 ++--- src/parser/ast/string.lua | 30 ++++----- src/parser/compile.lua | 1 + test/ast/Nil.lua | 4 +- test/ast/boolean.lua | 4 +- test/ast/comment.lua | 4 +- test/ast/init.lua | 2 + test/ast/local.lua | 91 ++++++++++++++++++++++++++- test/ast/number.lua | 4 +- test/ast/string.lua | 4 +- 17 files changed, 209 insertions(+), 54 deletions(-) rename src/parser/ast/{node.lua => base.lua} (98%) diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 1bbe46d..7079522 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -1,7 +1,7 @@ local class = require 'class' local lexer = require 'parser.lexer' -require 'parser.ast.node' +require 'parser.ast.base' require 'parser.ast.error' require 'parser.ast.nil' require 'parser.ast.boolean' @@ -85,7 +85,7 @@ end ---@private ---@param inState? boolean # 是否是语句 function M:skipSpace(inState) - self.lastRightCI = self.lexer.ci + self.lastRightCI = self.lexer.ci - 1 repeat until not self:skipNL() and not self:skipComment(inState) end diff --git a/src/parser/ast/node.lua b/src/parser/ast/base.lua similarity index 98% rename from src/parser/ast/node.lua rename to src/parser/ast/base.lua index e61bb28..d5318a9 100644 --- a/src/parser/ast/node.lua +++ b/src/parser/ast/base.lua @@ -12,6 +12,7 @@ local class = require 'class' ---@field finishRow integer # 结束行号 ---@field finishCol integer # 结束列号 ---@field code string # 对应的代码 +---@field parent LuaParser.Node.Base local Base = class.declare 'LuaParser.Node.Base' local rowcolMulti = 10000 diff --git a/src/parser/ast/comment.lua b/src/parser/ast/comment.lua index 388970f..9675864 100644 --- a/src/parser/ast/comment.lua +++ b/src/parser/ast/comment.lua @@ -47,7 +47,7 @@ function M:parseShortComment(inState) if token == '--' or (token == '//' and (inState or self.nssymbolMap['//'])) then if token == '//' and not self.nssymbolMap['//'] then - self:pushError('ERR_COMMENT_PREFIX', pos, pos + 2) + self:throw('ERR_COMMENT_PREFIX', pos, pos + 2) end local offset = self.code:find('[\r\n]', pos + 2) or (#self.code + 1) self.lexer:fastForward(offset) @@ -78,7 +78,7 @@ function M:parseLongComment() elseif token == '/*' then finishQuo = '*/' if not self.nssymbolMap['/**/'] then - self:pushError('ERR_C_LONG_COMMENT', pos, pos + 1) + self:throw('ERR_C_LONG_COMMENT', pos, pos + 1) end else return nil @@ -87,7 +87,7 @@ function M:parseLongComment() if offset then self.lexer:fastForward(offset + #finishQuo - 1) else - self:pushErrorMissSymbol(#self.code, finishQuo) + self:throwMissSymbol(#self.code, finishQuo) self.lexer:fastForward(#self.code) end return class.new('LuaParser.Node.Comment', { diff --git a/src/parser/ast/error.lua b/src/parser/ast/error.lua index 391d9ae..67164bf 100644 --- a/src/parser/ast/error.lua +++ b/src/parser/ast/error.lua @@ -17,7 +17,7 @@ local M = class.declare 'LuaParser.Ast' ---@param start integer ---@param finish integer ---@param extra table? -function M:pushError(errorCode, start, finish, extra) +function M:throw(errorCode, start, finish, extra) self.errors[#self.errors+1] = class.new('LuaParser.Node.Error', { code = errorCode, ast = self, @@ -30,8 +30,8 @@ end -- 添加错误“缺少符号” ---@param start integer ---@param symbol string -function M:pushErrorMissSymbol(start, symbol) - self:pushError('MISS_SYMBOL', start, start, { +function M:throwMissSymbol(start, symbol) + self:throw('MISS_SYMBOL', start, start, { symbol = symbol, }) end @@ -40,6 +40,6 @@ end ---@param symbol string function M:assertSymbol(symbol) if not self.lexer:consume(symbol) then - self:pushErrorMissSymbol(self:getLastPos(), symbol) + self:throwMissSymbol(self:getLastPos(), symbol) end end diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index 2a30f06..09c58c1 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -9,8 +9,49 @@ local M = class.declare 'LuaParser.Ast' ---| LuaParser.Node.Number ---| LuaParser.Node.String +---@param required? true ---@return LuaParser.Node.Exp? -function M:parseExp() +function M:parseExp(required) -- TODO - return self:parseNumber() + local exp = self:parseNumber() + + if not exp and required then + self:throw('MISS_EXP', self:getLastPos(), self:getLastPos()) + end + + return exp +end + +---@param atLeastOne? true +---@return LuaParser.Node.Exp[] +function M:parseExpList(atLeastOne) + ---@type LuaParser.Node.Exp[] + local list = {} + local first = self:parseExp(atLeastOne) + list[#list+1] = first + while true do + self:skipSpace() + local token, tp = self.lexer:peek() + if not token then + break + end + if tp == 'Symbol' then + if token == ',' then + self.lexer:next() + self:skipSpace() + else + break + end + else + if tp == 'Word' and self:isKeyWord(token) then + break + end + self:throwMissSymbol(self:getLastPos(), ',') + end + local exp = self:parseExp(true) + if exp then + list[#list+1] = exp + end + end + return list end diff --git a/src/parser/ast/id.lua b/src/parser/ast/id.lua index 0096406..877647d 100644 --- a/src/parser/ast/id.lua +++ b/src/parser/ast/id.lua @@ -11,17 +11,17 @@ function M:parseID(nodeType, required) local token, tp, pos = self.lexer:peek() if tp ~= 'Word' then if required then - self:pushError('MISS_NAME', self:getLastPos(), self:getLastPos()) + self:throw('MISS_NAME', self:getLastPos(), self:getLastPos()) end return nil end ---@cast token -? ---@cast pos -? if not self.unicodeName and token:find '[\x80-\xff]' then - self:pushError('UNICODE_NAME', pos, pos + #token) + self:throw('UNICODE_NAME', pos, pos + #token) end if self:isKeyWord(token) then - self:pushError('KEYWORD', pos, pos + #token) + self:throw('KEYWORD', pos, pos + #token) end self.lexer:next() return class.new(nodeType, { @@ -36,6 +36,7 @@ end ---@param atLeastOne? true ---@return T[] function M:parseIDList(nodeType, atLeastOne) + ---@type LuaParser.Node.ID[] local list = {} local first = self:parseID(nodeType, atLeastOne) list[#list+1] = first @@ -53,7 +54,10 @@ function M:parseIDList(nodeType, atLeastOne) break end else - self:pushErrorMissSymbol(self:getLastPos(), ',') + if tp == 'Word' and self:isKeyWord(token) then + break + end + self:throwMissSymbol(self:getLastPos(), ',') end local id = self:parseID(nodeType, true) if id then diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index 78fb820..d6fdbf2 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -14,6 +14,7 @@ LocalDef.type = 'localdef' ---@class LuaParser.Node.Local: LuaParser.Node.Base ---@field parent LuaParser.Node.LocalDef +---@field index integer ---@field value? LuaParser.Node.Exp local Local = class.declare('LuaParser.Node.Local', 'LuaParser.Node.Base') @@ -27,19 +28,37 @@ function M:parseLocal() end self.lexer:next() - self:skipSpace() - local vars = self:parseIDList('LuaParser.Node.Local', true) local localdef = class.new('LuaParser.Node.LocalDef', { ast = self, - vars = vars, start = pos, - finish = #vars > 0 and vars[#vars].finish or pos + 5, }) + self:skipSpace() + local vars = self:parseIDList('LuaParser.Node.Local', true) + localdef.vars = vars for i = 1, #vars do local var = vars[i] var.parent = localdef + var.index = i end + self:skipSpace() + if self.lexer:consume '=' then + self:skipSpace() + local values = self:parseExpList(true) + localdef.values = values + for i = 1, #values do + local value = values[i] + value.parent = localdef + + local var = vars[i] + if var then + var.value = value + end + end + end + + localdef.finish = self:getLastPos() + return localdef end diff --git a/src/parser/ast/number.lua b/src/parser/ast/number.lua index e8d81b3..04ab109 100644 --- a/src/parser/ast/number.lua +++ b/src/parser/ast/number.lua @@ -134,7 +134,7 @@ function M:fastForwardNumber(curOffset) self.lexer:fastForward(curOffset - 1) return end - self:pushError('MALFORMED_NUMBER', curOffset - 1, curOffset - 1 + #word) + self:throw('MALFORMED_NUMBER', curOffset - 1, curOffset - 1 + #word) self.lexer:fastForward(curOffset - 1 + #word) end @@ -144,7 +144,7 @@ end function M:parseNumberI(curOffset) if self.code:find('^[iI]', curOffset) then if not self.jit then - self:pushError('UNSUPPORT_SYMBOL', curOffset, curOffset, { + self:throw('UNSUPPORT_SYMBOL', curOffset, curOffset, { version = self.version, needVer = 'LuaJIT', }) @@ -235,16 +235,16 @@ function M:parseNumber16() if expOffset then curOffset = expOffset if #expPart == 0 then - self:pushError('MISS_EXPONENT', curOffset - 1, curOffset - 1) + self:throw('MISS_EXPONENT', curOffset - 1, curOffset - 1) end end if not intPart then if not numPart then - self:pushError('MUST_X16', pos + 2, pos + 2) + self:throw('MUST_X16', pos + 2, pos + 2) end if numPart == '' then - self:pushError('MUST_X16', numOffset - 1, numOffset - 1) + self:throw('MUST_X16', numOffset - 1, numOffset - 1) end end @@ -275,7 +275,7 @@ function M:parseNumber2() local value = tonumber(bins, 2) if not self.jit then - self:pushError('UNSUPPORT_SYMBOL', pos + 2, curOffset - 1, { + self:throw('UNSUPPORT_SYMBOL', pos + 2, curOffset - 1, { version = self.version, needVer = 'LuaJIT', }) @@ -309,14 +309,14 @@ function M:parseNumber10() if expOffset then curOffset = expOffset if #expPart == 0 then - self:pushError('MISS_EXPONENT', curOffset - 1, curOffset - 1) + self:throw('MISS_EXPONENT', curOffset - 1, curOffset - 1) end end if not intPart then if numPart == '' then self.lexer:next() - self:pushError('UNKNOWN_SYMBOL', pos, pos + 1) + self:throw('UNKNOWN_SYMBOL', pos, pos + 1) return nil end end diff --git a/src/parser/ast/string.lua b/src/parser/ast/string.lua index 4472a9e..f133ee2 100644 --- a/src/parser/ast/string.lua +++ b/src/parser/ast/string.lua @@ -85,7 +85,7 @@ function M:parseShortString() ---@cast pos -? if quo == '`' and not self.nssymbolMap['`'] then - self:pushError('ERR_NONSTANDARD_SYMBOL', pos, pos + 1) + self:throw('ERR_NONSTANDARD_SYMBOL', pos, pos + 1) end local pieces = {} @@ -116,14 +116,14 @@ function M:parseShortString() if not char then pieces[#pieces+1] = self.code:sub(curOffset) curOffset = #self.code + 1 - self:pushErrorMissSymbol(curOffset - 1, quo) + self:throwMissSymbol(curOffset - 1, quo) break end if char == '\r' or char == '\n' then pieces[#pieces+1] = self.code:sub(curOffset, offset - 2) curOffset = offset - 1 - self:pushErrorMissSymbol(curOffset - 1, quo) + self:throwMissSymbol(curOffset - 1, quo) break end if char == '\\' then @@ -151,7 +151,7 @@ function M:parseShortString() pieces[#pieces+1] = string.char(num) else pushEsc('err', offset - 2, offset - 1 + #numWord) - self:pushError('ERR_ESC_DEC', curPos, curPos + #numWord, { + self:throw('ERR_ESC_DEC', curPos, curPos + #numWord, { min = '000', max = '255', }) @@ -177,10 +177,10 @@ function M:parseShortString() else pushEsc('err', offset - 2, offset - 1) curOffset = offset + 1 - self:pushError('ERR_ESC_X', curPos, curPos + 1) + self:throw('ERR_ESC_X', curPos, curPos + 1) end if self.versionNum <= 51 then - self:pushError('ERR_ESC', curPos, curPos + 3) + self:throw('ERR_ESC', curPos, curPos + 3) end goto continue end @@ -188,25 +188,25 @@ function M:parseShortString() local leftP, word, rightP, tailOffset = self.code:match('^({)(%w*)(}?)()', offset + 1) if not leftP then pushEsc('err', offset - 2, offset - 1) - self:pushErrorMissSymbol(offset - 1, '{') + self:throwMissSymbol(offset - 1, '{') curOffset = offset goto continue end pushEsc('unicode', offset - 2, tailOffset - 1) if #word == 0 then - self:pushError('UTF8_SMALL', offset + 1, offset + 1) + self:throw('UTF8_SMALL', offset + 1, offset + 1) else local num = tonumber(word, 16) if num then if self.versionNum >= 54 then if num < 0 or num > 0x7FFFFFFF then - self:pushError('UTF8_MAX', offset + 1, offset + #word, { + self:throw('UTF8_MAX', offset + 1, offset + #word, { min = '00000000', max = '7FFFFFFF', }) end else - self:pushError('UTF8_MAX', offset + 1, offset + #word, { + self:throw('UTF8_MAX', offset + 1, offset + #word, { min = '000000', max = '10FFFF', needVer = num <= 0x7FFFFFFF and 'Lua 5.4' or nil, @@ -216,11 +216,11 @@ function M:parseShortString() pieces[#pieces+1] = utf8.char(num) end else - self:pushError('MUST_X16', offset + 1, offset + #word) + self:throw('MUST_X16', offset + 1, offset + #word) end end if rightP == '' then - self:pushErrorMissSymbol(tailOffset - 1, '}') + self:throwMissSymbol(tailOffset - 1, '}') end curOffset = tailOffset goto continue @@ -233,7 +233,7 @@ function M:parseShortString() else pushEsc('err', offset - 2, offset) curOffset = offset + 1 - self:pushError('ERR_ESC', offset - 2, offset) + self:throw('ERR_ESC', offset - 2, offset) goto continue end goto continue @@ -277,13 +277,13 @@ function M:parseLongString() else finishPos = #self.code missQuo = true - self:pushErrorMissSymbol(finishPos, finishQuo) + self:throwMissSymbol(finishPos, finishQuo) end if quo == '[[' and self.versionNum <= 51 then local nestOffset = self.code:find('[[', pos + #quo + 1, true) if nestOffset and nestOffset < finishOffset then - self:pushError('NESTING_LONG_MARK', nestOffset - 1, nestOffset + 1) + self:throw('NESTING_LONG_MARK', nestOffset - 1, nestOffset + 1) end end diff --git a/src/parser/compile.lua b/src/parser/compile.lua index 1fb808f..7c67066 100644 --- a/src/parser/compile.lua +++ b/src/parser/compile.lua @@ -31,5 +31,6 @@ local M = class.get 'LuaParser' ---@return LuaParser.Ast function M.compile(code, version, options) local ast = class.new 'LuaParser.Ast' (code, version, options) + ast:parseMain() return ast end diff --git a/test/ast/Nil.lua b/test/ast/Nil.lua index 27263c1..ebdfca7 100644 --- a/test/ast/Nil.lua +++ b/test/ast/Nil.lua @@ -1,8 +1,8 @@ -local parser = require 'parser' +local class = require 'class' local function TEST(code) return function (expect) - local ast = parser.compile(code) + local ast = class.new 'LuaParser.Ast' (code) local node = ast:parseNil() assert(node) Match(node, expect) diff --git a/test/ast/boolean.lua b/test/ast/boolean.lua index 68fd7e2..bc31677 100644 --- a/test/ast/boolean.lua +++ b/test/ast/boolean.lua @@ -1,8 +1,8 @@ -local parser = require 'parser' +local class = require 'class' local function TEST(code) return function (expect) - local ast = parser.compile(code) + local ast = class.new 'LuaParser.Ast' (code) local node = ast:parseBoolean() assert(node) Match(node, expect) diff --git a/test/ast/comment.lua b/test/ast/comment.lua index f97ae5c..c93f2e0 100644 --- a/test/ast/comment.lua +++ b/test/ast/comment.lua @@ -1,8 +1,8 @@ -local parser = require 'parser' +local class = require 'class' local function TEST(code) return function (expect) - local ast = parser.compile(code) + local ast = class.new 'LuaParser.Ast' (code) local node = ast:parseComment(true) assert(node) Match(node, expect) diff --git a/test/ast/init.lua b/test/ast/init.lua index 126d65d..adbe84b 100644 --- a/test/ast/init.lua +++ b/test/ast/init.lua @@ -10,6 +10,8 @@ function Match(result, expect) end end +require 'parser' + require 'test.ast.nil' require 'test.ast.boolean' require 'test.ast.string' diff --git a/test/ast/local.lua b/test/ast/local.lua index cdf8f44..d4a26ac 100644 --- a/test/ast/local.lua +++ b/test/ast/local.lua @@ -1,8 +1,8 @@ -local parser = require 'parser' +local class = require 'class' local function TEST(code) return function (expect) - local ast = parser.compile(code) + local ast = class.new 'LuaParser.Ast' (code) local node = ast:parseLocal() assert(node) Match(node, expect) @@ -20,9 +20,11 @@ TEST [[local x]] start = 6, finish = 7, id = 'x', + index = 1, }, }, } + TEST [[local x, y, z]] { type = 'localdef', @@ -34,18 +36,103 @@ TEST [[local x, y, z]] start = 6, finish = 7, id = 'x', + index = 1, }, [2] = { type = 'local', start = 9, finish = 10, id = 'y', + index = 2, }, [3] = { type = 'local', start = 12, finish = 13, id = 'z', + index = 3, }, }, } + +TEST [[local x = 1]] +{ + type = 'localdef', + left = 0, + finish = 11, + vars = { + [1] = { + type = 'local', + start = 6, + finish = 7, + id = 'x', + value = { + type = 'integer', + start = 10, + finish = 11, + value = 1, + } + }, + }, + values = { + [1] = { + type = 'integer', + start = 10, + finish = 11, + value = 1, + }, + } +} + +TEST [[local x, y = 1, 2, 3]] +{ + type = 'localdef', + left = 0, + finish = 20, + vars = { + [1] = { + type = 'local', + start = 6, + finish = 7, + id = 'x', + value = { + type = 'integer', + start = 13, + finish = 14, + value = 1, + } + }, + [2] = { + type = 'local', + start = 9, + finish = 10, + id = 'y', + value = { + type = 'integer', + start = 16, + finish = 17, + value = 2, + } + }, + }, + values = { + [1] = { + type = 'integer', + start = 13, + finish = 14, + value = 1, + }, + [2] = { + type = 'integer', + start = 16, + finish = 17, + value = 2, + }, + [3] = { + type = 'integer', + start = 19, + finish = 20, + value = 3, + }, + } +} diff --git a/test/ast/number.lua b/test/ast/number.lua index 9d667fe..49f76c6 100644 --- a/test/ast/number.lua +++ b/test/ast/number.lua @@ -1,8 +1,8 @@ -local parser = require 'parser' +local class = require 'class' local function TEST(code) return function (expect) - local ast = parser.compile(code) + local ast = class.new 'LuaParser.Ast' (code) local node = ast:parseNumber() assert(node) Match(node, expect) diff --git a/test/ast/string.lua b/test/ast/string.lua index 7cef45e..c541a83 100644 --- a/test/ast/string.lua +++ b/test/ast/string.lua @@ -1,8 +1,8 @@ -local parser = require 'parser' +local class = require 'class' local function TEST(code) return function (expect) - local ast = parser.compile(code) + local ast = class.new 'LuaParser.Ast' (code) local node = ast:parseString() assert(node) Match(node, expect) From ef8e18ae12c9f95aff5175e80ef12f0e2e3d0257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Tue, 12 Sep 2023 18:09:18 +0800 Subject: [PATCH 15/94] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 1 + src/parser/ast/base.lua | 3 +-- src/parser/ast/boolean.lua | 6 ++---- src/parser/ast/chunk.lua | 6 +++--- src/parser/ast/comment.lua | 10 ++++------ src/parser/ast/error.lua | 10 ++++------ src/parser/ast/exp.lua | 6 +++--- src/parser/ast/id.lua | 10 +++++----- src/parser/ast/local.lua | 10 ++++++---- src/parser/ast/nil.lua | 6 ++---- src/parser/ast/number.lua | 20 +++++++++----------- src/parser/ast/state.lua | 4 ++-- src/parser/ast/string.lua | 10 ++++------ src/parser/ast/var.lua | 7 +++++++ src/parser/compile.lua | 4 ++-- test/ast/Nil.lua | 2 -- test/ast/boolean.lua | 2 -- test/ast/comment.lua | 4 ---- test/ast/local.lua | 18 ------------------ test/ast/number.lua | 17 ----------------- test/ast/string.lua | 21 --------------------- 21 files changed, 55 insertions(+), 122 deletions(-) create mode 100644 src/parser/ast/var.lua diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 7079522..912a8f2 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -12,6 +12,7 @@ require 'parser.ast.exp' require 'parser.ast.state' require 'parser.ast.id' require 'parser.ast.local' +require 'parser.ast.var' ---@class LuaParser.Ast ---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast diff --git a/src/parser/ast/base.lua b/src/parser/ast/base.lua index d5318a9..f51d8f2 100644 --- a/src/parser/ast/base.lua +++ b/src/parser/ast/base.lua @@ -2,7 +2,6 @@ local class = require 'class' ---@class LuaParser.Node.Base: Class.Base ---@field ast LuaParser.Ast ----@field type LuaParser.Node.Type ---@field start integer # 开始位置(偏移) ---@field finish integer # 结束位置(偏移) ---@field left integer # 开始位置(行号与列号合并) @@ -83,5 +82,5 @@ end ---@return string ---@return true Base.__getter.parent = function (self) - error('未设置父节点:' .. tostring(self.type)) + error('未设置父节点:' .. class.type(self)) end diff --git a/src/parser/ast/boolean.lua b/src/parser/ast/boolean.lua index d8ddf9a..bd6d456 100644 --- a/src/parser/ast/boolean.lua +++ b/src/parser/ast/boolean.lua @@ -1,7 +1,5 @@ local class = require 'class' ----@alias LuaParser.Node.Type 'boolean' - ---@class LuaParser.Node.Boolean: LuaParser.Node.Base ---@field value boolean ---@field view string @@ -14,11 +12,11 @@ Boolean.__getter.view = function (self) end ---@class LuaParser.Ast -local M = class.declare 'LuaParser.Ast' +local Ast = class.declare 'LuaParser.Ast' -- 解析布尔值 ---@return LuaParser.Node.Boolean? -function M:parseBoolean() +function Ast:parseBoolean() local token = self.lexer:peek() if token == 'true' then local start, finish = self.lexer:range() diff --git a/src/parser/ast/chunk.lua b/src/parser/ast/chunk.lua index 7e1d052..1d97306 100644 --- a/src/parser/ast/chunk.lua +++ b/src/parser/ast/chunk.lua @@ -1,7 +1,7 @@ local class = require 'class' ----@class LuaParser.Ast -local M = class.declare 'LuaParser.Ast' - ---@class LuaParser.Node.Chunk: LuaParser.Node.Base local Chunk = class.declare('LuaParser.Node.Chunk', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' diff --git a/src/parser/ast/comment.lua b/src/parser/ast/comment.lua index 9675864..148206b 100644 --- a/src/parser/ast/comment.lua +++ b/src/parser/ast/comment.lua @@ -1,7 +1,5 @@ local class = require 'class' ----@alias LuaParser.LuaParser.Node.Type 'comment' - ---@class LuaParser.Node.Comment: LuaParser.Node.Base ---@field subtype 'short' | 'long' ---@field value string @@ -27,18 +25,18 @@ Comment.__getter.value = function (self) end ---@class LuaParser.Ast -local M = class.declare 'LuaParser.Ast' +local Ast = class.declare 'LuaParser.Ast' ---@param inState? boolean ---@return LuaParser.Node.Comment? -function M:parseComment(inState) +function Ast:parseComment(inState) return self:parseLongComment() or self:parseShortComment(inState) end ---@param inState? boolean ---@return LuaParser.Node.Comment? -function M:parseShortComment(inState) +function Ast:parseShortComment(inState) local token, _, pos = self.lexer:peek() if not token then return nil @@ -62,7 +60,7 @@ function M:parseShortComment(inState) end ---@return LuaParser.Node.Comment? -function M:parseLongComment() +function Ast:parseLongComment() local token, _, pos = self.lexer:peek() if not token then return nil diff --git a/src/parser/ast/error.lua b/src/parser/ast/error.lua index 67164bf..d6118c2 100644 --- a/src/parser/ast/error.lua +++ b/src/parser/ast/error.lua @@ -1,7 +1,5 @@ local class = require 'class' ----@alias LuaParser.Node.Type 'error' - ---@class LuaParser.Node.Error local Error = class.declare('LuaParser.Node.Error', 'LuaParser.Node.Base') @@ -10,14 +8,14 @@ Error.code = 'UNKNOWN' ---@class LuaParser.Ast ---@field extra? table -local M = class.declare 'LuaParser.Ast' +local Ast = class.declare 'LuaParser.Ast' -- 添加错误信息 ---@param errorCode string ---@param start integer ---@param finish integer ---@param extra table? -function M:throw(errorCode, start, finish, extra) +function Ast:throw(errorCode, start, finish, extra) self.errors[#self.errors+1] = class.new('LuaParser.Node.Error', { code = errorCode, ast = self, @@ -30,7 +28,7 @@ end -- 添加错误“缺少符号” ---@param start integer ---@param symbol string -function M:throwMissSymbol(start, symbol) +function Ast:throwMissSymbol(start, symbol) self:throw('MISS_SYMBOL', start, start, { symbol = symbol, }) @@ -38,7 +36,7 @@ end -- 断言下个符号,如果成功则消耗,否则报错 ---@param symbol string -function M:assertSymbol(symbol) +function Ast:assertSymbol(symbol) if not self.lexer:consume(symbol) then self:throwMissSymbol(self:getLastPos(), symbol) end diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index 09c58c1..00e35d3 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -1,7 +1,7 @@ local class = require 'class' ---@class LuaParser.Ast -local M = class.declare 'LuaParser.Ast' +local Ast = class.declare 'LuaParser.Ast' ---@alias LuaParser.Node.Exp ---| LuaParser.Node.Nil @@ -11,7 +11,7 @@ local M = class.declare 'LuaParser.Ast' ---@param required? true ---@return LuaParser.Node.Exp? -function M:parseExp(required) +function Ast:parseExp(required) -- TODO local exp = self:parseNumber() @@ -24,7 +24,7 @@ end ---@param atLeastOne? true ---@return LuaParser.Node.Exp[] -function M:parseExpList(atLeastOne) +function Ast:parseExpList(atLeastOne) ---@type LuaParser.Node.Exp[] local list = {} local first = self:parseExp(atLeastOne) diff --git a/src/parser/ast/id.lua b/src/parser/ast/id.lua index 877647d..079b8d3 100644 --- a/src/parser/ast/id.lua +++ b/src/parser/ast/id.lua @@ -1,13 +1,13 @@ local class = require 'class' ---@class LuaParser.Ast -local M = class.declare 'LuaParser.Ast' +local Ast = class.declare 'LuaParser.Ast' ---@generic T: LuaParser.Node.ID ---@param nodeType `T` ---@param required? true ---@return T? -function M:parseID(nodeType, required) +function Ast:parseID(nodeType, required) local token, tp, pos = self.lexer:peek() if tp ~= 'Word' then if required then @@ -35,7 +35,7 @@ end ---@param nodeType `T` ---@param atLeastOne? true ---@return T[] -function M:parseIDList(nodeType, atLeastOne) +function Ast:parseIDList(nodeType, atLeastOne) ---@type LuaParser.Node.ID[] local list = {} local first = self:parseID(nodeType, atLeastOne) @@ -68,7 +68,7 @@ function M:parseIDList(nodeType, atLeastOne) end -- goto 单独处理 -M.keyWordMap = { +Ast.keyWordMap = { ['and'] = true, ['break'] = true, ['do'] = true, @@ -94,7 +94,7 @@ M.keyWordMap = { ---@param word string ---@return boolean -function M:isKeyWord(word) +function Ast:isKeyWord(word) if self.keyWordMap[word] then return true end diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index d6fdbf2..c15050f 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -1,8 +1,5 @@ local class = require 'class' ----@class LuaParser.Ast -local M = class.declare 'LuaParser.Ast' - ---@class LuaParser.Node.LocalDef: LuaParser.Node.Base ---@field vars LuaParser.Node.Local[] ---@field values? LuaParser.Node.Exp[] @@ -16,12 +13,16 @@ LocalDef.type = 'localdef' ---@field parent LuaParser.Node.LocalDef ---@field index integer ---@field value? LuaParser.Node.Exp +---@field refs? LuaParser.Node.Var[] local Local = class.declare('LuaParser.Node.Local', 'LuaParser.Node.Base') Local.type = 'local' +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + ---@return LuaParser.Node.LocalDef? -function M:parseLocal() +function Ast:parseLocal() local token, _, pos = self.lexer:peek() if token ~= 'local' then return nil @@ -31,6 +32,7 @@ function M:parseLocal() local localdef = class.new('LuaParser.Node.LocalDef', { ast = self, start = pos, + refs = {}, }) self:skipSpace() diff --git a/src/parser/ast/nil.lua b/src/parser/ast/nil.lua index 1d576d9..4d0706a 100644 --- a/src/parser/ast/nil.lua +++ b/src/parser/ast/nil.lua @@ -1,7 +1,5 @@ local class = require 'class' ----@alias LuaParser.Node.Type 'nil' - ---@class LuaParser.Node.Nil: LuaParser.Node.Base local Nil = class.declare('LuaParser.Node.Nil', 'LuaParser.Node.Base') @@ -10,11 +8,11 @@ Nil.type = 'nil' Nil.view = 'nil' ---@class LuaParser.Ast -local M = class.declare 'LuaParser.Ast' +local Ast = class.declare 'LuaParser.Ast' -- 解析 nil ---@return LuaParser.Node.Nil? -function M:parseNil() +function Ast:parseNil() local token = self.lexer:peek() if token ~= 'nil' then return nil diff --git a/src/parser/ast/number.lua b/src/parser/ast/number.lua index 04ab109..d231e62 100644 --- a/src/parser/ast/number.lua +++ b/src/parser/ast/number.lua @@ -1,7 +1,5 @@ local class = require 'class' ----@alias LuaParser.Node.Type 'Float' | 'Integer' - ---@alias LuaParser.Node.Number LuaParser.Node.Float | LuaParser.Node.Integer ---@class LuaParser.Node.Float: LuaParser.Node.Base @@ -85,11 +83,11 @@ Integer.__getter.view = function (self) end ---@class LuaParser.Ast -local M = class.declare 'LuaParser.Ast' +local Ast = class.declare 'LuaParser.Ast' -- 解析数字(可以带负号) ---@return LuaParser.Node.Float | LuaParser.Node.Integer | nil -function M:parseNumber() +function Ast:parseNumber() -- 快速判断是否为数字 if self.lexer:peek() == '-' then local token, tp = self.lexer:peek(1) @@ -128,7 +126,7 @@ end ---@private ---@param curOffset integer -function M:fastForwardNumber(curOffset) +function Ast:fastForwardNumber(curOffset) local word = self.code:match('^[%.%w_\x80-\xff]+', curOffset) if not word then self.lexer:fastForward(curOffset - 1) @@ -141,7 +139,7 @@ end ---@private ---@param curOffset integer ---@return boolean -function M:parseNumberI(curOffset) +function Ast:parseNumberI(curOffset) if self.code:find('^[iI]', curOffset) then if not self.jit then self:throw('UNSUPPORT_SYMBOL', curOffset, curOffset, { @@ -159,7 +157,7 @@ end ---@param startPos integer ---@param curOffset integer ---@return LuaParser.Node.Float -function M:buildFloat(value, startPos, curOffset) +function Ast:buildFloat(value, startPos, curOffset) local valuei if self:parseNumberI(curOffset) then curOffset = curOffset + 1 @@ -180,7 +178,7 @@ end ---@param startPos integer ---@param curOffset integer ---@return LuaParser.Node.Integer -function M:buildInteger(value, startPos, curOffset) +function Ast:buildInteger(value, startPos, curOffset) local valuei, intTail if self:parseNumberI(curOffset) then curOffset = curOffset + 1 @@ -208,7 +206,7 @@ end -- 解析十六进制数字(不支持负号) ---@return LuaParser.Node.Float | LuaParser.Node.Integer | nil -function M:parseNumber16() +function Ast:parseNumber16() local token, _, pos = self.lexer:peek() if token ~= '0' then @@ -259,7 +257,7 @@ end -- 解析二进制数字(不支持负号) ---@return LuaParser.Node.Integer | nil -function M:parseNumber2() +function Ast:parseNumber2() local token, _, pos = self.lexer:peek() if token ~= '0' then @@ -286,7 +284,7 @@ end -- 解析十进制数字(不支持负号) ---@return LuaParser.Node.Float | LuaParser.Node.Integer | nil -function M:parseNumber10() +function Ast:parseNumber10() local token, tp, pos = self.lexer:peek() if token ~= '.' and tp ~= 'Num' then return nil diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua index ad8ebaf..617ca8a 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state.lua @@ -1,11 +1,11 @@ local class = require 'class' ---@class LuaParser.Ast -local M = class.declare 'LuaParser.Ast' +local Ast = class.declare 'LuaParser.Ast' ---@alias LuaParser.Node.State ---| LuaParser.Node.LocalDef -function M:parseState() +function Ast:parseState() return self:parseLocal() end diff --git a/src/parser/ast/string.lua b/src/parser/ast/string.lua index f133ee2..bf8417e 100644 --- a/src/parser/ast/string.lua +++ b/src/parser/ast/string.lua @@ -1,7 +1,5 @@ local class = require 'class' ----@alias LuaParser.Node.Type 'String' - ---@alias LuaParser.EscMode 'normal' | 'unicode' | 'err' | 'byte' ---@class LuaParser.Node.String: LuaParser.Node.Base @@ -57,11 +55,11 @@ String.__getter.view = function (self) end ---@class LuaParser.Ast -local M = class.declare 'LuaParser.Ast' +local Ast = class.declare 'LuaParser.Ast' -- 解析字符串 ---@return LuaParser.Node.String? -function M:parseString() +function Ast:parseString() local token = self.lexer:peek() if token == '"' or token == "'" @@ -76,7 +74,7 @@ end -- 解析短字符串 ---@return LuaParser.Node.String? -function M:parseShortString() +function Ast:parseShortString() local quo, _, pos = self.lexer:peek() if quo ~= '"' and quo ~= "'" and quo ~= '`' then return nil @@ -261,7 +259,7 @@ end -- 解析长字符串 ---@return LuaParser.Node.String? -function M:parseLongString() +function Ast:parseLongString() local _, _, pos = self.lexer:peek() local quo = self.code:match('^(%[=*%[)', pos + 1) if not quo then diff --git a/src/parser/ast/var.lua b/src/parser/ast/var.lua new file mode 100644 index 0000000..5892501 --- /dev/null +++ b/src/parser/ast/var.lua @@ -0,0 +1,7 @@ +local class = require 'class' + +---@alias LuaParser.Node.ID LuaParser.Node.Var + +---@class LuaParser.Node.Var: LuaParser.Node.Base +---@field loc? LuaParser.Node.Local +local Var = class.declare('LuaParser.Node.Var', 'LuaParser.Node.Base') diff --git a/src/parser/compile.lua b/src/parser/compile.lua index 7c67066..2de5ff0 100644 --- a/src/parser/compile.lua +++ b/src/parser/compile.lua @@ -3,7 +3,7 @@ local class = require 'class' require 'parser.ast.ast' ---@class LuaParser -local M = class.get 'LuaParser' +local Ast = class.get 'LuaParser' ---@alias LuaParser.LuaVersion ---| 'Lua 5.1' @@ -29,7 +29,7 @@ local M = class.get 'LuaParser' ---@param version? LuaParser.LuaVersion # 默认为 '5.4' ---@param options? LuaParser.CompileOptions ---@return LuaParser.Ast -function M.compile(code, version, options) +function Ast.compile(code, version, options) local ast = class.new 'LuaParser.Ast' (code, version, options) ast:parseMain() return ast diff --git a/test/ast/Nil.lua b/test/ast/Nil.lua index ebdfca7..ba3007d 100644 --- a/test/ast/Nil.lua +++ b/test/ast/Nil.lua @@ -11,13 +11,11 @@ end TEST [[nil]] { - type = "nil", left = 0, right = 3, } TEST [[ nil]] { - type = "nil", left = 3, right = 6, } diff --git a/test/ast/boolean.lua b/test/ast/boolean.lua index bc31677..6f5ebb9 100644 --- a/test/ast/boolean.lua +++ b/test/ast/boolean.lua @@ -11,14 +11,12 @@ end TEST [[true]] { - type = "boolean", left = 0, finish = 4, value = true, } TEST [[false]] { - type = "boolean", left = 0, right = 5, value = false, diff --git a/test/ast/comment.lua b/test/ast/comment.lua index c93f2e0..3ed02fa 100644 --- a/test/ast/comment.lua +++ b/test/ast/comment.lua @@ -11,7 +11,6 @@ end TEST [[--AAA]] { - type = "comment", left = 0, right = 5, subtype = 'short', @@ -20,7 +19,6 @@ TEST [[--AAA]] TEST [[//AAA]] { - type = "comment", left = 0, right = 5, subtype = 'short', @@ -31,7 +29,6 @@ TEST [===[--[[ 1234 ]]]===] { - type = "comment", left = 0, right = 20002, subtype = 'long', @@ -42,7 +39,6 @@ TEST [===[/* 1234 */]===] { - type = "comment", left = 0, right = 20002, subtype = 'long', diff --git a/test/ast/local.lua b/test/ast/local.lua index d4a26ac..eed1d6f 100644 --- a/test/ast/local.lua +++ b/test/ast/local.lua @@ -11,12 +11,10 @@ end TEST [[local x]] { - type = 'localdef', left = 0, finish = 7, vars = { [1] = { - type = 'local', start = 6, finish = 7, id = 'x', @@ -27,26 +25,22 @@ TEST [[local x]] TEST [[local x, y, z]] { - type = 'localdef', left = 0, finish = 13, vars = { [1] = { - type = 'local', start = 6, finish = 7, id = 'x', index = 1, }, [2] = { - type = 'local', start = 9, finish = 10, id = 'y', index = 2, }, [3] = { - type = 'local', start = 12, finish = 13, id = 'z', @@ -57,17 +51,14 @@ TEST [[local x, y, z]] TEST [[local x = 1]] { - type = 'localdef', left = 0, finish = 11, vars = { [1] = { - type = 'local', start = 6, finish = 7, id = 'x', value = { - type = 'integer', start = 10, finish = 11, value = 1, @@ -76,7 +67,6 @@ TEST [[local x = 1]] }, values = { [1] = { - type = 'integer', start = 10, finish = 11, value = 1, @@ -86,29 +76,24 @@ TEST [[local x = 1]] TEST [[local x, y = 1, 2, 3]] { - type = 'localdef', left = 0, finish = 20, vars = { [1] = { - type = 'local', start = 6, finish = 7, id = 'x', value = { - type = 'integer', start = 13, finish = 14, value = 1, } }, [2] = { - type = 'local', start = 9, finish = 10, id = 'y', value = { - type = 'integer', start = 16, finish = 17, value = 2, @@ -117,19 +102,16 @@ TEST [[local x, y = 1, 2, 3]] }, values = { [1] = { - type = 'integer', start = 13, finish = 14, value = 1, }, [2] = { - type = 'integer', start = 16, finish = 17, value = 2, }, [3] = { - type = 'integer', start = 19, finish = 20, value = 3, diff --git a/test/ast/number.lua b/test/ast/number.lua index 49f76c6..55521a0 100644 --- a/test/ast/number.lua +++ b/test/ast/number.lua @@ -11,7 +11,6 @@ end TEST '345' { - type = "integer", left = 0, right = 3, value = 345, @@ -20,7 +19,6 @@ TEST '345' } TEST '345.0' { - type = "float", left = 0, right = 5, value = 0x1.59p+8, @@ -29,7 +27,6 @@ TEST '345.0' } TEST '0xff' { - type = "integer", left = 0, right = 4, value = 255, @@ -38,7 +35,6 @@ TEST '0xff' } TEST '314.16e-2' { - type = "float", left = 0, right = 9, value = 3.1416, @@ -47,7 +43,6 @@ TEST '314.16e-2' } TEST '0.31416E1' { - type = "float", left = 0, right = 9, value = 0x1.921ff2e48e8a7p+1, @@ -56,7 +51,6 @@ TEST '0.31416E1' } TEST '.31416E1' { - type = "float", left = 0, right = 8, value = 0x1.921ff2e48e8a7p+1, @@ -65,7 +59,6 @@ TEST '.31416E1' } TEST '34e1' { - type = "float", left = 0, right = 4, value = 0x1.54p+8, @@ -74,7 +67,6 @@ TEST '34e1' } TEST '0x0.1E' { - type = "float", left = 0, right = 6, value = 0x1.ep-4, @@ -83,7 +75,6 @@ TEST '0x0.1E' } TEST '0xA23p-4' { - type = "float", left = 0, right = 8, value = 0x1.446p+7, @@ -92,7 +83,6 @@ TEST '0xA23p-4' } TEST '0X1.921FB54442D18P+1' { - type = "float", left = 0, right = 20, value = 0x1.921fb54442d18p+1, @@ -101,7 +91,6 @@ TEST '0X1.921FB54442D18P+1' } TEST '-345' { - type = "integer", left = 0, right = 4, value = -345, @@ -110,7 +99,6 @@ TEST '-345' } TEST '0b110110' { - type = "integer", left = 0, right = 8, value = 54, @@ -119,7 +107,6 @@ TEST '0b110110' } TEST '123ll' { - type = "integer", left = 0, right = 5, value = 123, @@ -128,7 +115,6 @@ TEST '123ll' } TEST '123ull' { - type = "integer", left = 0, right = 6, value = 123, @@ -137,7 +123,6 @@ TEST '123ull' } TEST '123llu' { - type = "integer", left = 0, right = 6, value = 123, @@ -146,7 +131,6 @@ TEST '123llu' } TEST '123i' { - type = "integer", left = 0, right = 4, value = 0, @@ -156,7 +140,6 @@ TEST '123i' } TEST '123.45i' { - type = "float", left = 0, right = 7, value = 0, diff --git a/test/ast/string.lua b/test/ast/string.lua index c541a83..f998837 100644 --- a/test/ast/string.lua +++ b/test/ast/string.lua @@ -11,7 +11,6 @@ end TEST [['123']] { - type = "string", left = 0, right = 5, value = "123", @@ -19,7 +18,6 @@ TEST [['123']] } TEST [['123\'']] { - type = "string", left = 0, right = 7, escs = { @@ -33,7 +31,6 @@ TEST [['123\'']] TEST [['123\z 345']] { - type = "string", left = 0, right = 10008, escs = { @@ -48,7 +45,6 @@ TEST [['123\z TEST [['123\ 345']] { - type = "string", left = 0, right = 10004, escs = { @@ -62,7 +58,6 @@ TEST [['123\ } TEST [===[[[123]]]===] { - type = "string", left = 0, right = 7, value = "123", @@ -71,7 +66,6 @@ TEST [===[[[123]]]===] TEST [===[[[123 345]]]===] { - type = "string", left = 0, right = 10005, value = "123\ @@ -80,7 +74,6 @@ TEST [===[[[123 } TEST [['alo\n123"']] { - type = "string", left = 0, right = 11, escs = { @@ -94,7 +87,6 @@ TEST [['alo\n123"']] } TEST [['\97lo\10\04923"']] { - type = "string", left = 0, right = 17, escs = { @@ -114,7 +106,6 @@ TEST [['\97lo\10\04923"']] } TEST [['\xff']] { - type = "string", left = 0, right = 6, escs = { @@ -127,7 +118,6 @@ TEST [['\xff']] } TEST [['\x1A']] { - type = "string", left = 0, right = 6, escs = { @@ -140,7 +130,6 @@ TEST [['\x1A']] } TEST [['\32']] { - type = "string", left = 0, right = 5, escs = { @@ -153,7 +142,6 @@ TEST [['\32']] } TEST [['\123']] { - type = "string", left = 0, right = 6, escs = { @@ -166,7 +154,6 @@ TEST [['\123']] } TEST [['\0123']] { - type = "string", left = 0, right = 7, escs = { @@ -179,7 +166,6 @@ TEST [['\0123']] } TEST [['\492']] { - type = "string", left = 0, right = 6, escs = { @@ -192,7 +178,6 @@ TEST [['\492']] } TEST [['\u{3b1}']] { - type = "string", left = 0, right = 9, escs = { @@ -205,7 +190,6 @@ TEST [['\u{3b1}']] } TEST [['\u{0}']] { - type = "string", left = 0, right = 7, escs = { @@ -218,7 +202,6 @@ TEST [['\u{0}']] } TEST [['\u{ffffff}']] { - type = "string", left = 0, right = 12, escs = { @@ -232,7 +215,6 @@ TEST [['\u{ffffff}']] TEST [=[[[ abcdef]]]=] { - type = "string", left = 0, right = 10008, value = "abcdef", @@ -240,7 +222,6 @@ abcdef]]]=] } TEST [['aaa]] { - type = "string", left = 0, right = 4, value = "aaa", @@ -249,7 +230,6 @@ TEST [['aaa]] TEST [['aaa ]] { - type = "string", left = 0, right = 4, value = "aaa", @@ -257,7 +237,6 @@ TEST [['aaa } TEST [[`12345`]] { - type = "string", left = 0, right = 7, value = "12345", From da43c956bf44528aaf52372abd3c957edb415248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Tue, 12 Sep 2023 18:12:51 +0800 Subject: [PATCH 16/94] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/base.lua | 4 ---- src/parser/ast/boolean.lua | 2 -- src/parser/ast/comment.lua | 2 -- src/parser/ast/error.lua | 1 - src/parser/ast/id.lua | 4 ++++ src/parser/ast/local.lua | 6 ------ src/parser/ast/nil.lua | 2 -- src/parser/ast/number.lua | 4 ---- src/parser/ast/string.lua | 2 -- src/parser/ast/var.lua | 2 -- 10 files changed, 4 insertions(+), 25 deletions(-) diff --git a/src/parser/ast/base.lua b/src/parser/ast/base.lua index f51d8f2..fb145cb 100644 --- a/src/parser/ast/base.lua +++ b/src/parser/ast/base.lua @@ -16,10 +16,6 @@ local Base = class.declare 'LuaParser.Node.Base' local rowcolMulti = 10000 -Base.__getter.type = function () - error('No type') -end - ---@param self LuaParser.Node.Base ---@return integer ---@return true diff --git a/src/parser/ast/boolean.lua b/src/parser/ast/boolean.lua index bd6d456..5e26c64 100644 --- a/src/parser/ast/boolean.lua +++ b/src/parser/ast/boolean.lua @@ -5,8 +5,6 @@ local class = require 'class' ---@field view string local Boolean = class.declare('LuaParser.Node.Boolean', 'LuaParser.Node.Base') -Boolean.type = 'boolean' - Boolean.__getter.view = function (self) return tostring(self.value), true end diff --git a/src/parser/ast/comment.lua b/src/parser/ast/comment.lua index 148206b..6464062 100644 --- a/src/parser/ast/comment.lua +++ b/src/parser/ast/comment.lua @@ -5,8 +5,6 @@ local class = require 'class' ---@field value string local Comment = class.declare('LuaParser.Node.Comment', 'LuaParser.Node.Base') -Comment.type = 'comment' - ---@param self LuaParser.Node.Comment ---@return string ---@return true diff --git a/src/parser/ast/error.lua b/src/parser/ast/error.lua index d6118c2..3de44cd 100644 --- a/src/parser/ast/error.lua +++ b/src/parser/ast/error.lua @@ -3,7 +3,6 @@ local class = require 'class' ---@class LuaParser.Node.Error local Error = class.declare('LuaParser.Node.Error', 'LuaParser.Node.Base') -Error.type = 'error' Error.code = 'UNKNOWN' ---@class LuaParser.Ast diff --git a/src/parser/ast/id.lua b/src/parser/ast/id.lua index 079b8d3..63badf4 100644 --- a/src/parser/ast/id.lua +++ b/src/parser/ast/id.lua @@ -3,6 +3,10 @@ local class = require 'class' ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@alias LuaParser.Node.ID +---| LuaParser.Node.Local +---| LuaParser.Node.Var + ---@generic T: LuaParser.Node.ID ---@param nodeType `T` ---@param required? true diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index c15050f..7944e97 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -5,10 +5,6 @@ local class = require 'class' ---@field values? LuaParser.Node.Exp[] local LocalDef = class.declare('LuaParser.Node.LocalDef', 'LuaParser.Node.Base') -LocalDef.type = 'localdef' - ----@alias LuaParser.Node.ID LuaParser.Node.Local - ---@class LuaParser.Node.Local: LuaParser.Node.Base ---@field parent LuaParser.Node.LocalDef ---@field index integer @@ -16,8 +12,6 @@ LocalDef.type = 'localdef' ---@field refs? LuaParser.Node.Var[] local Local = class.declare('LuaParser.Node.Local', 'LuaParser.Node.Base') -Local.type = 'local' - ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' diff --git a/src/parser/ast/nil.lua b/src/parser/ast/nil.lua index 4d0706a..c2ed532 100644 --- a/src/parser/ast/nil.lua +++ b/src/parser/ast/nil.lua @@ -3,8 +3,6 @@ local class = require 'class' ---@class LuaParser.Node.Nil: LuaParser.Node.Base local Nil = class.declare('LuaParser.Node.Nil', 'LuaParser.Node.Base') -Nil.type = 'nil' - Nil.view = 'nil' ---@class LuaParser.Ast diff --git a/src/parser/ast/number.lua b/src/parser/ast/number.lua index d231e62..f0fb05b 100644 --- a/src/parser/ast/number.lua +++ b/src/parser/ast/number.lua @@ -9,8 +9,6 @@ local class = require 'class' ---@field view string local Float = class.declare('LuaParser.Node.Float', 'LuaParser.Node.Base') -Float.type = 'float' - Float.value = 0.0 ---@param self LuaParser.Node.Float @@ -50,8 +48,6 @@ end ---@field view string local Integer = class.declare('LuaParser.Node.Integer', 'LuaParser.Node.Base') -Integer.type = 'integer' - Integer.value = 0 ---@param self LuaParser.Node.Integer diff --git a/src/parser/ast/string.lua b/src/parser/ast/string.lua index bf8417e..baaaf1f 100644 --- a/src/parser/ast/string.lua +++ b/src/parser/ast/string.lua @@ -10,8 +10,6 @@ local class = require 'class' ---@field missQuo? true local String = class.declare('LuaParser.Node.String', 'LuaParser.Node.Base') -String.type = 'string' - local escMap = { ['a'] = '\a', ['b'] = '\b', diff --git a/src/parser/ast/var.lua b/src/parser/ast/var.lua index 5892501..934bf38 100644 --- a/src/parser/ast/var.lua +++ b/src/parser/ast/var.lua @@ -1,7 +1,5 @@ local class = require 'class' ----@alias LuaParser.Node.ID LuaParser.Node.Var - ---@class LuaParser.Node.Var: LuaParser.Node.Base ---@field loc? LuaParser.Node.Local local Var = class.declare('LuaParser.Node.Var', 'LuaParser.Node.Base') From c4c938a5eaab3d58b27b615ee8741c7d91d68d79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Tue, 12 Sep 2023 18:30:39 +0800 Subject: [PATCH 17/94] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/exp.lua | 29 +- src/parser/ast/var.lua | 13 + test/ast/exp.lua | 1593 ++++++++++++++++++++++++++++++++++++++++ test/ast/init.lua | 1 + 4 files changed, 1631 insertions(+), 5 deletions(-) create mode 100644 test/ast/exp.lua diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index 00e35d3..6ca069f 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -4,16 +4,14 @@ local class = require 'class' local Ast = class.declare 'LuaParser.Ast' ---@alias LuaParser.Node.Exp ----| LuaParser.Node.Nil ----| LuaParser.Node.Boolean ----| LuaParser.Node.Number ----| LuaParser.Node.String +---| LuaParser.Node.Term +-- 解析表达式 ---@param required? true ---@return LuaParser.Node.Exp? function Ast:parseExp(required) -- TODO - local exp = self:parseNumber() + local exp = self:parseTerm() if not exp and required then self:throw('MISS_EXP', self:getLastPos(), self:getLastPos()) @@ -22,6 +20,7 @@ function Ast:parseExp(required) return exp end +-- 解析表达式列表,以逗号分隔 ---@param atLeastOne? true ---@return LuaParser.Node.Exp[] function Ast:parseExpList(atLeastOne) @@ -55,3 +54,23 @@ function Ast:parseExpList(atLeastOne) end return list end + +---@alias LuaParser.Node.Term +---| LuaParser.Node.Nil +---| LuaParser.Node.Boolean +---| LuaParser.Node.Number +---| LuaParser.Node.String +---| LuaParser.Node.Var + +-- 解析表达式中的一项 +---@return LuaParser.Node.Term? +function Ast:parseTerm() + -- TODO + local term = self:parseNil() + or self:parseBoolean() + or self:parseNumber() + or self:parseString() + or self:parseVar() + + return term +end diff --git a/src/parser/ast/var.lua b/src/parser/ast/var.lua index 934bf38..9df3ff1 100644 --- a/src/parser/ast/var.lua +++ b/src/parser/ast/var.lua @@ -3,3 +3,16 @@ local class = require 'class' ---@class LuaParser.Node.Var: LuaParser.Node.Base ---@field loc? LuaParser.Node.Local local Var = class.declare('LuaParser.Node.Var', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@return LuaParser.Node.Var? +function Ast:parseVar() + local var = self:parseID('LuaParser.Node.Var') + if not var then + return nil + end + + return var +end diff --git a/test/ast/exp.lua b/test/ast/exp.lua new file mode 100644 index 0000000..431b61a --- /dev/null +++ b/test/ast/exp.lua @@ -0,0 +1,1593 @@ +local class = require 'class' + +local function TEST(code) + return function (expect) + local ast = class.new 'LuaParser.Ast' (code) + local node = ast:parseExp() + assert(node) + Match(node, expect) + end +end + +TEST 'nil' +{ + start = 0, + finish = 3, +} + +TEST 'a' +{ + start = 0, + finish = 1, + id = 'a' +} + +TEST 'a.b' +{ + start = 0, + finish = 3, + node = "", + dot = { + type = ".", + start = 1, + finish = 2, + }, + field = { + type = "field", + start = 2, + finish = 3, + parent = "", + [1] = "b", + }, +} + +TEST 'a.b.c' +{ + type = "getfield", + start = 0, + finish = 5, + node = "", + dot = { + type = ".", + start = 3, + finish = 4, + }, + field = { + type = "field", + start = 4, + finish = 5, + parent = "", + [1] = "c", + }, +} +TEST 'func()' +{ + type = "call", + start = 0, + finish = 6, + node = "", +} +TEST 'a.b.c()' +{ + type = "call", + start = 0, + finish = 7, + node = "", +} +TEST '1 or 2' +{ + type = "binary", + start = 0, + finish = 6, + op = { + type = "or", + start = 2, + finish = 4, + }, + [1] = { + type = "integer", + start = 0, + finish = 1, + parent = "", + [1] = 1, + }, + [2] = { + type = "integer", + start = 5, + finish = 6, + parent = "", + [1] = 2, + }, +} +TEST '1 < 2' +{ + type = "binary", + start = 0, + finish = 5, + op = { + type = "<", + start = 2, + finish = 3, + }, + [1] = { + type = "integer", + start = 0, + finish = 1, + parent = "", + [1] = 1, + }, + [2] = { + type = "integer", + start = 4, + finish = 5, + parent = "", + [1] = 2, + }, +} +TEST '- 1' +{ + type = "integer", + start = 0, + finish = 3, + [1] = -1, +} +TEST 'not not true' +{ + type = "unary", + start = 0, + finish = 12, + op = { + type = "not", + start = 0, + finish = 3, + }, + [1] = { + type = "unary", + start = 4, + finish = 12, + parent = "", + op = { + type = "not", + start = 4, + finish = 7, + }, + [1] = { + type = "boolean", + start = 8, + finish = 12, + parent = "", + [1] = true, + }, + }, +} +TEST '1 ^ 2' +{ + type = "binary", + start = 0, + finish = 5, + op = { + type = "^", + start = 2, + finish = 3, + }, + [1] = { + type = "integer", + start = 0, + finish = 1, + parent = "", + [1] = 1, + }, + [2] = { + type = "integer", + start = 4, + finish = 5, + parent = "", + [1] = 2, + }, +} +TEST '1 ^ -2' +{ + type = "binary", + start = 0, + finish = 6, + op = { + type = "^", + start = 2, + finish = 3, + }, + [1] = { + type = "integer", + start = 0, + finish = 1, + parent = "", + [1] = 1, + }, + [2] = { + type = "integer", + start = 4, + finish = 6, + parent = "", + [1] = -2, + }, +} +TEST '-1 ^ 2' +{ + type = "unary", + start = 0, + finish = 6, + op = { + type = "-", + start = 0, + finish = 1, + }, + [1] = { + type = "binary", + start = 1, + finish = 6, + parent = "", + op = { + type = "^", + start = 3, + finish = 4, + }, + [1] = { + type = 'integer', + start = 1, + finish = 2, + parent = "", + [1] = 1, + }, + [2] = { + type = 'integer', + start = 5, + finish = 6, + parent = "", + [1] = 2, + }, + }, +} +TEST '...' +{ + type = "varargs", + start = 0, + finish = 3, +} +TEST '1 + 2 + 3' +{ + type = "binary", + start = 0, + finish = 9, + op = { + type = "+", + start = 6, + finish = 7, + }, + [1] = { + type = "binary", + start = 0, + finish = 5, + parent = "", + op = { + type = "+", + start = 2, + finish = 3, + }, + [1] = { + type = "integer", + start = 0, + finish = 1, + parent = "", + [1] = 1, + }, + [2] = { + type = "integer", + start = 4, + finish = 5, + parent = "", + [1] = 2, + }, + }, + [2] = { + type = "integer", + start = 8, + finish = 9, + parent = "", + [1] = 3, + }, +} +TEST '1 + 2 * 3' +{ + type = "binary", + start = 0, + finish = 9, + op = { + type = "+", + start = 2, + finish = 3, + }, + [1] = { + type = "integer", + start = 0, + finish = 1, + parent = "", + [1] = 1, + }, + [2] = { + type = "binary", + start = 4, + finish = 9, + parent = "", + op = { + type = "*", + start = 6, + finish = 7, + }, + [1] = { + type = "integer", + start = 4, + finish = 5, + parent = "", + [1] = 2, + }, + [2] = { + type = "integer", + start = 8, + finish = 9, + parent = "", + [1] = 3, + }, + }, +} +TEST '- 1 + 2 * 3' +{ + type = "binary", + start = 0, + finish = 11, + op = { + type = "+", + start = 4, + finish = 5, + }, + [1] = { + type = "integer", + start = 0, + finish = 3, + parent = "", + [1] = -1, + }, + [2] = { + type = "binary", + start = 6, + finish = 11, + parent = "", + op = { + type = "*", + start = 8, + finish = 9, + }, + [1] = { + type = "integer", + start = 6, + finish = 7, + parent = "", + [1] = 2, + }, + [2] = { + type = "integer", + start = 10, + finish = 11, + parent = "", + [1] = 3, + }, + }, +} +TEST '-1 + 2 * 3' +{ + type = "binary", + start = 0, + finish = 10, + op = { + type = "+", + start = 3, + finish = 4, + }, + [1] = { + type = "integer", + start = 0, + finish = 2, + parent = "", + [1] = -1, + }, + [2] = { + type = "binary", + start = 5, + finish = 10, + parent = "", + op = { + type = "*", + start = 7, + finish = 8, + }, + [1] = { + type = "integer", + start = 5, + finish = 6, + parent = "", + [1] = 2, + }, + [2] = { + type = "integer", + start = 9, + finish = 10, + parent = "", + [1] = 3, + }, + }, +} +CHECK"x and y == 'unary' and z" +{ + type = "binary", + start = 0, + finish = 24, + op = { + type = "and", + start = 19, + finish = 22, + }, + [1] = { + type = "binary", + start = 0, + finish = 18, + parent = "", + op = { + type = "and", + start = 2, + finish = 5, + }, + [1] = { + type = "getglobal", + start = 0, + finish = 1, + parent = "", + [1] = "x", + }, + [2] = { + type = "binary", + start = 6, + finish = 18, + parent = "", + op = { + type = "==", + start = 8, + finish = 10, + }, + [1] = { + type = "getglobal", + start = 6, + finish = 7, + parent = "", + [1] = "y", + }, + [2] = { + type = "string", + start = 11, + finish = 18, + parent = "", + [1] = "unary", + [2] = "'", + }, + }, + }, + [2] = { + type = "getglobal", + start = 23, + finish = 24, + parent = "", + [1] = "z", + }, +} +CHECK"x and y or '' .. z" +{ + type = "binary", + start = 0, + finish = 18, + op = { + type = "or", + start = 8, + finish = 10, + }, + [1] = { + type = "binary", + start = 0, + finish = 7, + parent = "", + op = { + type = "and", + start = 2, + finish = 5, + }, + [1] = { + type = "getglobal", + start = 0, + finish = 1, + parent = "", + [1] = "x", + }, + [2] = { + type = "getglobal", + start = 6, + finish = 7, + parent = "", + [1] = "y", + }, + }, + [2] = { + type = "binary", + start = 11, + finish = 18, + parent = "", + op = { + type = "..", + start = 14, + finish = 16, + }, + [1] = { + type = "string", + start = 11, + finish = 13, + parent = "", + [1] = "", + [2] = "'", + }, + [2] = { + type = "getglobal", + start = 17, + finish = 18, + parent = "", + [1] = "z", + }, + }, +} +-- 幂运算从右向左连接 +TEST '1 ^ 2 ^ 3' +{ + type = "binary", + start = 0, + finish = 9, + op = { + type = "^", + start = 2, + finish = 3, + }, + [1] = { + type = "integer", + start = 0, + finish = 1, + parent = "", + [1] = 1, + }, + [2] = { + type = "binary", + start = 4, + finish = 9, + parent = "", + op = { + type = "^", + start = 6, + finish = 7, + }, + [1] = { + type = "integer", + start = 4, + finish = 5, + parent = "", + [1] = 2, + }, + [2] = { + type = "integer", + start = 8, + finish = 9, + parent = "", + [1] = 3, + }, + }, +} +-- 连接运算从右向左连接 +TEST '1 .. 2 .. 3' +{ + type = "binary", + start = 0, + finish = 11, + op = { + type = "..", + start = 2, + finish = 4, + }, + [1] = { + type = "integer", + start = 0, + finish = 1, + parent = "", + [1] = 1, + }, + [2] = { + type = "binary", + start = 5, + finish = 11, + parent = "", + op = { + type = "..", + start = 7, + finish = 9, + }, + [1] = { + type = "integer", + start = 5, + finish = 6, + parent = "", + [1] = 2, + }, + [2] = { + type = "integer", + start = 10, + finish = 11, + parent = "", + [1] = 3, + }, + }, +} +TEST '1 + - - - - - - - 1' +{ + type = "binary", + start = 0, + finish = 19, + op = { + type = "+", + start = 2, + finish = 3, + }, + [1] = { + type = "integer", + start = 0, + finish = 1, + parent = "", + [1] = 1, + }, + [2] = { + type = "integer", + start = 4, + finish = 19, + parent = "", + [1] = -1, + }, +} +TEST '(1)' +{ + type = "paren", + start = 0, + finish = 3, + exp = { + type = "integer", + start = 1, + finish = 2, + parent = "", + [1] = 1, + }, +} +TEST '(1 + 2)' +{ + type = "paren", + start = 0, + finish = 7, + exp = { + type = "binary", + start = 1, + finish = 6, + parent = "", + op = { + type = "+", + start = 3, + finish = 4, + }, + [1] = { + type = "integer", + start = 1, + finish = 2, + parent = "", + [1] = 1, + }, + [2] = { + type = "integer", + start = 5, + finish = 6, + parent = "", + [1] = 2, + }, + }, +} +TEST 'func(1)' +{ + type = "call", + start = 0, + finish = 7, + node = "", + args = { + type = "callargs", + start = 4, + finish = 7, + parent = "", + [1] = { + type = "integer", + start = 5, + finish = 6, + parent = "", + [1] = 1, + }, + }, +} +TEST 'func(1, 2)' +{ + type = "call", + start = 0, + finish = 10, + node = "", + args = { + type = "callargs", + start = 4, + finish = 10, + parent = "", + [1] = { + type = "integer", + start = 5, + finish = 6, + parent = "", + [1] = 1, + }, + [2] = { + type = "integer", + start = 8, + finish = 9, + parent = "", + [1] = 2, + }, + }, +} +TEST 'func(...)' +{ + type = "call", + start = 0, + finish = 9, + node = "", + args = { + type = "callargs", + start = 4, + finish = 9, + parent = "", + [1] = { + type = "varargs", + start = 5, + finish = 8, + parent = "", + }, + }, +} +TEST 'func(1, ...)' +{ + type = "call", + start = 0, + finish = 12, + node = "", + args = { + type = "callargs", + start = 4, + finish = 12, + parent = "", + [1] = { + type = "integer", + start = 5, + finish = 6, + parent = "", + [1] = 1, + }, + [2] = { + type = "varargs", + start = 8, + finish = 11, + parent = "", + }, + }, +} +TEST 'func ""' +{ + type = "call", + start = 0, + finish = 7, + node = "", + args = { + type = "callargs", + start = 5, + finish = 7, + parent = "", + [1] = { + type = "string", + start = 5, + finish = 7, + parent = "", + [1] = "", + [2] = "\"", + }, + }, +} +TEST 'func {}' +{ + type = "call", + start = 0, + finish = 7, + node = "", + args = { + type = "callargs", + start = 5, + finish = 7, + parent = "", + [1] = { + type = "table", + start = 5, + finish = 7, + parent = "", + }, + }, +} +TEST 'table[1]' +{ + type = "getindex", + start = 0, + finish = 8, + node = "", + index = { + type = "integer", + start = 6, + finish = 7, + parent = "", + [1] = 1, + }, +} +TEST 'table[[1]]' +{ + type = "call", + start = 0, + finish = 10, + node = "", + args = { + type = "callargs", + start = 5, + finish = 10, + parent = "", + [1] = { + type = "string", + start = 5, + finish = 10, + parent = "", + [1] = '1', + [2] = '[[' + }, + }, +} +TEST 'get_point().x' +{ + type = "getfield", + start = 0, + finish = 13, + node = "", + dot = { + type = ".", + start = 11, + finish = 12, + }, + field = { + type = "field", + start = 12, + finish = 13, + parent = "", + [1] = "x", + }, +} +TEST 'obj:remove()' +{ + type = "call", + start = 0, + finish = 12, + node = "", + args = { + type = "callargs", + start = 0, + finish = 12, + parent = "", + [1] = { + type = "self", + start = 3, + finish = 4, + parent = "", + [1] = "self", + }, + }, +} +TEST '(...)[1]' +{ + type = "getindex", + start = 0, + finish = 8, + node = "", + index = { + type = "integer", + start = 6, + finish = 7, + parent = "", + [1] = 1, + }, +} +TEST 'function () end' +{ + type = "function", + start = 0, + bstart = 11, + finish = 15, + keyword = { + [1] = 0, + [2] = 8, + [3] = 12, + [4] = 15, + }, + args = { + type = "funcargs", + start = 9, + finish = 11, + parent = "", + }, +} +TEST 'function (...) end' +{ + type = "function", + start = 0, + bstart = 14, + finish = 18, + keyword = { + [1] = 0, + [2] = 8, + [3] = 15, + [4] = 18, + }, + vararg = "", + args = { + type = "funcargs", + start = 9, + finish = 14, + parent = "", + [1] = { + type = "...", + start = 10, + finish = 13, + parent = "", + [1] = "...", + }, + }, +} +TEST 'function (a, ...) end' +{ + type = "function", + start = 0, + bstart = 17, + finish = 21, + keyword = { + [1] = 0, + [2] = 8, + [3] = 18, + [4] = 21, + }, + vararg = "", + args = { + type = "funcargs", + start = 9, + finish = 17, + parent = "", + [1] = { + type = "local", + start = 10, + finish = 11, + effect = 11, + parent = "", + [1] = "a", + }, + [2] = { + type = "...", + start = 13, + finish = 16, + parent = "", + [1] = "...", + }, + }, + locals = "", +} +TEST '{}' +{ + type = "table", + start = 0, + finish = 2, +} +TEST '{...}' +{ + type = "table", + start = 0, + finish = 5, + [1] = { + type = "varargs", + start = 1, + finish = 4, + parent = "", + }, +} +TEST '{1, 2, 3}' +{ + type = "table", + start = 0, + finish = 9, + [1] = { + type = "tableexp", + start = 1, + finish = 2, + tindex = 1, + parent = "", + value = { + type = "integer", + start = 1, + finish = 2, + parent = "", + [1] = 1, + }, + }, + [2] = { + type = "tableexp", + start = 4, + finish = 5, + tindex = 2, + parent = "", + value = { + type = "integer", + start = 4, + finish = 5, + parent = "", + [1] = 2, + }, + }, + [3] = { + type = "tableexp", + start = 7, + finish = 8, + tindex = 3, + parent = "", + value = { + type = "integer", + start = 7, + finish = 8, + parent = "", + [1] = 3, + }, + }, +} +TEST '{x = 1, y = 2}' +{ + type = "table", + start = 0, + finish = 14, + [1] = { + type = "tablefield", + start = 1, + finish = 2, + range = 6, + parent = "", + node = "", + field = { + type = "field", + start = 1, + finish = 2, + parent = "", + [1] = "x", + }, + value = { + type = "integer", + start = 5, + finish = 6, + parent = "", + [1] = 1, + }, + }, + [2] = { + type = "tablefield", + start = 8, + finish = 9, + range = 13, + parent = "", + node = "", + field = { + type = "field", + start = 8, + finish = 9, + parent = "", + [1] = "y", + }, + value = { + type = "integer", + start = 12, + finish = 13, + parent = "", + [1] = 2, + }, + }, +} +TEST '{["x"] = 1, ["y"] = 2}' +{ + type = "table", + start = 0, + finish = 22, + [1] = { + type = "tableindex", + start = 1, + finish = 6, + range = 10, + parent = "", + node = "", + index = { + type = "string", + start = 2, + finish = 5, + parent = "", + [1] = "x", + [2] = "\"", + }, + value = { + type = "integer", + start = 9, + finish = 10, + parent = "", + [1] = 1, + }, + }, + [2] = { + type = "tableindex", + start = 12, + finish = 17, + range = 21, + parent = "", + node = "", + index = { + type = "string", + start = 13, + finish = 16, + parent = "", + [1] = "y", + [2] = "\"", + }, + value = { + type = "integer", + start = 20, + finish = 21, + parent = "", + [1] = 2, + }, + }, +} +TEST '{[x] = 1, [y] = 2}' +{ + type = "table", + start = 0, + finish = 18, + [1] = { + type = "tableindex", + start = 1, + finish = 4, + range = 8, + parent = "", + node = "", + index = { + type = "getglobal", + start = 2, + finish = 3, + parent = "", + [1] = "x", + }, + value = { + type = "integer", + start = 7, + finish = 8, + parent = "", + [1] = 1, + }, + }, + [2] = { + type = "tableindex", + start = 10, + finish = 13, + range = 17, + parent = "", + node = "", + index = { + type = "getglobal", + start = 11, + finish = 12, + parent = "", + [1] = "y", + }, + value = { + type = "integer", + start = 16, + finish = 17, + parent = "", + [1] = 2, + }, + }, +} +TEST '{x = 1, y = 2, 3}' +{ + type = "table", + start = 0, + finish = 17, + [1] = { + type = "tablefield", + start = 1, + finish = 2, + range = 6, + parent = "", + node = "", + field = { + type = "field", + start = 1, + finish = 2, + parent = "", + [1] = "x", + }, + value = { + type = "integer", + start = 5, + finish = 6, + parent = "", + [1] = 1, + }, + }, + [2] = { + type = "tablefield", + start = 8, + finish = 9, + range = 13, + parent = "", + node = "", + field = { + type = "field", + start = 8, + finish = 9, + parent = "", + [1] = "y", + }, + value = { + type = "integer", + start = 12, + finish = 13, + parent = "", + [1] = 2, + }, + }, + [3] = { + type = "tableexp", + start = 15, + finish = 16, + tindex = 1, + parent = "", + value = { + type = "integer", + start = 15, + finish = 16, + parent = "", + [1] = 3, + }, + }, +} +TEST '{{}}' +{ + type = "table", + start = 0, + finish = 4, + [1] = { + type = "tableexp", + start = 1, + finish = 3, + tindex = 1, + parent = "", + value = { + type = "table", + start = 1, + finish = 3, + parent = "", + }, + }, +} +TEST '{ a = { b = { c = {} } } }' +{ + type = "table", + start = 0, + finish = 26, + [1] = { + type = "tablefield", + start = 2, + finish = 3, + range = 24, + parent = "", + node = "", + field = { + type = "field", + start = 2, + finish = 3, + parent = "", + [1] = "a", + }, + value = { + type = "table", + start = 6, + finish = 24, + parent = "", + [1] = { + type = "tablefield", + start = 8, + finish = 9, + range = 22, + parent = "", + node = "", + field = { + type = "field", + start = 8, + finish = 9, + parent = "", + [1] = "b", + }, + value = { + type = "table", + start = 12, + finish = 22, + parent = "", + [1] = { + type = "tablefield", + start = 14, + finish = 15, + range = 20, + parent = "", + node = "", + field = { + type = "field", + start = 14, + finish = 15, + parent = "", + [1] = "c", + }, + value = { + type = "table", + start = 18, + finish = 20, + parent = "", + }, + }, + }, + }, + }, + }, +} +TEST '{{}, {}, {{}, {}}}' +{ + type = "table", + start = 0, + finish = 18, + [1] = { + type = "tableexp", + start = 1, + finish = 3, + tindex = 1, + parent = "", + value = { + type = "table", + start = 1, + finish = 3, + parent = "", + }, + }, + [2] = { + type = "tableexp", + start = 5, + finish = 7, + tindex = 2, + parent = "", + value = { + type = "table", + start = 5, + finish = 7, + parent = "", + }, + }, + [3] = { + type = "tableexp", + start = 9, + finish = 17, + tindex = 3, + parent = "", + value = { + type = "table", + start = 9, + finish = 17, + parent = "", + [1] = { + type = "tableexp", + start = 10, + finish = 12, + tindex = 1, + parent = "", + value = { + type = "table", + start = 10, + finish = 12, + parent = "", + }, + }, + [2] = { + type = "tableexp", + start = 14, + finish = 16, + tindex = 2, + parent = "", + value = { + type = "table", + start = 14, + finish = 16, + parent = "", + }, + }, + }, + }, +} +TEST '{1, 2, 3,}' +{ + type = "table", + start = 0, + finish = 10, + [1] = { + type = "tableexp", + start = 1, + finish = 2, + tindex = 1, + parent = "", + value = { + type = "integer", + start = 1, + finish = 2, + parent = "", + [1] = 1, + }, + }, + [2] = { + type = "tableexp", + start = 4, + finish = 5, + tindex = 2, + parent = "", + value = { + type = "integer", + start = 4, + finish = 5, + parent = "", + [1] = 2, + }, + }, + [3] = { + type = "tableexp", + start = 7, + finish = 8, + tindex = 3, + parent = "", + value = { + type = "integer", + start = 7, + finish = 8, + parent = "", + [1] = 3, + }, + }, +} + +CHECK 'notify' +{ + type = "getglobal", + start = 0, + finish = 6, + [1] = "notify", +} + +CHECK 'a ^ - b' +{ + type = "binary", + start = 0, + finish = 7, + op = { + type = "^", + start = 2, + finish = 3, + }, + [1] = { + type = "getglobal", + start = 0, + finish = 1, + parent = "", + [1] = "a", + }, + [2] = { + type = "unary", + start = 4, + finish = 7, + parent = "", + op = { + type = "-", + start = 4, + finish = 5, + }, + [1] = { + type = "getglobal", + start = 6, + finish = 7, + parent = "", + [1] = "b", + }, + }, +} + +CHECK [=[ +{ +[[]] +}]=] +{ + type = "table", + start = 0, + finish = 20001, + [1] = { + type = "tableexp", + start = 10000, + finish = 10004, + tindex = 1, + parent = "", + value = { + type = "string", + start = 10000, + finish = 10004, + parent = "", + [1] = "", + [2] = "[[", + }, + }, +} + +CHECK [[ +{ + [xxx] +} +]] +{ + type = "table", + start = 0, + finish = 20001, + [1] = { + type = "tableindex", + start = 10004, + finish = 10009, + parent = "", + node = "", + index = { + type = "getglobal", + start = 10005, + finish = 10008, + parent = "", + [1] = "xxx", + }, + }, +} diff --git a/test/ast/init.lua b/test/ast/init.lua index adbe84b..99bef14 100644 --- a/test/ast/init.lua +++ b/test/ast/init.lua @@ -18,3 +18,4 @@ require 'test.ast.string' require 'test.ast.number' require 'test.ast.comment' require 'test.ast.local' +require 'test.ast.exp' From 9f9f411464b44ef6f8323ae0ef16ce285d72630d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 13 Sep 2023 16:23:58 +0800 Subject: [PATCH 18/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/exp.lua | 38 +++++++++++++++++++++++++--- src/parser/ast/id.lua | 1 + src/parser/ast/var.lua | 54 +++++++++++++++++++++++++++++++++++++++ test/ast/exp.lua | 57 +++++++++++++++++++++++++----------------- 4 files changed, 124 insertions(+), 26 deletions(-) diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index 6ca069f..a7243ae 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -56,21 +56,53 @@ function Ast:parseExpList(atLeastOne) end ---@alias LuaParser.Node.Term +---| LuaParser.Node.TermHead +---| LuaParser.Node.TermChain + +---@alias LuaParser.Node.TermHead ---| LuaParser.Node.Nil ---| LuaParser.Node.Boolean ---| LuaParser.Node.Number ---| LuaParser.Node.String ---| LuaParser.Node.Var +---| LuaParser.Node.Paren + +---@alias LuaParser.Node.TermChain +---| LuaParser.Node.Field -- 解析表达式中的一项 ---@return LuaParser.Node.Term? function Ast:parseTerm() - -- TODO - local term = self:parseNil() + ---@type LuaParser.Node.TermHead? + local head = self:parseNil() or self:parseBoolean() or self:parseNumber() or self:parseString() or self:parseVar() - return term + if not head then + return nil + end + + ---@type LuaParser.Node.Term + local current = head + + while true do + self:skipSpace() + local token, tp, pos = self.lexer:peek() + if not token then + break + end + local field = self:parseField() + if field then + current.next = field + field.last = current + current = field + goto continue + end + break + ::continue:: + end + + return current end diff --git a/src/parser/ast/id.lua b/src/parser/ast/id.lua index 63badf4..ca0d92c 100644 --- a/src/parser/ast/id.lua +++ b/src/parser/ast/id.lua @@ -6,6 +6,7 @@ local Ast = class.declare 'LuaParser.Ast' ---@alias LuaParser.Node.ID ---| LuaParser.Node.Local ---| LuaParser.Node.Var +---| LuaParser.Node.FieldID ---@generic T: LuaParser.Node.ID ---@param nodeType `T` diff --git a/src/parser/ast/var.lua b/src/parser/ast/var.lua index 9df3ff1..5e0b81b 100644 --- a/src/parser/ast/var.lua +++ b/src/parser/ast/var.lua @@ -2,8 +2,25 @@ local class = require 'class' ---@class LuaParser.Node.Var: LuaParser.Node.Base ---@field loc? LuaParser.Node.Local +---@field next? LuaParser.Node.Field local Var = class.declare('LuaParser.Node.Var', 'LuaParser.Node.Base') +---@class LuaParser.Node.Field: LuaParser.Node.Base +---@field subtype 'field' | 'method' | 'index' +---@field key LuaParser.Node.FieldID +---@field next? LuaParser.Node.Field +---@field last? LuaParser.Node.Field | LuaParser.Node.TermHead +local Field = class.declare('LuaParser.Node.Field', 'LuaParser.Node.Base') + +---@class LuaParser.Node.FieldID: LuaParser.Node.Base +---@field parent LuaParser.Node.Field +local FieldID = class.declare('LuaParser.Node.FieldID', 'LuaParser.Node.Base') + +---@class LuaParser.Node.Paren: LuaParser.Node.Base +---@field exp LuaParser.Node.Exp +---@field next? LuaParser.Node.Field +local Paren = class.declare('LuaParser.Node.Paren', 'LuaParser.Node.Base') + ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' @@ -16,3 +33,40 @@ function Ast:parseVar() return var end + +---@return LuaParser.Node.Field? +function Ast:parseField() + local token, _, pos = self.lexer:peek() + if token == '.' + or token == ':' then + self.lexer:next() + self:skipSpace() + local key = self:parseID('LuaParser.Node.FieldID', true) + if key then + return class.new('LuaParser.Node.Field', { + ast = self, + start = pos, + finish = key.finish, + subtype = (token == '.') and 'field' or 'method', + key = key, + }) + end + return nil + end + if token == '[' then + self:skipSpace() + local key = self:parseExp(true) + if key then + self:skipSpace() + self:assertSymbol(']') + return class.new('LuaParser.Node.Field', { + ast = self, + start = pos, + finish = self:getLastPos(), + subtype = 'index', + key = key, + }) + end + end + return nil +end diff --git a/test/ast/exp.lua b/test/ast/exp.lua index 431b61a..c3de629 100644 --- a/test/ast/exp.lua +++ b/test/ast/exp.lua @@ -24,41 +24,52 @@ TEST 'a' TEST 'a.b' { - start = 0, + start = 1, finish = 3, - node = "", - dot = { - type = ".", - start = 1, - finish = 2, - }, - field = { - type = "field", + key = { start = 2, finish = 3, - parent = "", - [1] = "b", + id = 'b', }, + last = { + start = 0, + finish = 1, + id = 'a', + next = { + __class__ = 'LuaParser.Node.Field', + }, + } } TEST 'a.b.c' { - type = "getfield", - start = 0, + start = 3, finish = 5, - node = "", - dot = { - type = ".", - start = 3, - finish = 4, - }, - field = { - type = "field", + key = { start = 4, finish = 5, - parent = "", - [1] = "c", + id = 'c', }, + last = { + start = 1, + finish = 3, + key = { + start = 2, + finish = 3, + id = 'b', + }, + last = { + start = 0, + finish = 1, + id = 'a', + next = { + __class__ = 'LuaParser.Node.Field', + }, + }, + next = { + __class__ = 'LuaParser.Node.Field', + }, + } } TEST 'func()' { From e85ec1bca2d145034d86ff4bb2128e05b3580038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 13 Sep 2023 17:53:02 +0800 Subject: [PATCH 19/94] =?UTF-8?q?=E6=94=AF=E6=8C=81=20field=20=E5=92=8C=20?= =?UTF-8?q?call?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 14 ++++++++---- src/parser/ast/call.lua | 49 ++++++++++++++++++++++++++++++++++++++++ src/parser/ast/exp.lua | 19 +++++++--------- src/parser/ast/table.lua | 12 ++++++++++ src/parser/ast/var.lua | 19 ++++++++++++---- test/ast/exp.lua | 38 +++++++++++++++++++++++-------- 6 files changed, 122 insertions(+), 29 deletions(-) create mode 100644 src/parser/ast/call.lua create mode 100644 src/parser/ast/table.lua diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 912a8f2..a001e71 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -13,6 +13,8 @@ require 'parser.ast.state' require 'parser.ast.id' require 'parser.ast.local' require 'parser.ast.var' +require 'parser.ast.call' +require 'parser.ast.table' ---@class LuaParser.Ast ---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast @@ -86,19 +88,21 @@ end ---@private ---@param inState? boolean # 是否是语句 function M:skipSpace(inState) - self.lastRightCI = self.lexer.ci - 1 + self.lastRightCI = self.lexer.ci repeat until not self:skipNL() and not self:skipComment(inState) + self.lastSpaceCI = self.lexer.ci end -- 获取上个词的右侧位置(不包括换行符和注释) ---@return integer function M:getLastPos() - if not self.lastRightCI then - return 0 + local ci = self.lexer.ci + if ci == self.lastSpaceCI then + ci = self.lastRightCI end - local token = self.lexer.tokens[self.lastRightCI] - local pos = self.lexer.poses[self.lastRightCI] + local token = self.lexer.tokens[ci - 1] + local pos = self.lexer.poses[ci - 1] return pos + #token end diff --git a/src/parser/ast/call.lua b/src/parser/ast/call.lua new file mode 100644 index 0000000..8edfbec --- /dev/null +++ b/src/parser/ast/call.lua @@ -0,0 +1,49 @@ +local class = require 'class' + +---@class LuaParser.Node.Call: LuaParser.Node.Base +---@field node LuaParser.Node.Term +---@field args LuaParser.Node.Exp[] +local Call = class.declare('LuaParser.Node.Call', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@param last LuaParser.Node.Term +---@return LuaParser.Node.Call? +function Ast:parseCall(last) + local token, _, pos = self.lexer:peek() + if token == '(' then + self.lexer:next() + local exps = self:parseExpList() + self:assertSymbol ')' + local call = class.new('LuaParser.Node.Call', { + ast = self, + start = pos, + finish = self:getLastPos(), + node = last, + args = exps, + }) + last.parent = call + for i = 1, #exps do + exps[i].parent = call + end + return call + end + + local literalArg = self:parseString() + or self:parseTable() + if literalArg then + local call = class.new('LuaParser.Node.Call', { + ast = self, + start = pos, + finish = self:getLastPos(), + node = last, + args = { literalArg }, + }) + last.parent = call + literalArg.parent = call + return call + end + + return nil +end diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index a7243ae..2bf2a82 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -69,6 +69,7 @@ end ---@alias LuaParser.Node.TermChain ---| LuaParser.Node.Field +---| LuaParser.Node.Call -- 解析表达式中的一项 ---@return LuaParser.Node.Term? @@ -89,19 +90,15 @@ function Ast:parseTerm() while true do self:skipSpace() - local token, tp, pos = self.lexer:peek() - if not token then + + local chain = self:parseField(current) + or self:parseCall(current) + + if chain then + current = chain + else break end - local field = self:parseField() - if field then - current.next = field - field.last = current - current = field - goto continue - end - break - ::continue:: end return current diff --git a/src/parser/ast/table.lua b/src/parser/ast/table.lua new file mode 100644 index 0000000..5d9b7ef --- /dev/null +++ b/src/parser/ast/table.lua @@ -0,0 +1,12 @@ +local class = require 'class' + +---@class LuaParser.Ast.Table: LuaParser.Node.Base +local Table = class.declare('LuaParser.Ast.Table', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@return LuaParser.Ast.Table? +function Ast:parseTable() + +end diff --git a/src/parser/ast/var.lua b/src/parser/ast/var.lua index 5e0b81b..ef73967 100644 --- a/src/parser/ast/var.lua +++ b/src/parser/ast/var.lua @@ -9,7 +9,7 @@ local Var = class.declare('LuaParser.Node.Var', 'LuaParser.Node.Base') ---@field subtype 'field' | 'method' | 'index' ---@field key LuaParser.Node.FieldID ---@field next? LuaParser.Node.Field ----@field last? LuaParser.Node.Field | LuaParser.Node.TermHead +---@field last? LuaParser.Node.Term local Field = class.declare('LuaParser.Node.Field', 'LuaParser.Node.Base') ---@class LuaParser.Node.FieldID: LuaParser.Node.Base @@ -34,8 +34,9 @@ function Ast:parseVar() return var end +---@param last LuaParser.Node.Term ---@return LuaParser.Node.Field? -function Ast:parseField() +function Ast:parseField(last) local token, _, pos = self.lexer:peek() if token == '.' or token == ':' then @@ -43,13 +44,18 @@ function Ast:parseField() self:skipSpace() local key = self:parseID('LuaParser.Node.FieldID', true) if key then - return class.new('LuaParser.Node.Field', { + local field = class.new('LuaParser.Node.Field', { ast = self, start = pos, finish = key.finish, subtype = (token == '.') and 'field' or 'method', key = key, + last = last, }) + last.next = field + last.parent = field + key.parent = field + return field end return nil end @@ -59,13 +65,18 @@ function Ast:parseField() if key then self:skipSpace() self:assertSymbol(']') - return class.new('LuaParser.Node.Field', { + local field = class.new('LuaParser.Node.Field', { ast = self, start = pos, finish = self:getLastPos(), subtype = 'index', key = key, + last = last, }) + last.parent = field + last.next = field + key.parent = field + return field end end return nil diff --git a/test/ast/exp.lua b/test/ast/exp.lua index c3de629..b709aec 100644 --- a/test/ast/exp.lua +++ b/test/ast/exp.lua @@ -30,11 +30,13 @@ TEST 'a.b' start = 2, finish = 3, id = 'b', + parent = {}, }, last = { start = 0, finish = 1, id = 'a', + parent = {}, next = { __class__ = 'LuaParser.Node.Field', }, @@ -49,42 +51,60 @@ TEST 'a.b.c' start = 4, finish = 5, id = 'c', + parent = {}, }, last = { start = 1, finish = 3, + parent = {}, key = { start = 2, finish = 3, id = 'b', + parent = {}, + }, + next = { + __class__ = 'LuaParser.Node.Field', }, last = { start = 0, finish = 1, id = 'a', + parent = {}, next = { __class__ = 'LuaParser.Node.Field', }, }, - next = { - __class__ = 'LuaParser.Node.Field', - }, } } + TEST 'func()' { - type = "call", - start = 0, + start = 4, finish = 6, - node = "", + node = { + start = 0, + finish = 4, + id = 'func', + parent = { + __class__ = 'LuaParser.Node.Call', + }, + }, + args = {}, } + TEST 'a.b.c()' { - type = "call", - start = 0, + start = 5, finish = 7, - node = "", + node = { + start = 3, + finish = 5, + key = {}, + parent = {}, + } } + TEST '1 or 2' { type = "binary", From ac70a1a6d3a76062a7acea009531475063d6ed29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 14 Sep 2023 10:37:07 +0800 Subject: [PATCH 20/94] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/call.lua | 7 +++++-- src/parser/ast/var.lua | 11 ++++++++--- test/ast/exp.lua | 43 ++++++++++++++++++++++++----------------- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/src/parser/ast/call.lua b/src/parser/ast/call.lua index 8edfbec..3bab02f 100644 --- a/src/parser/ast/call.lua +++ b/src/parser/ast/call.lua @@ -2,6 +2,7 @@ local class = require 'class' ---@class LuaParser.Node.Call: LuaParser.Node.Base ---@field node LuaParser.Node.Term +---@field argPos integer ---@field args LuaParser.Node.Exp[] local Call = class.declare('LuaParser.Node.Call', 'LuaParser.Node.Base') @@ -18,10 +19,11 @@ function Ast:parseCall(last) self:assertSymbol ')' local call = class.new('LuaParser.Node.Call', { ast = self, - start = pos, + start = last.start, finish = self:getLastPos(), node = last, args = exps, + argPos = pos, }) last.parent = call for i = 1, #exps do @@ -35,10 +37,11 @@ function Ast:parseCall(last) if literalArg then local call = class.new('LuaParser.Node.Call', { ast = self, - start = pos, + start = last.start, finish = self:getLastPos(), node = last, args = { literalArg }, + argPos = literalArg.start, }) last.parent = call literalArg.parent = call diff --git a/src/parser/ast/var.lua b/src/parser/ast/var.lua index ef73967..48f2e62 100644 --- a/src/parser/ast/var.lua +++ b/src/parser/ast/var.lua @@ -8,6 +8,8 @@ local Var = class.declare('LuaParser.Node.Var', 'LuaParser.Node.Base') ---@class LuaParser.Node.Field: LuaParser.Node.Base ---@field subtype 'field' | 'method' | 'index' ---@field key LuaParser.Node.FieldID +---@field symbolPos integer +---@field symbolPos2? integer ---@field next? LuaParser.Node.Field ---@field last? LuaParser.Node.Term local Field = class.declare('LuaParser.Node.Field', 'LuaParser.Node.Base') @@ -46,11 +48,12 @@ function Ast:parseField(last) if key then local field = class.new('LuaParser.Node.Field', { ast = self, - start = pos, + start = last.start, finish = key.finish, subtype = (token == '.') and 'field' or 'method', key = key, last = last, + symbolPos = pos, }) last.next = field last.parent = field @@ -64,14 +67,16 @@ function Ast:parseField(last) local key = self:parseExp(true) if key then self:skipSpace() - self:assertSymbol(']') + local hasSymbol = self:assertSymbol(']') local field = class.new('LuaParser.Node.Field', { ast = self, - start = pos, + start = last.start, finish = self:getLastPos(), subtype = 'index', key = key, last = last, + symbolPos = pos, + symbolPos2 = hasSymbol and self:getLastPos() or nil, }) last.parent = field last.next = field diff --git a/test/ast/exp.lua b/test/ast/exp.lua index b709aec..dffe6b6 100644 --- a/test/ast/exp.lua +++ b/test/ast/exp.lua @@ -24,15 +24,17 @@ TEST 'a' TEST 'a.b' { - start = 1, - finish = 3, - key = { + start = 0, + finish = 3, + symbolPos = 1, + subtype = 'field', + key = { start = 2, finish = 3, id = 'b', parent = {}, }, - last = { + last = { start = 0, finish = 1, id = 'a', @@ -45,28 +47,31 @@ TEST 'a.b' TEST 'a.b.c' { - start = 3, - finish = 5, - key = { + start = 0, + finish = 5, + symbolPos = 3, + subtype = 'field', + key = { start = 4, finish = 5, id = 'c', parent = {}, }, - last = { - start = 1, - finish = 3, - parent = {}, - key = { + last = { + start = 0, + finish = 3, + symbolPos = 1, + parent = {}, + key = { start = 2, finish = 3, id = 'b', parent = {}, }, - next = { + next = { __class__ = 'LuaParser.Node.Field', }, - last = { + last = { start = 0, finish = 1, id = 'a', @@ -80,8 +85,9 @@ TEST 'a.b.c' TEST 'func()' { - start = 4, + start = 0, finish = 6, + argPos = 4, node = { start = 0, finish = 4, @@ -90,15 +96,16 @@ TEST 'func()' __class__ = 'LuaParser.Node.Call', }, }, - args = {}, + args = {}, } TEST 'a.b.c()' { - start = 5, + start = 0, finish = 7, + argPos = 5, node = { - start = 3, + start = 0, finish = 5, key = {}, parent = {}, From 1393f282a78d19d3120eae88a54d9a7bc1bceb47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 14 Sep 2023 18:49:29 +0800 Subject: [PATCH 21/94] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/call.lua | 5 +- src/parser/ast/error.lua | 5 +- src/parser/ast/exp.lua | 29 +++ src/parser/ast/local.lua | 5 +- src/parser/ast/table.lua | 5 +- src/parser/ast/var.lua | 20 +- src/parser/lexer.lua | 13 +- test/ast/exp.lua | 539 -------------------------------------- test/ast/init.lua | 1 + test/ast/table.lua | 551 +++++++++++++++++++++++++++++++++++++++ 10 files changed, 616 insertions(+), 557 deletions(-) create mode 100644 test/ast/table.lua diff --git a/src/parser/ast/call.lua b/src/parser/ast/call.lua index 3bab02f..97e0ad9 100644 --- a/src/parser/ast/call.lua +++ b/src/parser/ast/call.lua @@ -12,9 +12,8 @@ local Ast = class.declare 'LuaParser.Ast' ---@param last LuaParser.Node.Term ---@return LuaParser.Node.Call? function Ast:parseCall(last) - local token, _, pos = self.lexer:peek() - if token == '(' then - self.lexer:next() + local pos = self.lexer:consume '(' + if pos then local exps = self:parseExpList() self:assertSymbol ')' local call = class.new('LuaParser.Node.Call', { diff --git a/src/parser/ast/error.lua b/src/parser/ast/error.lua index 3de44cd..e2c50f5 100644 --- a/src/parser/ast/error.lua +++ b/src/parser/ast/error.lua @@ -35,8 +35,11 @@ end -- 断言下个符号,如果成功则消耗,否则报错 ---@param symbol string +---@return integer? pos function Ast:assertSymbol(symbol) - if not self.lexer:consume(symbol) then + local pos = self.lexer:consume(symbol) + if not pos then self:throwMissSymbol(self:getLastPos(), symbol) end + return pos end diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index 2bf2a82..7da639d 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -1,5 +1,10 @@ local class = require 'class' +---@class LuaParser.Node.Paren: LuaParser.Node.Base +---@field exp LuaParser.Node.Exp +---@field next? LuaParser.Node.Field +local Paren = class.declare('LuaParser.Node.Paren', 'LuaParser.Node.Base') + ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' @@ -66,6 +71,7 @@ end ---| LuaParser.Node.String ---| LuaParser.Node.Var ---| LuaParser.Node.Paren +---| LuaParser.Node.Varargs ---@alias LuaParser.Node.TermChain ---| LuaParser.Node.Field @@ -79,7 +85,9 @@ function Ast:parseTerm() or self:parseBoolean() or self:parseNumber() or self:parseString() + or self:parseVarargs() or self:parseVar() + or self:parseParen() if not head then return nil @@ -103,3 +111,24 @@ function Ast:parseTerm() return current end + +---@return LuaParser.Node.Paren? +function Ast:parseParen() + local pos = self.lexer:consume '(' + if not pos then + return nil + end + local exp = self:parseExp(true) + if not exp then + return nil + end + local paren = class.new('LuaParser.Node.Paren', { + ast = self, + start = pos, + exp = exp, + }) + exp.parent = paren + paren.finish = self:assertSymbol ')' or exp.finish + + return paren +end diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index 7944e97..9aebd0e 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -17,11 +17,10 @@ local Ast = class.declare 'LuaParser.Ast' ---@return LuaParser.Node.LocalDef? function Ast:parseLocal() - local token, _, pos = self.lexer:peek() - if token ~= 'local' then + local pos = self.lexer:consume 'local' + if not pos then return nil end - self.lexer:next() local localdef = class.new('LuaParser.Node.LocalDef', { ast = self, diff --git a/src/parser/ast/table.lua b/src/parser/ast/table.lua index 5d9b7ef..9d05379 100644 --- a/src/parser/ast/table.lua +++ b/src/parser/ast/table.lua @@ -8,5 +8,8 @@ local Ast = class.declare 'LuaParser.Ast' ---@return LuaParser.Ast.Table? function Ast:parseTable() - + local pos = self.lexer:consume '{' + if pos then + return nil + end end diff --git a/src/parser/ast/var.lua b/src/parser/ast/var.lua index 48f2e62..dd7643b 100644 --- a/src/parser/ast/var.lua +++ b/src/parser/ast/var.lua @@ -18,10 +18,8 @@ local Field = class.declare('LuaParser.Node.Field', 'LuaParser.Node.Base') ---@field parent LuaParser.Node.Field local FieldID = class.declare('LuaParser.Node.FieldID', 'LuaParser.Node.Base') ----@class LuaParser.Node.Paren: LuaParser.Node.Base ----@field exp LuaParser.Node.Exp ----@field next? LuaParser.Node.Field -local Paren = class.declare('LuaParser.Node.Paren', 'LuaParser.Node.Base') +---@class LuaParser.Node.Varargs: LuaParser.Node.Base +local Varargs = class.declare('LuaParser.Node.Varargs', 'LuaParser.Node.Base') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' @@ -36,6 +34,19 @@ function Ast:parseVar() return var end +---@return LuaParser.Node.Varargs? +function Ast:parseVarargs() + local pos = self.lexer:consume '...' + if pos then + return nil + end + return class.new('LuaParser.Node.Varargs', { + ast = self, + start = pos, + finish = pos + 3, + }) +end + ---@param last LuaParser.Node.Term ---@return LuaParser.Node.Field? function Ast:parseField(last) @@ -63,6 +74,7 @@ function Ast:parseField(last) return nil end if token == '[' then + self.lexer:next() self:skipSpace() local key = self:parseExp(true) if key then diff --git a/src/parser/lexer.lua b/src/parser/lexer.lua index cc17a85..5121db6 100644 --- a/src/parser/lexer.lua +++ b/src/parser/lexer.lua @@ -115,15 +115,16 @@ function M:next(count) return token, tp, pos end --- 消耗一个指定的词,返回是否成功 +-- 消耗一个指定的词,返回消耗掉词的位置 ---@param token string ----@return boolean +---@return integer? function M:consume(token) - if self.tokens[self.ci] == token then - self.ci = self.ci + 1 - return true + local ci = self.ci + if self.tokens[ci] == token then + self.ci = ci + 1 + return self.poses[ci] end - return false + return nil end -- 获取当前词的2侧光标位置 diff --git a/test/ast/exp.lua b/test/ast/exp.lua index dffe6b6..a56590e 100644 --- a/test/ast/exp.lua +++ b/test/ast/exp.lua @@ -1045,495 +1045,6 @@ TEST 'function (a, ...) end' }, locals = "", } -TEST '{}' -{ - type = "table", - start = 0, - finish = 2, -} -TEST '{...}' -{ - type = "table", - start = 0, - finish = 5, - [1] = { - type = "varargs", - start = 1, - finish = 4, - parent = "", - }, -} -TEST '{1, 2, 3}' -{ - type = "table", - start = 0, - finish = 9, - [1] = { - type = "tableexp", - start = 1, - finish = 2, - tindex = 1, - parent = "", - value = { - type = "integer", - start = 1, - finish = 2, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "tableexp", - start = 4, - finish = 5, - tindex = 2, - parent = "", - value = { - type = "integer", - start = 4, - finish = 5, - parent = "", - [1] = 2, - }, - }, - [3] = { - type = "tableexp", - start = 7, - finish = 8, - tindex = 3, - parent = "", - value = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 3, - }, - }, -} -TEST '{x = 1, y = 2}' -{ - type = "table", - start = 0, - finish = 14, - [1] = { - type = "tablefield", - start = 1, - finish = 2, - range = 6, - parent = "", - node = "", - field = { - type = "field", - start = 1, - finish = 2, - parent = "", - [1] = "x", - }, - value = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "tablefield", - start = 8, - finish = 9, - range = 13, - parent = "", - node = "", - field = { - type = "field", - start = 8, - finish = 9, - parent = "", - [1] = "y", - }, - value = { - type = "integer", - start = 12, - finish = 13, - parent = "", - [1] = 2, - }, - }, -} -TEST '{["x"] = 1, ["y"] = 2}' -{ - type = "table", - start = 0, - finish = 22, - [1] = { - type = "tableindex", - start = 1, - finish = 6, - range = 10, - parent = "", - node = "", - index = { - type = "string", - start = 2, - finish = 5, - parent = "", - [1] = "x", - [2] = "\"", - }, - value = { - type = "integer", - start = 9, - finish = 10, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "tableindex", - start = 12, - finish = 17, - range = 21, - parent = "", - node = "", - index = { - type = "string", - start = 13, - finish = 16, - parent = "", - [1] = "y", - [2] = "\"", - }, - value = { - type = "integer", - start = 20, - finish = 21, - parent = "", - [1] = 2, - }, - }, -} -TEST '{[x] = 1, [y] = 2}' -{ - type = "table", - start = 0, - finish = 18, - [1] = { - type = "tableindex", - start = 1, - finish = 4, - range = 8, - parent = "", - node = "", - index = { - type = "getglobal", - start = 2, - finish = 3, - parent = "", - [1] = "x", - }, - value = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "tableindex", - start = 10, - finish = 13, - range = 17, - parent = "", - node = "", - index = { - type = "getglobal", - start = 11, - finish = 12, - parent = "", - [1] = "y", - }, - value = { - type = "integer", - start = 16, - finish = 17, - parent = "", - [1] = 2, - }, - }, -} -TEST '{x = 1, y = 2, 3}' -{ - type = "table", - start = 0, - finish = 17, - [1] = { - type = "tablefield", - start = 1, - finish = 2, - range = 6, - parent = "", - node = "", - field = { - type = "field", - start = 1, - finish = 2, - parent = "", - [1] = "x", - }, - value = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "tablefield", - start = 8, - finish = 9, - range = 13, - parent = "", - node = "", - field = { - type = "field", - start = 8, - finish = 9, - parent = "", - [1] = "y", - }, - value = { - type = "integer", - start = 12, - finish = 13, - parent = "", - [1] = 2, - }, - }, - [3] = { - type = "tableexp", - start = 15, - finish = 16, - tindex = 1, - parent = "", - value = { - type = "integer", - start = 15, - finish = 16, - parent = "", - [1] = 3, - }, - }, -} -TEST '{{}}' -{ - type = "table", - start = 0, - finish = 4, - [1] = { - type = "tableexp", - start = 1, - finish = 3, - tindex = 1, - parent = "", - value = { - type = "table", - start = 1, - finish = 3, - parent = "", - }, - }, -} -TEST '{ a = { b = { c = {} } } }' -{ - type = "table", - start = 0, - finish = 26, - [1] = { - type = "tablefield", - start = 2, - finish = 3, - range = 24, - parent = "", - node = "", - field = { - type = "field", - start = 2, - finish = 3, - parent = "", - [1] = "a", - }, - value = { - type = "table", - start = 6, - finish = 24, - parent = "", - [1] = { - type = "tablefield", - start = 8, - finish = 9, - range = 22, - parent = "", - node = "", - field = { - type = "field", - start = 8, - finish = 9, - parent = "", - [1] = "b", - }, - value = { - type = "table", - start = 12, - finish = 22, - parent = "", - [1] = { - type = "tablefield", - start = 14, - finish = 15, - range = 20, - parent = "", - node = "", - field = { - type = "field", - start = 14, - finish = 15, - parent = "", - [1] = "c", - }, - value = { - type = "table", - start = 18, - finish = 20, - parent = "", - }, - }, - }, - }, - }, - }, -} -TEST '{{}, {}, {{}, {}}}' -{ - type = "table", - start = 0, - finish = 18, - [1] = { - type = "tableexp", - start = 1, - finish = 3, - tindex = 1, - parent = "", - value = { - type = "table", - start = 1, - finish = 3, - parent = "", - }, - }, - [2] = { - type = "tableexp", - start = 5, - finish = 7, - tindex = 2, - parent = "", - value = { - type = "table", - start = 5, - finish = 7, - parent = "", - }, - }, - [3] = { - type = "tableexp", - start = 9, - finish = 17, - tindex = 3, - parent = "", - value = { - type = "table", - start = 9, - finish = 17, - parent = "", - [1] = { - type = "tableexp", - start = 10, - finish = 12, - tindex = 1, - parent = "", - value = { - type = "table", - start = 10, - finish = 12, - parent = "", - }, - }, - [2] = { - type = "tableexp", - start = 14, - finish = 16, - tindex = 2, - parent = "", - value = { - type = "table", - start = 14, - finish = 16, - parent = "", - }, - }, - }, - }, -} -TEST '{1, 2, 3,}' -{ - type = "table", - start = 0, - finish = 10, - [1] = { - type = "tableexp", - start = 1, - finish = 2, - tindex = 1, - parent = "", - value = { - type = "integer", - start = 1, - finish = 2, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "tableexp", - start = 4, - finish = 5, - tindex = 2, - parent = "", - value = { - type = "integer", - start = 4, - finish = 5, - parent = "", - [1] = 2, - }, - }, - [3] = { - type = "tableexp", - start = 7, - finish = 8, - tindex = 3, - parent = "", - value = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 3, - }, - }, -} CHECK 'notify' { @@ -1579,53 +1090,3 @@ CHECK 'a ^ - b' }, }, } - -CHECK [=[ -{ -[[]] -}]=] -{ - type = "table", - start = 0, - finish = 20001, - [1] = { - type = "tableexp", - start = 10000, - finish = 10004, - tindex = 1, - parent = "", - value = { - type = "string", - start = 10000, - finish = 10004, - parent = "", - [1] = "", - [2] = "[[", - }, - }, -} - -CHECK [[ -{ - [xxx] -} -]] -{ - type = "table", - start = 0, - finish = 20001, - [1] = { - type = "tableindex", - start = 10004, - finish = 10009, - parent = "", - node = "", - index = { - type = "getglobal", - start = 10005, - finish = 10008, - parent = "", - [1] = "xxx", - }, - }, -} diff --git a/test/ast/init.lua b/test/ast/init.lua index 99bef14..bfdfc58 100644 --- a/test/ast/init.lua +++ b/test/ast/init.lua @@ -18,4 +18,5 @@ require 'test.ast.string' require 'test.ast.number' require 'test.ast.comment' require 'test.ast.local' +require 'test.ast.table' require 'test.ast.exp' diff --git a/test/ast/table.lua b/test/ast/table.lua new file mode 100644 index 0000000..1c2c9c4 --- /dev/null +++ b/test/ast/table.lua @@ -0,0 +1,551 @@ +local class = require 'class' + +local function TEST(code) + return function (expect) + local ast = class.new 'LuaParser.Ast' (code) + local node = ast:parseTable() + assert(node) + Match(node, expect) + end +end + +TEST '{}' +{ + type = "table", + start = 0, + finish = 2, +} + +TEST '{...}' +{ + type = "table", + start = 0, + finish = 5, + [1] = { + type = "varargs", + start = 1, + finish = 4, + parent = "", + }, +} +TEST '{1, 2, 3}' +{ + type = "table", + start = 0, + finish = 9, + [1] = { + type = "tableexp", + start = 1, + finish = 2, + tindex = 1, + parent = "", + value = { + type = "integer", + start = 1, + finish = 2, + parent = "", + [1] = 1, + }, + }, + [2] = { + type = "tableexp", + start = 4, + finish = 5, + tindex = 2, + parent = "", + value = { + type = "integer", + start = 4, + finish = 5, + parent = "", + [1] = 2, + }, + }, + [3] = { + type = "tableexp", + start = 7, + finish = 8, + tindex = 3, + parent = "", + value = { + type = "integer", + start = 7, + finish = 8, + parent = "", + [1] = 3, + }, + }, +} +TEST '{x = 1, y = 2}' +{ + type = "table", + start = 0, + finish = 14, + [1] = { + type = "tablefield", + start = 1, + finish = 2, + range = 6, + parent = "", + node = "", + field = { + type = "field", + start = 1, + finish = 2, + parent = "", + [1] = "x", + }, + value = { + type = "integer", + start = 5, + finish = 6, + parent = "", + [1] = 1, + }, + }, + [2] = { + type = "tablefield", + start = 8, + finish = 9, + range = 13, + parent = "", + node = "", + field = { + type = "field", + start = 8, + finish = 9, + parent = "", + [1] = "y", + }, + value = { + type = "integer", + start = 12, + finish = 13, + parent = "", + [1] = 2, + }, + }, +} +TEST '{["x"] = 1, ["y"] = 2}' +{ + type = "table", + start = 0, + finish = 22, + [1] = { + type = "tableindex", + start = 1, + finish = 6, + range = 10, + parent = "", + node = "", + index = { + type = "string", + start = 2, + finish = 5, + parent = "", + [1] = "x", + [2] = "\"", + }, + value = { + type = "integer", + start = 9, + finish = 10, + parent = "", + [1] = 1, + }, + }, + [2] = { + type = "tableindex", + start = 12, + finish = 17, + range = 21, + parent = "", + node = "", + index = { + type = "string", + start = 13, + finish = 16, + parent = "", + [1] = "y", + [2] = "\"", + }, + value = { + type = "integer", + start = 20, + finish = 21, + parent = "", + [1] = 2, + }, + }, +} +TEST '{[x] = 1, [y] = 2}' +{ + type = "table", + start = 0, + finish = 18, + [1] = { + type = "tableindex", + start = 1, + finish = 4, + range = 8, + parent = "", + node = "", + index = { + type = "getglobal", + start = 2, + finish = 3, + parent = "", + [1] = "x", + }, + value = { + type = "integer", + start = 7, + finish = 8, + parent = "", + [1] = 1, + }, + }, + [2] = { + type = "tableindex", + start = 10, + finish = 13, + range = 17, + parent = "", + node = "", + index = { + type = "getglobal", + start = 11, + finish = 12, + parent = "", + [1] = "y", + }, + value = { + type = "integer", + start = 16, + finish = 17, + parent = "", + [1] = 2, + }, + }, +} +TEST '{x = 1, y = 2, 3}' +{ + type = "table", + start = 0, + finish = 17, + [1] = { + type = "tablefield", + start = 1, + finish = 2, + range = 6, + parent = "", + node = "", + field = { + type = "field", + start = 1, + finish = 2, + parent = "", + [1] = "x", + }, + value = { + type = "integer", + start = 5, + finish = 6, + parent = "", + [1] = 1, + }, + }, + [2] = { + type = "tablefield", + start = 8, + finish = 9, + range = 13, + parent = "", + node = "", + field = { + type = "field", + start = 8, + finish = 9, + parent = "", + [1] = "y", + }, + value = { + type = "integer", + start = 12, + finish = 13, + parent = "", + [1] = 2, + }, + }, + [3] = { + type = "tableexp", + start = 15, + finish = 16, + tindex = 1, + parent = "", + value = { + type = "integer", + start = 15, + finish = 16, + parent = "", + [1] = 3, + }, + }, +} +TEST '{{}}' +{ + type = "table", + start = 0, + finish = 4, + [1] = { + type = "tableexp", + start = 1, + finish = 3, + tindex = 1, + parent = "", + value = { + type = "table", + start = 1, + finish = 3, + parent = "", + }, + }, +} +TEST '{ a = { b = { c = {} } } }' +{ + type = "table", + start = 0, + finish = 26, + [1] = { + type = "tablefield", + start = 2, + finish = 3, + range = 24, + parent = "", + node = "", + field = { + type = "field", + start = 2, + finish = 3, + parent = "", + [1] = "a", + }, + value = { + type = "table", + start = 6, + finish = 24, + parent = "", + [1] = { + type = "tablefield", + start = 8, + finish = 9, + range = 22, + parent = "", + node = "", + field = { + type = "field", + start = 8, + finish = 9, + parent = "", + [1] = "b", + }, + value = { + type = "table", + start = 12, + finish = 22, + parent = "", + [1] = { + type = "tablefield", + start = 14, + finish = 15, + range = 20, + parent = "", + node = "", + field = { + type = "field", + start = 14, + finish = 15, + parent = "", + [1] = "c", + }, + value = { + type = "table", + start = 18, + finish = 20, + parent = "", + }, + }, + }, + }, + }, + }, +} +TEST '{{}, {}, {{}, {}}}' +{ + type = "table", + start = 0, + finish = 18, + [1] = { + type = "tableexp", + start = 1, + finish = 3, + tindex = 1, + parent = "", + value = { + type = "table", + start = 1, + finish = 3, + parent = "", + }, + }, + [2] = { + type = "tableexp", + start = 5, + finish = 7, + tindex = 2, + parent = "", + value = { + type = "table", + start = 5, + finish = 7, + parent = "", + }, + }, + [3] = { + type = "tableexp", + start = 9, + finish = 17, + tindex = 3, + parent = "", + value = { + type = "table", + start = 9, + finish = 17, + parent = "", + [1] = { + type = "tableexp", + start = 10, + finish = 12, + tindex = 1, + parent = "", + value = { + type = "table", + start = 10, + finish = 12, + parent = "", + }, + }, + [2] = { + type = "tableexp", + start = 14, + finish = 16, + tindex = 2, + parent = "", + value = { + type = "table", + start = 14, + finish = 16, + parent = "", + }, + }, + }, + }, +} +TEST '{1, 2, 3,}' +{ + type = "table", + start = 0, + finish = 10, + [1] = { + type = "tableexp", + start = 1, + finish = 2, + tindex = 1, + parent = "", + value = { + type = "integer", + start = 1, + finish = 2, + parent = "", + [1] = 1, + }, + }, + [2] = { + type = "tableexp", + start = 4, + finish = 5, + tindex = 2, + parent = "", + value = { + type = "integer", + start = 4, + finish = 5, + parent = "", + [1] = 2, + }, + }, + [3] = { + type = "tableexp", + start = 7, + finish = 8, + tindex = 3, + parent = "", + value = { + type = "integer", + start = 7, + finish = 8, + parent = "", + [1] = 3, + }, + }, +} + +CHECK [=[ +{ +[[]] +}]=] +{ + type = "table", + start = 0, + finish = 20001, + [1] = { + type = "tableexp", + start = 10000, + finish = 10004, + tindex = 1, + parent = "", + value = { + type = "string", + start = 10000, + finish = 10004, + parent = "", + [1] = "", + [2] = "[[", + }, + }, +} + +CHECK [[ +{ + [xxx] +} +]] +{ + type = "table", + start = 0, + finish = 20001, + [1] = { + type = "tableindex", + start = 10004, + finish = 10009, + parent = "", + node = "", + index = { + type = "getglobal", + start = 10005, + finish = 10008, + parent = "", + [1] = "xxx", + }, + }, +} From f4ed23810699790cb1ebc540d7db035bff7f2fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Fri, 15 Sep 2023 12:02:03 +0800 Subject: [PATCH 22/94] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .luarc.json | 3 -- src/parser/ast/ast.lua | 3 ++ src/parser/ast/base.lua | 26 ++++++++++++- src/parser/ast/block.lua | 8 ++++ src/parser/ast/chunk.lua | 7 ---- src/parser/ast/field.lua | 78 +++++++++++++++++++++++++++++++++++++ src/parser/ast/function.lua | 4 ++ src/parser/ast/table.lua | 17 ++++++-- src/parser/ast/var.lua | 66 +------------------------------ test/ast/table.lua | 2 - 10 files changed, 132 insertions(+), 82 deletions(-) create mode 100644 src/parser/ast/block.lua delete mode 100644 src/parser/ast/chunk.lua create mode 100644 src/parser/ast/field.lua create mode 100644 src/parser/ast/function.lua diff --git a/.luarc.json b/.luarc.json index cf9a458..22b5475 100644 --- a/.luarc.json +++ b/.luarc.json @@ -1,9 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", "diagnostics": { - "disable": [ - "duplicate-doc-alias" - ], "globals": [ "root", "log", diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index a001e71..6cbcfb5 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -15,6 +15,9 @@ require 'parser.ast.local' require 'parser.ast.var' require 'parser.ast.call' require 'parser.ast.table' +require 'parser.ast.block' +require 'parser.ast.function' +require 'parser.ast.field' ---@class LuaParser.Ast ---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast diff --git a/src/parser/ast/base.lua b/src/parser/ast/base.lua index fb145cb..948c222 100644 --- a/src/parser/ast/base.lua +++ b/src/parser/ast/base.lua @@ -11,7 +11,9 @@ local class = require 'class' ---@field finishRow integer # 结束行号 ---@field finishCol integer # 结束列号 ---@field code string # 对应的代码 ----@field parent LuaParser.Node.Base +---@field parent unknown +---@field parentBlock LuaParser.Node.Block +---@field parentFunction LuaParser.Node.Function local Base = class.declare 'LuaParser.Node.Base' local rowcolMulti = 10000 @@ -80,3 +82,25 @@ end Base.__getter.parent = function (self) error('未设置父节点:' .. class.type(self)) end + +---@param self LuaParser.Node.Base +---@return LuaParser.Node.Block +---@return true +Base.__getter.parentBlock = function (self) + local parent = self.parent + if class.type(parent) == 'LuaParser.Node.Block' then + return parent, true + end + return parent.parentBlock, true +end + +---@param self LuaParser.Node.Base +---@return LuaParser.Node.Function +---@return true +Base.__getter.parentFunction = function (self) + local parent = self.parentFunction + if class.type(parent) == 'LuaParser.Node.Function' then + return parent, true + end + return parent.parentFunction, true +end diff --git a/src/parser/ast/block.lua b/src/parser/ast/block.lua new file mode 100644 index 0000000..b7fdcd8 --- /dev/null +++ b/src/parser/ast/block.lua @@ -0,0 +1,8 @@ +local class = require 'class' + +---@class LuaParser.Node.Block: LuaParser.Node.Base +---@field childs LuaParser.Node.State[] +local Block = class.declare('LuaParser.Node.Block', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' diff --git a/src/parser/ast/chunk.lua b/src/parser/ast/chunk.lua deleted file mode 100644 index 1d97306..0000000 --- a/src/parser/ast/chunk.lua +++ /dev/null @@ -1,7 +0,0 @@ -local class = require 'class' - ----@class LuaParser.Node.Chunk: LuaParser.Node.Base -local Chunk = class.declare('LuaParser.Node.Chunk', 'LuaParser.Node.Base') - ----@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' diff --git a/src/parser/ast/field.lua b/src/parser/ast/field.lua new file mode 100644 index 0000000..fcda9a3 --- /dev/null +++ b/src/parser/ast/field.lua @@ -0,0 +1,78 @@ +local class = require 'class' + +---@class LuaParser.Node.Field: LuaParser.Node.Base +---@field subtype 'field' | 'method' | 'index' +---@field key LuaParser.Node.FieldID +---@field symbolPos integer +---@field symbolPos2? integer +---@field next? LuaParser.Node.Field +---@field last? LuaParser.Node.Term +local Field = class.declare('LuaParser.Node.Field', 'LuaParser.Node.Base') + +---@class LuaParser.Node.FieldID: LuaParser.Node.Base +---@field parent LuaParser.Node.Field +local FieldID = class.declare('LuaParser.Node.FieldID', 'LuaParser.Node.Base') + +---@class LuaParser.Node.TableField: LuaParser.Node.Base +---@field subtype 'field' | 'index' | 'exp' +---@field key LuaParser.Node.Exp +---@field value LuaParser.Node.Exp +---@field symbolPos? integer +---@field symbolPos2? integer +---@field parent LuaParser.Node.Table +local TableField = class.declare('LuaParser.Node.TableField', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@param last LuaParser.Node.Term +---@return LuaParser.Node.Field? +function Ast:parseField(last) + local token, _, pos = self.lexer:peek() + if token == '.' + or token == ':' then + self.lexer:next() + self:skipSpace() + local key = self:parseID('LuaParser.Node.FieldID', true) + if key then + local field = class.new('LuaParser.Node.Field', { + ast = self, + start = last.start, + finish = key.finish, + subtype = (token == '.') and 'field' or 'method', + key = key, + last = last, + symbolPos = pos, + }) + last.next = field + last.parent = field + key.parent = field + return field + end + return nil + end + if token == '[' then + self.lexer:next() + self:skipSpace() + local key = self:parseExp(true) + if key then + self:skipSpace() + local hasSymbol = self:assertSymbol(']') + local field = class.new('LuaParser.Node.Field', { + ast = self, + start = last.start, + finish = self:getLastPos(), + subtype = 'index', + key = key, + last = last, + symbolPos = pos, + symbolPos2 = hasSymbol and self:getLastPos() or nil, + }) + last.parent = field + last.next = field + key.parent = field + return field + end + end + return nil +end diff --git a/src/parser/ast/function.lua b/src/parser/ast/function.lua new file mode 100644 index 0000000..989b923 --- /dev/null +++ b/src/parser/ast/function.lua @@ -0,0 +1,4 @@ +local class = require 'class' + +---@class LuaParser.Node.Function: LuaParser.Node.Base +local Function = class.declare('LuaParser.Node.Function', 'LuaParser.Node.Base') diff --git a/src/parser/ast/table.lua b/src/parser/ast/table.lua index 9d05379..79d6355 100644 --- a/src/parser/ast/table.lua +++ b/src/parser/ast/table.lua @@ -1,15 +1,24 @@ local class = require 'class' ----@class LuaParser.Ast.Table: LuaParser.Node.Base -local Table = class.declare('LuaParser.Ast.Table', 'LuaParser.Node.Base') +---@class LuaParser.Node.Table: LuaParser.Node.Base +---@field fields LuaParser.Node.Field[] +local Table = class.declare('LuaParser.Node.Table', 'LuaParser.Node.Base') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' ----@return LuaParser.Ast.Table? +---@return LuaParser.Node.Table? function Ast:parseTable() local pos = self.lexer:consume '{' - if pos then + if not pos then return nil end + + self:assertSymbol '}' + local table = class.new('LuaParser.Node.Table', { + ast = self, + start = pos, + finish = self:getLastPos(), + }) + return table end diff --git a/src/parser/ast/var.lua b/src/parser/ast/var.lua index dd7643b..fbc3812 100644 --- a/src/parser/ast/var.lua +++ b/src/parser/ast/var.lua @@ -1,23 +1,11 @@ local class = require 'class' ---@class LuaParser.Node.Var: LuaParser.Node.Base +---@field subtype 'global' | 'local' ---@field loc? LuaParser.Node.Local ---@field next? LuaParser.Node.Field local Var = class.declare('LuaParser.Node.Var', 'LuaParser.Node.Base') ----@class LuaParser.Node.Field: LuaParser.Node.Base ----@field subtype 'field' | 'method' | 'index' ----@field key LuaParser.Node.FieldID ----@field symbolPos integer ----@field symbolPos2? integer ----@field next? LuaParser.Node.Field ----@field last? LuaParser.Node.Term -local Field = class.declare('LuaParser.Node.Field', 'LuaParser.Node.Base') - ----@class LuaParser.Node.FieldID: LuaParser.Node.Base ----@field parent LuaParser.Node.Field -local FieldID = class.declare('LuaParser.Node.FieldID', 'LuaParser.Node.Base') - ---@class LuaParser.Node.Varargs: LuaParser.Node.Base local Varargs = class.declare('LuaParser.Node.Varargs', 'LuaParser.Node.Base') @@ -46,55 +34,3 @@ function Ast:parseVarargs() finish = pos + 3, }) end - ----@param last LuaParser.Node.Term ----@return LuaParser.Node.Field? -function Ast:parseField(last) - local token, _, pos = self.lexer:peek() - if token == '.' - or token == ':' then - self.lexer:next() - self:skipSpace() - local key = self:parseID('LuaParser.Node.FieldID', true) - if key then - local field = class.new('LuaParser.Node.Field', { - ast = self, - start = last.start, - finish = key.finish, - subtype = (token == '.') and 'field' or 'method', - key = key, - last = last, - symbolPos = pos, - }) - last.next = field - last.parent = field - key.parent = field - return field - end - return nil - end - if token == '[' then - self.lexer:next() - self:skipSpace() - local key = self:parseExp(true) - if key then - self:skipSpace() - local hasSymbol = self:assertSymbol(']') - local field = class.new('LuaParser.Node.Field', { - ast = self, - start = last.start, - finish = self:getLastPos(), - subtype = 'index', - key = key, - last = last, - symbolPos = pos, - symbolPos2 = hasSymbol and self:getLastPos() or nil, - }) - last.parent = field - last.next = field - key.parent = field - return field - end - end - return nil -end diff --git a/test/ast/table.lua b/test/ast/table.lua index 1c2c9c4..9ebe5db 100644 --- a/test/ast/table.lua +++ b/test/ast/table.lua @@ -11,14 +11,12 @@ end TEST '{}' { - type = "table", start = 0, finish = 2, } TEST '{...}' { - type = "table", start = 0, finish = 5, [1] = { From 8e5c7e0c75406e45174e9a5a4b85cda24195b256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Fri, 15 Sep 2023 18:51:25 +0800 Subject: [PATCH 23/94] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 37 ++++++++++++++++++++---------- src/parser/ast/base.lua | 41 +++++++++++++++++++++++++++++++++ src/parser/ast/boolean.lua | 9 ++------ src/parser/ast/comment.lua | 12 +++++----- src/parser/ast/error.lua | 6 +++++ src/parser/ast/exp.lua | 32 ++++++++++++++++++++++++++ src/parser/ast/field.lua | 5 ++-- src/parser/ast/nil.lua | 7 +++--- src/parser/ast/number.lua | 42 ++++++++++++++++++++++++++++------ src/parser/ast/string.lua | 6 ++--- src/parser/ast/table.lua | 47 ++++++++++++++++++++++++++++++++++++++ src/parser/ast/var.lua | 1 + src/parser/lexer.lua | 35 +++++++++++----------------- test/ast/table.lua | 22 +++++++++++++----- 14 files changed, 234 insertions(+), 68 deletions(-) diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 6cbcfb5..c7753be 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -66,20 +66,15 @@ end ---@private ---@return boolean # 是否成功跳过换行符 function M:skipNL() - local _, tp = self.lexer:peek() - if tp == 'NL' then - self.lexer:next() - return true - end - return false + return self.lexer:consumeType 'NL' ~= nil end -- 跳过注释 ---@private ----@param inState? boolean # 是否是语句 +---@param inExp? boolean # 在表达式中 ---@return boolean # 是否成功跳过注释 -function M:skipComment(inState) - local comment = self:parseComment(inState) +function M:skipComment(inExp) + local comment = self:parseComment(inExp) if comment then self.comments[#self.comments+1] = comment return true @@ -87,13 +82,31 @@ function M:skipComment(inState) return false end +-- 跳过未知符号 +---@private +---@return boolean +function M:skipUnknown() + local token, pos = self.lexer:consumeType 'Unknown' + if not token then + return false + end + + ---@cast pos -? + self:throw('UNKNOWN_SYMBOL', pos, pos + #token, { + symbol = token, + }) + + return true +end + -- 跳过空白符 ---@private ----@param inState? boolean # 是否是语句 -function M:skipSpace(inState) +---@param inExp? boolean # 在表达式中 +function M:skipSpace(inExp) self.lastRightCI = self.lexer.ci repeat until not self:skipNL() - and not self:skipComment(inState) + and not self:skipComment(inExp) + and not self:skipUnknown() self.lastSpaceCI = self.lexer.ci end diff --git a/src/parser/ast/base.lua b/src/parser/ast/base.lua index 948c222..47215c5 100644 --- a/src/parser/ast/base.lua +++ b/src/parser/ast/base.lua @@ -14,6 +14,15 @@ local class = require 'class' ---@field parent unknown ---@field parentBlock LuaParser.Node.Block ---@field parentFunction LuaParser.Node.Function +---@field asNumber? number +---@field asString? string +---@field asBoolean? boolean +---@field asInteger? integer +---@field toNumber? number +---@field toString? string +---@field toInteger? integer +---@field isTruly? boolean +---@field dummy? boolean local Base = class.declare 'LuaParser.Node.Base' local rowcolMulti = 10000 @@ -104,3 +113,35 @@ Base.__getter.parentFunction = function (self) end return parent.parentFunction, true end + +---@class LuaParser.Node.Literal: LuaParser.Node.Base +---@field value? nil|boolean|number|string|integer +local Literal = class.declare('LuaParser.Node.Literal', 'LuaParser.Node.Base') + +---@param self LuaParser.Node.Literal +---@return string +---@return true +function Literal.__getter.toString(self) + return tostring(self.value), true +end + +---@param self LuaParser.Node.Literal +---@return number? +---@return true +function Literal.__getter.toNumber(self) + return tonumber(self.value), true +end + +---@param self LuaParser.Node.Literal +---@return integer? +---@return true +function Literal.__getter.tointeger(self) + return math.tointeger(self.value), true +end + +---@param self LuaParser.Node.Literal +---@return boolean +---@return true +function Literal.__getter.isTruly(self) + return self.value and true or false, true +end diff --git a/src/parser/ast/boolean.lua b/src/parser/ast/boolean.lua index 5e26c64..1a8c580 100644 --- a/src/parser/ast/boolean.lua +++ b/src/parser/ast/boolean.lua @@ -1,13 +1,8 @@ local class = require 'class' ----@class LuaParser.Node.Boolean: LuaParser.Node.Base +---@class LuaParser.Node.Boolean: LuaParser.Node.Literal ---@field value boolean ----@field view string -local Boolean = class.declare('LuaParser.Node.Boolean', 'LuaParser.Node.Base') - -Boolean.__getter.view = function (self) - return tostring(self.value), true -end +local Boolean = class.declare('LuaParser.Node.Boolean', 'LuaParser.Node.Literal') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' diff --git a/src/parser/ast/comment.lua b/src/parser/ast/comment.lua index 6464062..8ff0a77 100644 --- a/src/parser/ast/comment.lua +++ b/src/parser/ast/comment.lua @@ -25,23 +25,23 @@ end ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' ----@param inState? boolean +---@param inExp? boolean ---@return LuaParser.Node.Comment? -function Ast:parseComment(inState) +function Ast:parseComment(inExp) return self:parseLongComment() - or self:parseShortComment(inState) + or self:parseShortComment(inExp) end ----@param inState? boolean +---@param inExp? boolean ---@return LuaParser.Node.Comment? -function Ast:parseShortComment(inState) +function Ast:parseShortComment(inExp) local token, _, pos = self.lexer:peek() if not token then return nil end ---@cast pos -? if token == '--' - or (token == '//' and (inState or self.nssymbolMap['//'])) then + or (token == '//' and (not inExp or self.nssymbolMap['//'])) then if token == '//' and not self.nssymbolMap['//'] then self:throw('ERR_COMMENT_PREFIX', pos, pos + 2) end diff --git a/src/parser/ast/error.lua b/src/parser/ast/error.lua index e2c50f5..dfdafe2 100644 --- a/src/parser/ast/error.lua +++ b/src/parser/ast/error.lua @@ -33,6 +33,12 @@ function Ast:throwMissSymbol(start, symbol) }) end +-- 添加错误“缺少表达式” +---@param start integer +function Ast:throwMissExp(start) + self:throw('MISS_EXP', start, start) +end + -- 断言下个符号,如果成功则消耗,否则报错 ---@param symbol string ---@return integer? pos diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index 7da639d..930ce15 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -5,6 +5,38 @@ local class = require 'class' ---@field next? LuaParser.Node.Field local Paren = class.declare('LuaParser.Node.Paren', 'LuaParser.Node.Base') +function Paren.__getter.asNumber(self) + return self.exp.asNumber, true +end + +function Paren.__getter.asString(self) + return self.exp.asString, true +end + +function Paren.__getter.asBoolean(self) + return self.exp.asBoolean, true +end + +function Paren.__getter.asInteger(self) + return self.exp.asInteger, true +end + +function Paren.__getter.toNumber(self) + return self.exp.toNumber, true +end + +function Paren.__getter.toString(self) + return self.exp.toString, true +end + +function Paren.__getter.toInteger(self) + return self.exp.toInteger, true +end + +function Paren.__getter.isTruly(self) + return self.exp.isTruly, true +end + ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' diff --git a/src/parser/ast/field.lua b/src/parser/ast/field.lua index fcda9a3..97123ae 100644 --- a/src/parser/ast/field.lua +++ b/src/parser/ast/field.lua @@ -2,11 +2,12 @@ local class = require 'class' ---@class LuaParser.Node.Field: LuaParser.Node.Base ---@field subtype 'field' | 'method' | 'index' ----@field key LuaParser.Node.FieldID +---@field key LuaParser.Node.FieldID | LuaParser.Node.Exp ---@field symbolPos integer ---@field symbolPos2? integer ---@field next? LuaParser.Node.Field ---@field last? LuaParser.Node.Term +---@field value? LuaParser.Node.Exp local Field = class.declare('LuaParser.Node.Field', 'LuaParser.Node.Base') ---@class LuaParser.Node.FieldID: LuaParser.Node.Base @@ -15,7 +16,7 @@ local FieldID = class.declare('LuaParser.Node.FieldID', 'LuaParser.Node.Base') ---@class LuaParser.Node.TableField: LuaParser.Node.Base ---@field subtype 'field' | 'index' | 'exp' ----@field key LuaParser.Node.Exp +---@field key? LuaParser.Node.Exp ---@field value LuaParser.Node.Exp ---@field symbolPos? integer ---@field symbolPos2? integer diff --git a/src/parser/ast/nil.lua b/src/parser/ast/nil.lua index c2ed532..45e8cd9 100644 --- a/src/parser/ast/nil.lua +++ b/src/parser/ast/nil.lua @@ -1,9 +1,10 @@ local class = require 'class' ----@class LuaParser.Node.Nil: LuaParser.Node.Base -local Nil = class.declare('LuaParser.Node.Nil', 'LuaParser.Node.Base') +---@class LuaParser.Node.Nil: LuaParser.Node.Literal +local Nil = class.declare('LuaParser.Node.Nil', 'LuaParser.Node.Literal') -Nil.view = 'nil' +Nil.toString = 'nil' +Nil.isTruly = false ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' diff --git a/src/parser/ast/number.lua b/src/parser/ast/number.lua index f0fb05b..ae84ede 100644 --- a/src/parser/ast/number.lua +++ b/src/parser/ast/number.lua @@ -2,12 +2,12 @@ local class = require 'class' ---@alias LuaParser.Node.Number LuaParser.Node.Float | LuaParser.Node.Integer ----@class LuaParser.Node.Float: LuaParser.Node.Base +---@class LuaParser.Node.Float: LuaParser.Node.Literal ---@field value number ---@field valuei? number # 虚数 ---@field numBase 2 | 10 | 16 ---@field view string -local Float = class.declare('LuaParser.Node.Float', 'LuaParser.Node.Base') +local Float = class.declare('LuaParser.Node.Float', 'LuaParser.Node.Literal') Float.value = 0.0 @@ -28,7 +28,7 @@ end ---@param self LuaParser.Node.Float ---@return string ---@return true -Float.__getter.view = function (self) +Float.__getter.toString = function (self) local num = self.valuei or self.value local view = ('%.10f'):format(num):gsub('0+$', '') if view:sub(-1) == '.' then @@ -40,13 +40,27 @@ Float.__getter.view = function (self) return view, true end ----@class LuaParser.Node.Integer: LuaParser.Node.Base +---@param self LuaParser.Node.Float +---@return number +---@return true +Float.__getter.asNumber = function (self) + return self.value, true +end + +---@param self LuaParser.Node.Float +---@return integer? +---@return true +Float.__getter.asInteger = function (self) + return math.tointeger(self), true +end + +---@class LuaParser.Node.Integer: LuaParser.Node.Literal ---@field value integer ---@field valuei? number # 虚数 ---@field numBase 2 | 10 | 16 ---@field intTail? 'LL' | 'ULL' ---@field view string -local Integer = class.declare('LuaParser.Node.Integer', 'LuaParser.Node.Base') +local Integer = class.declare('LuaParser.Node.Integer', 'LuaParser.Node.Literal') Integer.value = 0 @@ -67,7 +81,7 @@ end ---@param self LuaParser.Node.Integer ---@return string ---@return true -Integer.__getter.view = function (self) +Integer.__getter.toString = function (self) local view = tostring(self.valuei or self.value) if self.intTail then view = view .. self.intTail @@ -78,6 +92,20 @@ Integer.__getter.view = function (self) return view, true end +---@param self LuaParser.Node.Integer +---@return number +---@return true +Integer.__getter.asNumber = function (self) + return self.value, true +end + +---@param self LuaParser.Node.Integer +---@return integer? +---@return true +Integer.__getter.asInteger = function (self) + return self.value, true +end + ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' @@ -98,7 +126,7 @@ function Ast:parseNumber() end local start = self.lexer:range() - local neg = self.lexer:skipToken '-' + local neg = self.lexer:consume '-' local node = self:parseNumber16() or self:parseNumber2() diff --git a/src/parser/ast/string.lua b/src/parser/ast/string.lua index baaaf1f..9e9d6ec 100644 --- a/src/parser/ast/string.lua +++ b/src/parser/ast/string.lua @@ -2,13 +2,13 @@ local class = require 'class' ---@alias LuaParser.EscMode 'normal' | 'unicode' | 'err' | 'byte' ----@class LuaParser.Node.String: LuaParser.Node.Base +---@class LuaParser.Node.String: LuaParser.Node.Literal ---@field value string ---@field view string ---@field quo string ---@field escs? table ---@field missQuo? true -local String = class.declare('LuaParser.Node.String', 'LuaParser.Node.Base') +local String = class.declare('LuaParser.Node.String', 'LuaParser.Node.Literal') local escMap = { ['a'] = '\a', @@ -158,7 +158,7 @@ function Ast:parseShortString() if escChar == 'z' then pushEsc('normal', offset - 2, offset) self.lexer:fastForward(offset) - repeat until not self.lexer:skipType 'NL' + repeat until not self.lexer:consumeType 'NL' local _, _, afterPos = self.lexer:peek() curOffset = afterPos and (afterPos + 1) or (#self.code + 1) goto continue diff --git a/src/parser/ast/table.lua b/src/parser/ast/table.lua index 79d6355..8501c2f 100644 --- a/src/parser/ast/table.lua +++ b/src/parser/ast/table.lua @@ -14,6 +14,10 @@ function Ast:parseTable() return nil end + self:skipSpace() + self:parseTableFields() + self:skipSpace() + self:assertSymbol '}' local table = class.new('LuaParser.Node.Table', { ast = self, @@ -22,3 +26,46 @@ function Ast:parseTable() }) return table end + +---@return LuaParser.Node.TableField[] +function Ast:parseTableFields() + local fields = {} + local wantSep = false + while true do + local token = self.lexer:peek() + if not token or token == '}' then + break + end + if token == ',' then + if not wantSep then + self:throwMissExp(self:getLastPos()) + end + wantSep = false + else + if wantSep then + self:throwMissSymbol(self:getLastPos(), ',') + end + wantSep = true + local field = self:parseTableField() + if field then + fields[#fields+1] = field + else + self:throwMissExp(self:getLastPos()) + end + end + self:skipSpace() + end + return fields +end + +---@return LuaParser.Node.TableField? +function Ast:parseTableField() + local exp = self:parseExp() + if exp then + return class.new('LuaParser.Node.TableField', { + ast = self, + subtype = 'exp', + value = exp, + }) + end +end diff --git a/src/parser/ast/var.lua b/src/parser/ast/var.lua index fbc3812..ee00191 100644 --- a/src/parser/ast/var.lua +++ b/src/parser/ast/var.lua @@ -4,6 +4,7 @@ local class = require 'class' ---@field subtype 'global' | 'local' ---@field loc? LuaParser.Node.Local ---@field next? LuaParser.Node.Field +---@field value? LuaParser.Node.Exp local Var = class.declare('LuaParser.Node.Var', 'LuaParser.Node.Base') ---@class LuaParser.Node.Varargs: LuaParser.Node.Base diff --git a/src/parser/lexer.lua b/src/parser/lexer.lua index 5121db6..de166f4 100644 --- a/src/parser/lexer.lua +++ b/src/parser/lexer.lua @@ -127,6 +127,19 @@ function M:consume(token) return nil end +-- 消耗一个指定的类型,返回消耗掉词和位置 +---@param tp LuaParser.Lexer.Type +---@return string? +---@return integer? +function M:consumeType(tp) + local ci = self.ci + if self.types[ci] == tp then + self.ci = ci + 1 + return self.tokens[ci], self.poses[ci] + end + return nil, nil +end + -- 获取当前词的2侧光标位置 ---@param offset? integer # 偏移量,默认为0 ---@return integer? @@ -141,28 +154,6 @@ function M:range(offset) return pos, pos + #token end --- 跳过一个指定的词,返回是否成功 ----@param token string ----@return boolean -function M:skipToken(token) - if self.tokens[self.ci] == token then - self.ci = self.ci + 1 - return true - end - return false -end - --- 跳过一个指定的类型,返回是否成功 ----@param tp LuaParser.Lexer.Type ----@return boolean -function M:skipType(tp) - if self.types[self.ci] == tp then - self.ci = self.ci + 1 - return true - end - return false -end - -- 快进到某个光标位置 ---@param pos integer function M:fastForward(pos) diff --git a/test/ast/table.lua b/test/ast/table.lua index 9ebe5db..2757d5e 100644 --- a/test/ast/table.lua +++ b/test/ast/table.lua @@ -19,12 +19,22 @@ TEST '{...}' { start = 0, finish = 5, - [1] = { - type = "varargs", - start = 1, - finish = 4, - parent = "", - }, + fields = { + [1] = { + subtype = 'exp', + start = 1, + finish = 4, + key = { + dummy = true, + asInteger = 1, + }, + value = { + type = 'varargs', + start = 1, + finish = 4, + }, + } + } } TEST '{1, 2, 3}' { From c088c5d582324e2fe7d8131de28076b0b2d388f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Sat, 16 Sep 2023 23:29:13 +0800 Subject: [PATCH 24/94] =?UTF-8?q?=E8=A1=A8=E8=A7=A3=E6=9E=90=E6=95=B0?= =?UTF-8?q?=E7=BB=84=E8=A1=A8=E8=BE=BE=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/table.lua | 24 ++++++++++++++- src/parser/ast/var.lua | 2 +- test/ast/comment.lua | 2 +- test/ast/number.lua | 34 ++++++++++----------- test/ast/table.lua | 64 ++++++++++++++++++---------------------- 5 files changed, 70 insertions(+), 56 deletions(-) diff --git a/src/parser/ast/table.lua b/src/parser/ast/table.lua index 8501c2f..d7e1850 100644 --- a/src/parser/ast/table.lua +++ b/src/parser/ast/table.lua @@ -15,15 +15,34 @@ function Ast:parseTable() end self:skipSpace() - self:parseTableFields() + local fields = self:parseTableFields() self:skipSpace() self:assertSymbol '}' + local table = class.new('LuaParser.Node.Table', { ast = self, start = pos, finish = self:getLastPos(), + fields = fields, }) + + local expi = 0 + for _, field in ipairs(fields) do + field.parent = table + if field.subtype == 'exp' then + expi = expi + 1 + field.key = class.new('LuaParser.Node.Integer', { + ast = self, + dummy = true, + value = expi, + start = field.start, + finish = field.start, + parent = field, + }) + end + end + return table end @@ -37,6 +56,7 @@ function Ast:parseTableFields() break end if token == ',' then + self.lexer:next() if not wantSep then self:throwMissExp(self:getLastPos()) end @@ -66,6 +86,8 @@ function Ast:parseTableField() ast = self, subtype = 'exp', value = exp, + start = exp.start, + finish = exp.finish, }) end end diff --git a/src/parser/ast/var.lua b/src/parser/ast/var.lua index ee00191..4ac4f2c 100644 --- a/src/parser/ast/var.lua +++ b/src/parser/ast/var.lua @@ -26,7 +26,7 @@ end ---@return LuaParser.Node.Varargs? function Ast:parseVarargs() local pos = self.lexer:consume '...' - if pos then + if not pos then return nil end return class.new('LuaParser.Node.Varargs', { diff --git a/test/ast/comment.lua b/test/ast/comment.lua index 3ed02fa..431b7c6 100644 --- a/test/ast/comment.lua +++ b/test/ast/comment.lua @@ -3,7 +3,7 @@ local class = require 'class' local function TEST(code) return function (expect) local ast = class.new 'LuaParser.Ast' (code) - local node = ast:parseComment(true) + local node = ast:parseComment() assert(node) Match(node, expect) end diff --git a/test/ast/number.lua b/test/ast/number.lua index 55521a0..4d829fe 100644 --- a/test/ast/number.lua +++ b/test/ast/number.lua @@ -15,7 +15,7 @@ TEST '345' right = 3, value = 345, numBase = 10, - view = '345', + toString= '345', } TEST '345.0' { @@ -23,7 +23,7 @@ TEST '345.0' right = 5, value = 0x1.59p+8, numBase = 10, - view = '345.0', + toString= '345.0', } TEST '0xff' { @@ -31,7 +31,7 @@ TEST '0xff' right = 4, value = 255, numBase = 16, - view = '255', + toString= '255', } TEST '314.16e-2' { @@ -39,7 +39,7 @@ TEST '314.16e-2' right = 9, value = 3.1416, numBase = 10, - view = '3.1416', + toString= '3.1416', } TEST '0.31416E1' { @@ -47,7 +47,7 @@ TEST '0.31416E1' right = 9, value = 0x1.921ff2e48e8a7p+1, numBase = 10, - view = '3.1416', + toString= '3.1416', } TEST '.31416E1' { @@ -55,7 +55,7 @@ TEST '.31416E1' right = 8, value = 0x1.921ff2e48e8a7p+1, numBase = 10, - view = '3.1416', + toString= '3.1416', } TEST '34e1' { @@ -63,7 +63,7 @@ TEST '34e1' right = 4, value = 0x1.54p+8, numBase = 10, - view = '340.0', + toString= '340.0', } TEST '0x0.1E' { @@ -71,7 +71,7 @@ TEST '0x0.1E' right = 6, value = 0x1.ep-4, numBase = 16, - view = '0.1171875', + toString= '0.1171875', } TEST '0xA23p-4' { @@ -79,7 +79,7 @@ TEST '0xA23p-4' right = 8, value = 0x1.446p+7, numBase = 16, - view = '162.1875' + toString= '162.1875' } TEST '0X1.921FB54442D18P+1' { @@ -87,7 +87,7 @@ TEST '0X1.921FB54442D18P+1' right = 20, value = 0x1.921fb54442d18p+1, numBase = 16, - view = '3.1415926536' + toString= '3.1415926536' } TEST '-345' { @@ -95,7 +95,7 @@ TEST '-345' right = 4, value = -345, numBase = 10, - view = '-345', + toString= '-345', } TEST '0b110110' { @@ -103,7 +103,7 @@ TEST '0b110110' right = 8, value = 54, numBase = 2, - view = '54', + toString= '54', } TEST '123ll' { @@ -111,7 +111,7 @@ TEST '123ll' right = 5, value = 123, numBase = 10, - view = '123LL', + toString= '123LL', } TEST '123ull' { @@ -119,7 +119,7 @@ TEST '123ull' right = 6, value = 123, numBase = 10, - view = '123ULL', + toString= '123ULL', } TEST '123llu' { @@ -127,7 +127,7 @@ TEST '123llu' right = 6, value = 123, numBase = 10, - view = '123ULL', + toString= '123ULL', } TEST '123i' { @@ -136,7 +136,7 @@ TEST '123i' value = 0, valuei = 123, numBase = 10, - view = '0+123i', + toString= '0+123i', } TEST '123.45i' { @@ -145,5 +145,5 @@ TEST '123.45i' value = 0, valuei = 123.45, numBase = 10, - view = '0+123.45i', + toString= '0+123.45i', } diff --git a/test/ast/table.lua b/test/ast/table.lua index 2757d5e..d07f22e 100644 --- a/test/ast/table.lua +++ b/test/ast/table.lua @@ -26,10 +26,9 @@ TEST '{...}' finish = 4, key = { dummy = true, - asInteger = 1, + value = 1, }, value = { - type = 'varargs', start = 1, finish = 4, }, @@ -38,52 +37,45 @@ TEST '{...}' } TEST '{1, 2, 3}' { - type = "table", start = 0, finish = 9, - [1] = { - type = "tableexp", - start = 1, - finish = 2, - tindex = 1, - parent = "", - value = { - type = "integer", + fields = { + [1] = { start = 1, finish = 2, - parent = "", - [1] = 1, + key = { + dummy = true, + value = 1, + }, + value = { + value = 1, + }, }, - }, - [2] = { - type = "tableexp", - start = 4, - finish = 5, - tindex = 2, - parent = "", - value = { - type = "integer", + [2] = { start = 4, finish = 5, - parent = "", - [1] = 2, + key = { + dummy = true, + value = 2, + }, + value = { + value = 2, + }, }, - }, - [3] = { - type = "tableexp", - start = 7, - finish = 8, - tindex = 3, - parent = "", - value = { - type = "integer", + [3] = { start = 7, finish = 8, - parent = "", - [1] = 3, + key = { + dummy = true, + value = 3, + }, + value = { + value = 3, + }, }, - }, + } } + TEST '{x = 1, y = 2}' { type = "table", From eb36ed866f391a400aac3c840156d2f1b0c07469 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Mon, 18 Sep 2023 15:28:49 +0800 Subject: [PATCH 25/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/error.lua | 4 +- src/parser/ast/exp.lua | 2 + src/parser/ast/field.lua | 101 ++++++- src/parser/ast/id.lua | 1 + src/parser/ast/local.lua | 1 + src/parser/ast/table.lua | 20 +- src/parser/ast/var.lua | 1 + test/ast/table.lua | 608 +++++++++++++-------------------------- 8 files changed, 308 insertions(+), 430 deletions(-) diff --git a/src/parser/ast/error.lua b/src/parser/ast/error.lua index dfdafe2..338c9a2 100644 --- a/src/parser/ast/error.lua +++ b/src/parser/ast/error.lua @@ -12,14 +12,14 @@ local Ast = class.declare 'LuaParser.Ast' -- 添加错误信息 ---@param errorCode string ---@param start integer ----@param finish integer +---@param finish integer? ---@param extra table? function Ast:throw(errorCode, start, finish, extra) self.errors[#self.errors+1] = class.new('LuaParser.Node.Error', { code = errorCode, ast = self, start = start, - finish = finish, + finish = finish or start, extra = extra, }) end diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index 930ce15..0bea09a 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -104,6 +104,7 @@ end ---| LuaParser.Node.Var ---| LuaParser.Node.Paren ---| LuaParser.Node.Varargs +---| LuaParser.Node.Table ---@alias LuaParser.Node.TermChain ---| LuaParser.Node.Field @@ -120,6 +121,7 @@ function Ast:parseTerm() or self:parseVarargs() or self:parseVar() or self:parseParen() + or self:parseTable() if not head then return nil diff --git a/src/parser/ast/field.lua b/src/parser/ast/field.lua index 97123ae..ef14f56 100644 --- a/src/parser/ast/field.lua +++ b/src/parser/ast/field.lua @@ -11,18 +11,24 @@ local class = require 'class' local Field = class.declare('LuaParser.Node.Field', 'LuaParser.Node.Base') ---@class LuaParser.Node.FieldID: LuaParser.Node.Base +---@field id string ---@field parent LuaParser.Node.Field local FieldID = class.declare('LuaParser.Node.FieldID', 'LuaParser.Node.Base') ---@class LuaParser.Node.TableField: LuaParser.Node.Base ---@field subtype 'field' | 'index' | 'exp' ---@field key? LuaParser.Node.Exp ----@field value LuaParser.Node.Exp +---@field value? LuaParser.Node.Exp ---@field symbolPos? integer ---@field symbolPos2? integer ---@field parent LuaParser.Node.Table local TableField = class.declare('LuaParser.Node.TableField', 'LuaParser.Node.Base') +---@class LuaParser.Node.TableFieldID: LuaParser.Node.Base +---@field id string +---@field parent LuaParser.Node.TableField +local TableFieldID = class.declare('LuaParser.Node.TableFieldID', 'LuaParser.Node.Base') + ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' @@ -77,3 +83,96 @@ function Ast:parseField(last) end return nil end + +---@return LuaParser.Node.TableField? +function Ast:parseTableField() + return self:parseTableFieldAsField() + or self:parseTableFieldAsIndex() + or self:parseTableFieldAsExp() +end + +---@return LuaParser.Node.TableField? +function Ast:parseTableFieldAsField() + local savePoint = self.lexer:savePoint() + local key = self:parseID('LuaParser.Node.TableFieldID') + if not key then + return nil + end + self:skipSpace() + if not self.lexer:consume '=' then + savePoint() + return nil + end + self:skipSpace() + local value = self:parseExp(true) + local tfield = class.new('LuaParser.Node.TableField', { + ast = self, + subtype = 'field', + key = key, + value = value, + start = key.start, + finish = self:getLastPos(), + }) + key.parent = tfield + if value then + value.parent = tfield + end + return tfield +end + +---@return LuaParser.Node.TableField? +function Ast:parseTableFieldAsIndex() + local token, _, pos = self.lexer:peek() + if token ~= '[' then + return nil + end + local nextChar = self.code:sub(pos + 2, pos + 2) + if nextChar == '[' + or nextChar == '=' then + -- 长字符串? + return nil + end + self.lexer:next() + self:skipSpace() + local key = self:parseExp(true) + self:skipSpace() + local pos2 = self:assertSymbol ']' + self:skipSpace() + self:assertSymbol '=' + self:skipSpace() + local value = self:parseExp(true) + local tfield = class.new('LuaParser.Node.TableField', { + ast = self, + subtype = 'index', + key = key, + value = value, + start = pos, + finish = self:getLastPos(), + symbolPos = pos, + symbolPos2 = pos2, + }) + if key then + key.parent = tfield + end + if value then + value.parent = tfield + end + return tfield +end + +---@return LuaParser.Node.TableField? +function Ast:parseTableFieldAsExp() + local exp = self:parseExp() + if not exp then + return nil + end + local tfield = class.new('LuaParser.Node.TableField', { + ast = self, + subtype = 'exp', + value = exp, + start = exp.start, + finish = exp.finish, + }) + exp.parent = tfield + return tfield +end diff --git a/src/parser/ast/id.lua b/src/parser/ast/id.lua index ca0d92c..f8a0783 100644 --- a/src/parser/ast/id.lua +++ b/src/parser/ast/id.lua @@ -7,6 +7,7 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.Local ---| LuaParser.Node.Var ---| LuaParser.Node.FieldID +---| LuaParser.Node.TableFieldID ---@generic T: LuaParser.Node.ID ---@param nodeType `T` diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index 9aebd0e..d06ac5c 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -6,6 +6,7 @@ local class = require 'class' local LocalDef = class.declare('LuaParser.Node.LocalDef', 'LuaParser.Node.Base') ---@class LuaParser.Node.Local: LuaParser.Node.Base +---@field id string ---@field parent LuaParser.Node.LocalDef ---@field index integer ---@field value? LuaParser.Node.Exp diff --git a/src/parser/ast/table.lua b/src/parser/ast/table.lua index d7e1850..1ca6548 100644 --- a/src/parser/ast/table.lua +++ b/src/parser/ast/table.lua @@ -55,7 +55,8 @@ function Ast:parseTableFields() if not token or token == '}' then break end - if token == ',' then + if token == ',' + or token == ';' then self.lexer:next() if not wantSep then self:throwMissExp(self:getLastPos()) @@ -63,7 +64,7 @@ function Ast:parseTableFields() wantSep = false else if wantSep then - self:throwMissSymbol(self:getLastPos(), ',') + self:throw('MISS_SEP_IN_TABLE', self:getLastPos()) end wantSep = true local field = self:parseTableField() @@ -71,23 +72,10 @@ function Ast:parseTableFields() fields[#fields+1] = field else self:throwMissExp(self:getLastPos()) + break end end self:skipSpace() end return fields end - ----@return LuaParser.Node.TableField? -function Ast:parseTableField() - local exp = self:parseExp() - if exp then - return class.new('LuaParser.Node.TableField', { - ast = self, - subtype = 'exp', - value = exp, - start = exp.start, - finish = exp.finish, - }) - end -end diff --git a/src/parser/ast/var.lua b/src/parser/ast/var.lua index 4ac4f2c..ec55723 100644 --- a/src/parser/ast/var.lua +++ b/src/parser/ast/var.lua @@ -2,6 +2,7 @@ local class = require 'class' ---@class LuaParser.Node.Var: LuaParser.Node.Base ---@field subtype 'global' | 'local' +---@field id string ---@field loc? LuaParser.Node.Local ---@field next? LuaParser.Node.Field ---@field value? LuaParser.Node.Exp diff --git a/test/ast/table.lua b/test/ast/table.lua index d07f22e..3fed4ac 100644 --- a/test/ast/table.lua +++ b/test/ast/table.lua @@ -41,6 +41,7 @@ TEST '{1, 2, 3}' finish = 9, fields = { [1] = { + subtype = 'exp', start = 1, finish = 2, key = { @@ -52,6 +53,7 @@ TEST '{1, 2, 3}' }, }, [2] = { + subtype = 'exp', start = 4, finish = 5, key = { @@ -63,6 +65,7 @@ TEST '{1, 2, 3}' }, }, [3] = { + subtype = 'exp', start = 7, finish = 8, key = { @@ -78,474 +81,257 @@ TEST '{1, 2, 3}' TEST '{x = 1, y = 2}' { - type = "table", start = 0, finish = 14, - [1] = { - type = "tablefield", - start = 1, - finish = 2, - range = 6, - parent = "", - node = "", - field = { - type = "field", - start = 1, - finish = 2, - parent = "", - [1] = "x", - }, - value = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "tablefield", - start = 8, - finish = 9, - range = 13, - parent = "", - node = "", - field = { - type = "field", - start = 8, - finish = 9, - parent = "", - [1] = "y", + fields = { + [1] = { + subtype = 'field', + start = 1, + finish = 6, + key = { + id = 'x', + }, + value = { + value = 1, + }, }, - value = { - type = "integer", - start = 12, - finish = 13, - parent = "", - [1] = 2, + [2] = { + subtype = 'field', + start = 8, + finish = 13, + key = { + id = 'y', + }, + value = { + value = 2, + }, }, - }, + } } + TEST '{["x"] = 1, ["y"] = 2}' { - type = "table", start = 0, finish = 22, - [1] = { - type = "tableindex", - start = 1, - finish = 6, - range = 10, - parent = "", - node = "", - index = { - type = "string", - start = 2, - finish = 5, - parent = "", - [1] = "x", - [2] = "\"", - }, - value = { - type = "integer", - start = 9, - finish = 10, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "tableindex", - start = 12, - finish = 17, - range = 21, - parent = "", - node = "", - index = { - type = "string", - start = 13, - finish = 16, - parent = "", - [1] = "y", - [2] = "\"", + fields = { + [1] = { + subtype = 'index', + symbolPos = 1, + symbolPos2 = 5, + key = { + __class__ = 'LuaParser.Node.String', + value = 'x', + }, + value = { + value = 1, + } }, - value = { - type = "integer", - start = 20, - finish = 21, - parent = "", - [1] = 2, + [2] = { + subtype = 'index', + symbolPos = 12, + symbolPos2 = 16, + key = { + __class__ = 'LuaParser.Node.String', + value = 'y', + }, + value = { + value = 2, + } }, - }, + } } + TEST '{[x] = 1, [y] = 2}' { - type = "table", start = 0, finish = 18, - [1] = { - type = "tableindex", - start = 1, - finish = 4, - range = 8, - parent = "", - node = "", - index = { - type = "getglobal", - start = 2, - finish = 3, - parent = "", - [1] = "x", - }, - value = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "tableindex", - start = 10, - finish = 13, - range = 17, - parent = "", - node = "", - index = { - type = "getglobal", - start = 11, - finish = 12, - parent = "", - [1] = "y", + fields = { + [1] = { + subtype = 'index', + key = { + __class__ = 'LuaParser.Node.Var', + id = 'x', + } }, - value = { - type = "integer", - start = 16, - finish = 17, - parent = "", - [1] = 2, + [2] = { + subtype = 'index', + key = { + __class__ = 'LuaParser.Node.Var', + id = 'y', + } }, - }, + } } + TEST '{x = 1, y = 2, 3}' { - type = "table", start = 0, finish = 17, - [1] = { - type = "tablefield", - start = 1, - finish = 2, - range = 6, - parent = "", - node = "", - field = { - type = "field", - start = 1, - finish = 2, - parent = "", - [1] = "x", - }, - value = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "tablefield", - start = 8, - finish = 9, - range = 13, - parent = "", - node = "", - field = { - type = "field", - start = 8, - finish = 9, - parent = "", - [1] = "y", + fields = { + [1] = { + subtype = 'field' }, - value = { - type = "integer", - start = 12, - finish = 13, - parent = "", - [1] = 2, + [2] = { + subtype = 'field' }, - }, - [3] = { - type = "tableexp", - start = 15, - finish = 16, - tindex = 1, - parent = "", - value = { - type = "integer", - start = 15, - finish = 16, - parent = "", - [1] = 3, + [3] = { + subtype = 'exp', + key = { + dummy = true, + value = 1, + } }, - }, + } } + TEST '{{}}' { - type = "table", start = 0, finish = 4, - [1] = { - type = "tableexp", - start = 1, - finish = 3, - tindex = 1, - parent = "", - value = { - type = "table", - start = 1, - finish = 3, - parent = "", - }, - }, + fields = { + [1] = { + subtype = 'exp', + key = { + dummy = true, + value = 1, + }, + value = { + __class__ = 'LuaParser.Node.Table', + start = 1, + finish = 3, + } + } + } } + TEST '{ a = { b = { c = {} } } }' { - type = "table", - start = 0, - finish = 26, - [1] = { - type = "tablefield", - start = 2, - finish = 3, - range = 24, - parent = "", - node = "", - field = { - type = "field", - start = 2, - finish = 3, - parent = "", - [1] = "a", - }, - value = { - type = "table", - start = 6, - finish = 24, - parent = "", - [1] = { - type = "tablefield", - start = 8, - finish = 9, - range = 22, - parent = "", - node = "", - field = { - type = "field", - start = 8, - finish = 9, - parent = "", - [1] = "b", - }, - value = { - type = "table", - start = 12, - finish = 22, - parent = "", - [1] = { - type = "tablefield", - start = 14, - finish = 15, - range = 20, - parent = "", - node = "", - field = { - type = "field", - start = 14, - finish = 15, - parent = "", - [1] = "c", - }, - value = { - type = "table", - start = 18, - finish = 20, - parent = "", - }, - }, - }, + fields = { + [1] = { + subtype = 'field', + key = { + id = 'a', }, - }, - }, + value = { + fields = { + [1] = { + subtype = 'field', + key = { + id = 'b', + }, + value = { + fields = { + [1] = { + subtype = 'field', + key = { + id = 'c', + }, + value = { + } + } + } + } + } + } + } + } + } } + TEST '{{}, {}, {{}, {}}}' { - type = "table", - start = 0, - finish = 18, - [1] = { - type = "tableexp", - start = 1, - finish = 3, - tindex = 1, - parent = "", - value = { - type = "table", - start = 1, - finish = 3, - parent = "", - }, - }, - [2] = { - type = "tableexp", - start = 5, - finish = 7, - tindex = 2, - parent = "", - value = { - type = "table", - start = 5, - finish = 7, - parent = "", + fields = { + [1] = { + subtype = 'exp', }, - }, - [3] = { - type = "tableexp", - start = 9, - finish = 17, - tindex = 3, - parent = "", - value = { - type = "table", - start = 9, - finish = 17, - parent = "", - [1] = { - type = "tableexp", - start = 10, - finish = 12, - tindex = 1, - parent = "", - value = { - type = "table", - start = 10, - finish = 12, - parent = "", - }, - }, - [2] = { - type = "tableexp", - start = 14, - finish = 16, - tindex = 2, - parent = "", - value = { - type = "table", - start = 14, - finish = 16, - parent = "", - }, - }, + [2] = { + subtype = 'exp', }, - }, + [3] = { + subtype = 'exp', + value = { + fields = { + [1] = { + subtype = 'exp', + }, + [2] = { + subtype = 'exp', + }, + } + } + } + } } + TEST '{1, 2, 3,}' { - type = "table", - start = 0, - finish = 10, - [1] = { - type = "tableexp", - start = 1, - finish = 2, - tindex = 1, - parent = "", - value = { - type = "integer", - start = 1, - finish = 2, - parent = "", - [1] = 1, + fields = { + [1] = { + subtype = 'exp', + key = { + dummy = true, + value = 1, + }, + value = { + value = 1, + } }, - }, - [2] = { - type = "tableexp", - start = 4, - finish = 5, - tindex = 2, - parent = "", - value = { - type = "integer", - start = 4, - finish = 5, - parent = "", - [1] = 2, + [2] = { + subtype = 'exp', + key = { + dummy = true, + value = 2, + }, + value = { + value = 2, + } }, - }, - [3] = { - type = "tableexp", - start = 7, - finish = 8, - tindex = 3, - parent = "", - value = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 3, + [3] = { + subtype = 'exp', + key = { + dummy = true, + value = 3, + }, + value = { + value = 3, + } }, - }, + } } -CHECK [=[ +TEST [=[ { [[]] }]=] { - type = "table", - start = 0, - finish = 20001, - [1] = { - type = "tableexp", - start = 10000, - finish = 10004, - tindex = 1, - parent = "", - value = { - type = "string", - start = 10000, - finish = 10004, - parent = "", - [1] = "", - [2] = "[[", - }, - }, + fields = { + [1] = { + subtype = 'exp', + key = { + dummy = true, + value = 1, + }, + value = { + __class__ = 'LuaParser.Node.String', + value = '', + } + } + } } -CHECK [[ +TEST [[ { [xxx] } ]] { - type = "table", - start = 0, - finish = 20001, - [1] = { - type = "tableindex", - start = 10004, - finish = 10009, - parent = "", - node = "", - index = { - type = "getglobal", - start = 10005, - finish = 10008, - parent = "", - [1] = "xxx", - }, - }, + fields = { + [1] = { + subtype = 'index', + key = { + __class__ = 'LuaParser.Node.Var', + id = 'xxx', + }, + } + } } From 65be7580aab995f2476220c032eeafce6572f2ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Mon, 18 Sep 2023 18:30:20 +0800 Subject: [PATCH 26/94] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 2 + src/parser/ast/binary.lua | 111 ++++++++++++++++++++++++++++++++++++++ src/parser/ast/exp.lua | 44 ++++++++++++--- src/parser/ast/unary.lua | 76 ++++++++++++++++++++++++++ test/ast/exp.lua | 79 +++++++++++++-------------- 5 files changed, 262 insertions(+), 50 deletions(-) create mode 100644 src/parser/ast/binary.lua create mode 100644 src/parser/ast/unary.lua diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index c7753be..52ece70 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -18,6 +18,8 @@ require 'parser.ast.table' require 'parser.ast.block' require 'parser.ast.function' require 'parser.ast.field' +require 'parser.ast.unary' +require 'parser.ast.binary' ---@class LuaParser.Ast ---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast diff --git a/src/parser/ast/binary.lua b/src/parser/ast/binary.lua new file mode 100644 index 0000000..9d2b3f1 --- /dev/null +++ b/src/parser/ast/binary.lua @@ -0,0 +1,111 @@ +local class = require 'class' + +---@enum(key) LuaParser.BinarySymbol +local BinarySymbol = { + ['or'] = 1, + ['and'] = 2, + ['<='] = 3, + ['>='] = 3, + ['<'] = 3, + ['>'] = 3, + ['~='] = 3, + ['=='] = 3, + ['='] = 3, + ['|'] = 4, + ['~'] = 5, + ['&'] = 6, + ['<<'] = 7, + ['>>'] = 7, + ['..'] = 8, + ['+'] = 9, + ['-'] = 9, + ['*'] = 10, + ['//'] = 10, + ['/'] = 10, + ['%'] = 10, + ['^'] = 12, + -- nonstandard + ['&&'] = 2, + ['||'] = 1, + ['!='] = 3, +} + +local BinaryAlias = { + ['&&'] = 'and', + ['||'] = 'or', + ['!='] = '~=', +} + +---@class LuaParser.Node.Binary: LuaParser.Node.Base +---@field op LuaParser.BinarySymbol +---@field symbolPos integer +---@field exp1 LuaParser.Node.Exp +---@field exp2? LuaParser.Node.Exp +local Binary = class.declare('LuaParser.Node.Binary', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@param curExp LuaParser.Node.Exp +---@param curLevel? integer +---@return LuaParser.Node.Binary? +---@return integer? opLevel +function Ast:parseBinary(curExp, curLevel) + local token, _, pos = self.lexer:peek() + if not BinarySymbol[token] then + return nil + end + ---@cast pos -? + + local op = token + local myLevel = BinarySymbol[op] + if curLevel and myLevel < curLevel then + return nil + end + + if op == '//' and self.nssymbolMap['//'] then + -- 如果设置了 `//` 为非标准符号,那么就认为 `//` 是注释而不是整除 + return nil + end + if op == '=' then + self:throw('ERR_EQ_AS_ASSIGN', pos, pos + #op) + op = '==' + end + if BinaryAlias[op] then + if not self.nssymbolMap[op] then + self:throw('ERR_NONSTANDARD_SYMBOL', pos, pos + #op) + end + op = BinaryAlias[op] + end + if op == '//' + or op == '<<' + or op == '>>' + or op == '~' + or op == '&' + or op == '|' then + if self.versionNum < 53 then + self:throw('UNSUPPORT_SYMBOL', pos, pos + #op) + end + end + + self.lexer:next() + self:skipSpace() + + local exp2 = self:parseExp(true, myLevel) + + local binary = class.new('LuaParser.Node.Binary', { + ast = self, + start = curExp.start, + finish = self:getLastPos(), + op = op, + exp1 = curExp, + exp2 = exp2, + symbolPos = pos, + }) + curExp.parent = binary + if exp2 then + exp2.parent = binary + end + + return binary, myLevel +end diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index 0bea09a..9f575be 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -40,21 +40,51 @@ end ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +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, +} + ---@alias LuaParser.Node.Exp ---| LuaParser.Node.Term +---| LuaParser.Node.Unary +---| LuaParser.Node.Binary -- 解析表达式 ---@param required? true +---@param curLevel? integer ---@return LuaParser.Node.Exp? -function Ast:parseExp(required) - -- TODO - local exp = self:parseTerm() - - if not exp and required then - self:throw('MISS_EXP', self:getLastPos(), self:getLastPos()) +function Ast:parseExp(required, curLevel) + local curExp + + local unary, unaryLevel = self:parseUnary() + if unary then + curExp = unary + curLevel = unaryLevel + else + curExp = self:parseTerm() + if not curExp then + if required then + self:throw('MISS_EXP', self:getLastPos(), self:getLastPos()) + end + return nil + end + self:skipSpace(true) end - return exp + local binary = self:parseBinary(curExp, curLevel) + + return binary or curExp end -- 解析表达式列表,以逗号分隔 diff --git a/src/parser/ast/unary.lua b/src/parser/ast/unary.lua new file mode 100644 index 0000000..aa408b0 --- /dev/null +++ b/src/parser/ast/unary.lua @@ -0,0 +1,76 @@ +local class = require 'class' + +---@enum(key) LuaParser.UnarySymbol +local UnarySymbol = { + ['not'] = 11, + ['#'] = 11, + ['~'] = 11, + ['-'] = 11, + -- unstandard + ['!'] = 11, +} + +local UnaryAlias = { + ['!'] = 'not', +} + +---@class LuaParser.Node.Unary: LuaParser.Node.Base +---@field op LuaParser.UnarySymbol +---@field exp? LuaParser.Node.Exp +local Unary = class.declare('LuaParser.Node.Unary', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@return LuaParser.Node.Unary? +---@return integer? opLevel +function Ast:parseUnary() + local token, _, pos = self.lexer:peek() + if not UnarySymbol[token] then + return nil + end + ---@cast pos -? + + if token == '-' then + local savePoint = self.lexer:savePoint() + self.lexer:next() + self:skipSpace() + local nextToken, nextType = self.lexer:peek() + if nextToken == '.' + or nextType == 'Num' then + -- 负数? + savePoint() + return nil + end + else + self.lexer:next() + self:skipSpace() + end + + local op = token + if UnaryAlias[op] then + if not self.nssymbolMap[op] then + self:throw('ERR_NONSTANDARD_SYMBOL', pos, pos + #op) + end + op = UnaryAlias[op] + end + + if op == '~' then + if self.versionNum < 53 then + self:throw('UNSUPPORT_SYMBOL', pos, pos + #op) + end + end + + local myLevel = UnarySymbol[token] + local exp = self:parseExp(true, myLevel) + local unary = class.new('LuaParser.Node.Unary', { + ast = self, + start = pos, + finish = self:getLastPos(), + op = token, + exp = exp, + }) + exp.parent = unary + + return unary +end diff --git a/test/ast/exp.lua b/test/ast/exp.lua index a56590e..026e845 100644 --- a/test/ast/exp.lua +++ b/test/ast/exp.lua @@ -114,61 +114,53 @@ TEST 'a.b.c()' TEST '1 or 2' { - type = "binary", - start = 0, - finish = 6, - op = { - type = "or", - start = 2, - finish = 4, - }, - [1] = { - type = "integer", - start = 0, - finish = 1, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 2, - }, + start = 0, + finish = 6, + op = 'or', + symbolPos = 2, + exp1 = { + value = 1, + }, + exp2 = { + value = 2, + } } + TEST '1 < 2' { - type = "binary", start = 0, finish = 5, - op = { - type = "<", - start = 2, - finish = 3, - }, - [1] = { - type = "integer", - start = 0, - finish = 1, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 4, - finish = 5, - parent = "", - [1] = 2, + op = '<', + exp1 = { + value = 1, }, + exp2 = { + value = 2, + } } + TEST '- 1' { - type = "integer", + __class__ = 'LuaParser.Node.Integer', + start = 0, + finish = 3, + value = -1, +} + +TEST '- x' +{ + __class__ = 'LuaParser.Node.Unary', start = 0, finish = 3, - [1] = -1, + op = '-', + exp = { + __class__ = 'LuaParser.Node.Var', + start = 2, + finish = 3, + id = 'x', + } } + TEST 'not not true' { type = "unary", @@ -198,6 +190,7 @@ TEST 'not not true' }, }, } + TEST '1 ^ 2' { type = "binary", From 3d1a53f3b6a0e7f4feaa519b197ab9ae807f3e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Tue, 19 Sep 2023 01:37:31 +0800 Subject: [PATCH 27/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=E4=BA=8C=E5=85=83?= =?UTF-8?q?=E8=BF=90=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/binary.lua | 16 +- src/parser/ast/exp.lua | 30 +- test/ast/exp.lua | 759 +++++++++----------------------------- 3 files changed, 207 insertions(+), 598 deletions(-) diff --git a/src/parser/ast/binary.lua b/src/parser/ast/binary.lua index 9d2b3f1..0cd97ab 100644 --- a/src/parser/ast/binary.lua +++ b/src/parser/ast/binary.lua @@ -1,5 +1,6 @@ local class = require 'class' +-- 符号的优先级 ---@enum(key) LuaParser.BinarySymbol local BinarySymbol = { ['or'] = 1, @@ -36,6 +37,12 @@ local BinaryAlias = { ['!='] = '~=', } +-- 这些符号从右向左结合 +local RevertConcat = { + ['..'] = true, + ['^'] = true, +} + ---@class LuaParser.Node.Binary: LuaParser.Node.Base ---@field op LuaParser.BinarySymbol ---@field symbolPos integer @@ -59,8 +66,13 @@ function Ast:parseBinary(curExp, curLevel) local op = token local myLevel = BinarySymbol[op] - if curLevel and myLevel < curLevel then - return nil + if curLevel then + if myLevel < curLevel then + return nil + end + if myLevel == curLevel and not RevertConcat[op] then + return nil + end end if op == '//' and self.nssymbolMap['//'] then diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index 9f575be..54712b9 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -40,21 +40,6 @@ end ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' -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, -} - ---@alias LuaParser.Node.Exp ---| LuaParser.Node.Term ---| LuaParser.Node.Unary @@ -62,7 +47,7 @@ local SymbolForward = { -- 解析表达式 ---@param required? true ----@param curLevel? integer +---@param curLevel? integer # 新表达式的符号优先级必须比这个大 ---@return LuaParser.Node.Exp? function Ast:parseExp(required, curLevel) local curExp @@ -82,9 +67,15 @@ function Ast:parseExp(required, curLevel) self:skipSpace(true) end - local binary = self:parseBinary(curExp, curLevel) + while true do + local binary = self:parseBinary(curExp, curLevel) + if not binary then + break + end + curExp = binary + end - return binary or curExp + return curExp end -- 解析表达式列表,以逗号分隔 @@ -192,7 +183,8 @@ function Ast:parseParen() exp = exp, }) exp.parent = paren - paren.finish = self:assertSymbol ')' or exp.finish + self:assertSymbol ')' + paren.finish = self:getLastPos() return paren end diff --git a/test/ast/exp.lua b/test/ast/exp.lua index 026e845..d127f39 100644 --- a/test/ast/exp.lua +++ b/test/ast/exp.lua @@ -163,711 +163,316 @@ TEST '- x' TEST 'not not true' { - type = "unary", start = 0, finish = 12, - op = { - type = "not", - start = 0, - finish = 3, - }, - [1] = { - type = "unary", + op = 'not', + exp = { start = 4, finish = 12, - parent = "", - op = { - type = "not", - start = 4, - finish = 7, - }, - [1] = { - type = "boolean", - start = 8, - finish = 12, - parent = "", - [1] = true, - }, - }, + op = 'not', + exp = { + value = true, + } + } } TEST '1 ^ 2' { - type = "binary", start = 0, finish = 5, - op = { - type = "^", - start = 2, - finish = 3, - }, - [1] = { - type = "integer", - start = 0, - finish = 1, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 4, - finish = 5, - parent = "", - [1] = 2, + op = '^', + exp1 = { + value = 1, }, + exp2 = { + value = 2, + } } TEST '1 ^ -2' { - type = "binary", start = 0, finish = 6, - op = { - type = "^", - start = 2, - finish = 3, - }, - [1] = { - type = "integer", - start = 0, - finish = 1, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 4, - finish = 6, - parent = "", - [1] = -2, + op = '^', + exp1 = { + value = 1, }, + exp2 = { + value = -2, + } } + TEST '-1 ^ 2' { - type = "unary", start = 0, finish = 6, - op = { - type = "-", - start = 0, - finish = 1, - }, - [1] = { - type = "binary", - start = 1, - finish = 6, - parent = "", - op = { - type = "^", - start = 3, - finish = 4, - }, - [1] = { - type = 'integer', - start = 1, - finish = 2, - parent = "", - [1] = 1, - }, - [2] = { - type = 'integer', - start = 5, - finish = 6, - parent = "", - [1] = 2, - }, + op = '^', + exp1 = { + value = -1, }, + exp2 = { + value = 2, + } } -TEST '...' -{ - type = "varargs", - start = 0, - finish = 3, -} + TEST '1 + 2 + 3' { - type = "binary", - start = 0, - finish = 9, - op = { - type = "+", - start = 6, - finish = 7, - }, - [1] = { - type = "binary", - start = 0, - finish = 5, - parent = "", - op = { - type = "+", - start = 2, - finish = 3, - }, - [1] = { - type = "integer", - start = 0, - finish = 1, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 4, - finish = 5, - parent = "", - [1] = 2, + op = '+', + exp1 = { + op = '+', + exp1 = { + value = 1, }, + exp2 = { + value = 2, + } }, - [2] = { - type = "integer", - start = 8, - finish = 9, - parent = "", - [1] = 3, - }, + exp2 = { + value = 3, + } } + TEST '1 + 2 * 3' { - type = "binary", - start = 0, - finish = 9, - op = { - type = "+", - start = 2, - finish = 3, - }, - [1] = { - type = "integer", - start = 0, - finish = 1, - parent = "", - [1] = 1, + op = '+', + exp1 = { + value = 1, }, - [2] = { - type = "binary", - start = 4, - finish = 9, - parent = "", - op = { - type = "*", - start = 6, - finish = 7, + exp2 = { + op = '*', + exp1 = { + value = 2, }, - [1] = { - type = "integer", - start = 4, - finish = 5, - parent = "", - [1] = 2, - }, - [2] = { - type = "integer", - start = 8, - finish = 9, - parent = "", - [1] = 3, - }, - }, + exp2 = { + value = 3, + } + } } + TEST '- 1 + 2 * 3' { - type = "binary", - start = 0, - finish = 11, - op = { - type = "+", - start = 4, - finish = 5, - }, - [1] = { - type = "integer", - start = 0, - finish = 3, - parent = "", - [1] = -1, + op = '+', + exp1 = { + value = -1, }, - [2] = { - type = "binary", - start = 6, - finish = 11, - parent = "", - op = { - type = "*", - start = 8, - finish = 9, + exp2 = { + op = '*', + exp1 = { + value = 2, }, - [1] = { - type = "integer", - start = 6, - finish = 7, - parent = "", - [1] = 2, - }, - [2] = { - type = "integer", - start = 10, - finish = 11, - parent = "", - [1] = 3, - }, - }, + exp2 = { + value = 3, + } + } } TEST '-1 + 2 * 3' { - type = "binary", - start = 0, - finish = 10, - op = { - type = "+", - start = 3, - finish = 4, - }, - [1] = { - type = "integer", - start = 0, - finish = 2, - parent = "", - [1] = -1, + op = '+', + exp1 = { + value = -1, }, - [2] = { - type = "binary", - start = 5, - finish = 10, - parent = "", - op = { - type = "*", - start = 7, - finish = 8, - }, - [1] = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 2, - }, - [2] = { - type = "integer", - start = 9, - finish = 10, - parent = "", - [1] = 3, + exp2 = { + op = '*', + exp1 = { + value = 2, }, - }, + exp2 = { + value = 3, + } + } } -CHECK"x and y == 'unary' and z" + +TEST "x and y == 'unary' and z" { - type = "binary", start = 0, finish = 24, - op = { - type = "and", - start = 19, - finish = 22, - }, - [1] = { - type = "binary", - start = 0, - finish = 18, - parent = "", - op = { - type = "and", - start = 2, - finish = 5, - }, - [1] = { - type = "getglobal", - start = 0, - finish = 1, - parent = "", - [1] = "x", - }, - [2] = { - type = "binary", - start = 6, - finish = 18, - parent = "", - op = { - type = "==", - start = 8, - finish = 10, - }, - [1] = { - type = "getglobal", - start = 6, - finish = 7, - parent = "", - [1] = "y", + op = 'and', + exp1 = { + op = 'and', + exp1 = { + id = 'x' + }, + exp2 = { + op = '==', + exp1 = { + id = 'y' }, - [2] = { - type = "string", - start = 11, - finish = 18, - parent = "", - [1] = "unary", - [2] = "'", + exp2 = { + value = 'unary' }, }, }, - [2] = { - type = "getglobal", - start = 23, - finish = 24, - parent = "", - [1] = "z", + exp2 = { + id = 'z' }, } -CHECK"x and y or '' .. z" + +TEST "x and y or '' .. z" { - type = "binary", - start = 0, - finish = 18, - op = { - type = "or", - start = 8, - finish = 10, - }, - [1] = { - type = "binary", - start = 0, - finish = 7, - parent = "", - op = { - type = "and", - start = 2, - finish = 5, + op = 'or', + exp1 = { + op = 'and', + exp1 = { + id = 'x', }, - [1] = { - type = "getglobal", - start = 0, - finish = 1, - parent = "", - [1] = "x", - }, - [2] = { - type = "getglobal", - start = 6, - finish = 7, - parent = "", - [1] = "y", + exp2 = { + id = 'y', }, }, - [2] = { - type = "binary", - start = 11, - finish = 18, - parent = "", - op = { - type = "..", - start = 14, - finish = 16, + exp2 = { + op = '..', + exp1 = { + value = '', }, - [1] = { - type = "string", - start = 11, - finish = 13, - parent = "", - [1] = "", - [2] = "'", - }, - [2] = { - type = "getglobal", - start = 17, - finish = 18, - parent = "", - [1] = "z", + exp2 = { + id = 'z', }, }, } + -- 幂运算从右向左连接 TEST '1 ^ 2 ^ 3' { - type = "binary", - start = 0, - finish = 9, - op = { - type = "^", - start = 2, - finish = 3, - }, - [1] = { - type = "integer", - start = 0, - finish = 1, - parent = "", - [1] = 1, + op = '^', + exp1 = { + value = 1, }, - [2] = { - type = "binary", - start = 4, - finish = 9, - parent = "", - op = { - type = "^", - start = 6, - finish = 7, - }, - [1] = { - type = "integer", - start = 4, - finish = 5, - parent = "", - [1] = 2, + exp2 = { + op = '^', + exp1 = { + value = 2, }, - [2] = { - type = "integer", - start = 8, - finish = 9, - parent = "", - [1] = 3, + exp2 = { + value = 3, }, }, } + -- 连接运算从右向左连接 TEST '1 .. 2 .. 3' { - type = "binary", - start = 0, - finish = 11, - op = { - type = "..", - start = 2, - finish = 4, - }, - [1] = { - type = "integer", - start = 0, - finish = 1, - parent = "", - [1] = 1, + op = '..', + exp1 = { + value = 1, }, - [2] = { - type = "binary", - start = 5, - finish = 11, - parent = "", - op = { - type = "..", - start = 7, - finish = 9, - }, - [1] = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 2, + exp2 = { + op = '..', + exp1 = { + value = 2, }, - [2] = { - type = "integer", - start = 10, - finish = 11, - parent = "", - [1] = 3, + exp2 = { + value = 3, }, }, } -TEST '1 + - - - - - - - 1' + +TEST '1 + - - 1' { - type = "binary", - start = 0, - finish = 19, - op = { - type = "+", - start = 2, - finish = 3, - }, - [1] = { - type = "integer", - start = 0, - finish = 1, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 4, - finish = 19, - parent = "", - [1] = -1, + op = '+', + exp1 = { + value = 1, }, + exp2 = { + op = '-', + exp = { + value = -1 + } + } } + TEST '(1)' { - type = "paren", start = 0, finish = 3, exp = { - type = "integer", start = 1, finish = 2, - parent = "", - [1] = 1, + value = 1, }, } + TEST '(1 + 2)' { - type = "paren", - start = 0, - finish = 7, - exp = { - type = "binary", - start = 1, - finish = 6, - parent = "", - op = { - type = "+", - start = 3, - finish = 4, - }, - [1] = { - type = "integer", - start = 1, - finish = 2, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 2, + exp = { + op = '+', + exp1 = { + value = 1, }, - }, + exp2 = { + value = 2, + } + } } + TEST 'func(1)' { - type = "call", start = 0, finish = 7, - node = "", + argPos = 4, + node = { + __class__ = 'LuaParser.Node.Var', + id = 'func', + }, args = { - type = "callargs", - start = 4, - finish = 7, - parent = "", - [1] = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 1, + [1] = { + value = 1 }, }, } + TEST 'func(1, 2)' { - type = "call", - start = 0, - finish = 10, - node = "", - args = { - type = "callargs", - start = 4, - finish = 10, - parent = "", - [1] = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 1, + node = { + id = 'func', + }, + args = { + [1] = { + value = 1, }, - [2] = { - type = "integer", - start = 8, - finish = 9, - parent = "", - [1] = 2, + [2] = { + value = 2, }, - }, + } } + TEST 'func(...)' { - type = "call", - start = 0, - finish = 9, - node = "", args = { - type = "callargs", - start = 4, - finish = 9, - parent = "", - [1] = { - type = "varargs", - start = 5, - finish = 8, - parent = "", + [1] = { + __class__ = 'LuaParser.Node.Varargs', }, }, } + TEST 'func(1, ...)' { - type = "call", - start = 0, - finish = 12, - node = "", args = { - type = "callargs", - start = 4, - finish = 12, - parent = "", - [1] = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 1, + [1] = { + value = 1, }, - [2] = { - type = "varargs", - start = 8, - finish = 11, - parent = "", + [2] = { + __class__ = 'LuaParser.Node.Varargs', }, }, } + TEST 'func ""' { - type = "call", - start = 0, - finish = 7, - node = "", - args = { - type = "callargs", - start = 5, - finish = 7, - parent = "", - [1] = { - type = "string", - start = 5, - finish = 7, - parent = "", - [1] = "", - [2] = "\"", + args = { + [1] = { + value = '' }, }, } + TEST 'func {}' { - type = "call", - start = 0, - finish = 7, - node = "", - args = { - type = "callargs", - start = 5, - finish = 7, - parent = "", - [1] = { - type = "table", - start = 5, - finish = 7, - parent = "", - }, + args = { + [1] = { + __class__ = 'LuaParser.Node.Table', + } }, } + TEST 'table[1]' { type = "getindex", From 9897538cfa4ed5aa70a225a3e03dd6342610268a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Tue, 19 Sep 2023 11:26:45 +0800 Subject: [PATCH 28/94] =?UTF-8?q?=E8=BF=87=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/field.lua | 10 +++- test/ast/exp.lua | 107 +++++++++++++++------------------------ 2 files changed, 50 insertions(+), 67 deletions(-) diff --git a/src/parser/ast/field.lua b/src/parser/ast/field.lua index ef14f56..141d94f 100644 --- a/src/parser/ast/field.lua +++ b/src/parser/ast/field.lua @@ -59,12 +59,18 @@ function Ast:parseField(last) return nil end if token == '[' then + local nextChar = self.code:sub(pos + 2, pos + 2) + if nextChar == '[' + or nextChar == '=' then + -- 长字符串? + return nil + end self.lexer:next() self:skipSpace() local key = self:parseExp(true) if key then self:skipSpace() - local hasSymbol = self:assertSymbol(']') + local symbolPos2 = self:assertSymbol(']') local field = class.new('LuaParser.Node.Field', { ast = self, start = last.start, @@ -73,7 +79,7 @@ function Ast:parseField(last) key = key, last = last, symbolPos = pos, - symbolPos2 = hasSymbol and self:getLastPos() or nil, + symbolPos2 = symbolPos2, }) last.parent = field last.next = field diff --git a/test/ast/exp.lua b/test/ast/exp.lua index d127f39..f9bfc3d 100644 --- a/test/ast/exp.lua +++ b/test/ast/exp.lua @@ -475,92 +475,69 @@ TEST 'func {}' TEST 'table[1]' { - type = "getindex", - start = 0, - finish = 8, - node = "", - index = { - type = "integer", - start = 6, - finish = 7, - parent = "", - [1] = 1, - }, + subtype = 'index', + symbolPos = 5, + symbolPos2 = 7, + key = { + value = 1, + } } + TEST 'table[[1]]' { - type = "call", start = 0, finish = 10, - node = "", + node = { + id = 'table' + }, args = { - type = "callargs", - start = 5, - finish = 10, - parent = "", - [1] = { - type = "string", - start = 5, - finish = 10, - parent = "", - [1] = '1', - [2] = '[[' + [1] = { + value = '1' }, }, } + TEST 'get_point().x' { - type = "getfield", - start = 0, - finish = 13, - node = "", - dot = { - type = ".", - start = 11, - finish = 12, - }, - field = { - type = "field", - start = 12, - finish = 13, - parent = "", - [1] = "x", + subtype = 'field', + key = { + id = 'x' }, + last = { + __class__ = 'LuaParser.Node.Call', + node = { + id = 'get_point', + }, + } } + TEST 'obj:remove()' { - type = "call", - start = 0, - finish = 12, - node = "", - args = { - type = "callargs", - start = 0, - finish = 12, - parent = "", - [1] = { - type = "self", - start = 3, - finish = 4, - parent = "", - [1] = "self", + args = {}, + node = { + subtype = 'method', + key = { + id = 'remove' }, - }, + last = { + id = 'obj' + } + } } + TEST '(...)[1]' { - type = "getindex", - start = 0, - finish = 8, - node = "", - index = { - type = "integer", - start = 6, - finish = 7, - parent = "", - [1] = 1, + subtype = 'index', + key = { + value = 1, }, + last = { + exp = { + __class__ = 'LuaParser.Node.Varargs', + } + } } + TEST 'function () end' { type = "function", From d6fd8df7e359612a4524d90383a6a7966a7524fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Tue, 19 Sep 2023 17:01:19 +0800 Subject: [PATCH 29/94] =?UTF-8?q?=E8=A1=A8=E8=BE=BE=E5=BC=8F=E9=80=9A?= =?UTF-8?q?=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/block.lua | 19 +++++ src/parser/ast/exp.lua | 2 + src/parser/ast/function.lua | 139 +++++++++++++++++++++++++++++++++++ src/parser/ast/id.lua | 9 ++- test/ast/exp.lua | 142 ++++++++---------------------------- 5 files changed, 196 insertions(+), 115 deletions(-) diff --git a/src/parser/ast/block.lua b/src/parser/ast/block.lua index b7fdcd8..f557b96 100644 --- a/src/parser/ast/block.lua +++ b/src/parser/ast/block.lua @@ -6,3 +6,22 @@ local Block = class.declare('LuaParser.Node.Block', 'LuaParser.Node.Base') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' + +---@return LuaParser.Node.Block +function Ast:parseBlock() + local childs = {} + + local block = class.new('LuaParser.Node.Block', { + ast = self, + start = self:getLastPos(), + finish = self:getLastPos(), + childs = childs, + }) + + for i = 1, #childs do + local child = childs[i] + child.parent = block + end + + return block +end diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index 54712b9..f8db560 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -126,6 +126,7 @@ end ---| LuaParser.Node.Paren ---| LuaParser.Node.Varargs ---| LuaParser.Node.Table +---| LuaParser.Node.Function ---@alias LuaParser.Node.TermChain ---| LuaParser.Node.Field @@ -140,6 +141,7 @@ function Ast:parseTerm() or self:parseNumber() or self:parseString() or self:parseVarargs() + or self:parseFunction() or self:parseVar() or self:parseParen() or self:parseTable() diff --git a/src/parser/ast/function.lua b/src/parser/ast/function.lua index 989b923..00195d0 100644 --- a/src/parser/ast/function.lua +++ b/src/parser/ast/function.lua @@ -1,4 +1,143 @@ local class = require 'class' +---@class LuaParser.Node.Param: LuaParser.Node.Base +---@field parent LuaParser.Node.Function +---@field index integer +---@field id string +local Param = class.declare('LuaParser.Node.Param', 'LuaParser.Node.Base') + +---@class LuaParser.Node.FuncName: LuaParser.Node.Base +---@field parent LuaParser.Node.Function +local FuncName = class.declare('LuaParser.Node.FuncName', 'LuaParser.Node.Base') + ---@class LuaParser.Node.Function: LuaParser.Node.Base +---@field name? LuaParser.Node.Term +---@field params? LuaParser.Node.Local[] +---@field symbolPos1? integer # 左括号 +---@field symbolPos2? integer # 右括号 +---@field symbolPos3? integer # `end` +---@field block? LuaParser.Node.Block local Function = class.declare('LuaParser.Node.Function', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@return LuaParser.Node.Function? +function Ast:parseFunction() + local pos = self.lexer:consume 'function' + if not pos then + return nil + end + + self:skipSpace() + local name = self:parseFunctionName() + + self:skipSpace() + local symbolPos1 = self.lexer:consume '(' + if not name and not symbolPos1 then + return nil + end + + local params + if symbolPos1 then + self:skipSpace() + params = self:parseParamList() + else + self:throwMissSymbol(self:getLastPos(), '(') + end + + self:skipSpace() + local symbolPos2 = self:assertSymbol ')' + + local block + if symbolPos2 then + block = self:parseBlock() + end + + self:skipSpace() + local symbolPos3 = self:assertSymbol 'end' + + local func = class.new('LuaParser.Node.Function', { + ast = self, + start = pos, + finish = self:getLastPos(), + name = name, + params = params, + symbolPos1 = symbolPos1, + symbolPos2 = symbolPos2, + symbolPos3 = symbolPos3, + }) + + if name then + name.parent = func + end + + if params then + for i = 1, #params do + local param = params[i] + param.parent = func + param.index = i + end + end + + if block then + block.parent = func + end + + return func +end + +---@return LuaParser.Node.FuncName? +function Ast:parseFunctionName() + +end + +---@return LuaParser.Node.Param[] +function Ast:parseParamList() + ---@type LuaParser.Node.Param[] + local list = {} + + local first = self:parseParam() + list[#list+1] = first + while true do + self:skipSpace() + local token, tp = self.lexer:peek() + if not token then + break + end + if tp == 'Symbol' then + if token == ',' then + self.lexer:next() + self:skipSpace() + else + break + end + else + self:throwMissSymbol(self:getLastPos(), ',') + end + local param = self:parseParam() + if param then + list[#list+1] = param + end + end + + return list +end + +---@return LuaParser.Node.Param? +function Ast:parseParam() + local param = self:parseID('LuaParser.Node.Param') + if param then + return param + end + local pos = self.lexer:consume '...' + if pos then + return class.new('LuaParser.Node.Param', { + ast = self, + start = pos, + finish = pos, + id = '...', + }) + end + return nil +end diff --git a/src/parser/ast/id.lua b/src/parser/ast/id.lua index f8a0783..326d974 100644 --- a/src/parser/ast/id.lua +++ b/src/parser/ast/id.lua @@ -8,6 +8,7 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.Var ---| LuaParser.Node.FieldID ---| LuaParser.Node.TableFieldID +---| LuaParser.Node.Param ---@generic T: LuaParser.Node.ID ---@param nodeType `T` @@ -17,7 +18,7 @@ function Ast:parseID(nodeType, required) local token, tp, pos = self.lexer:peek() if tp ~= 'Word' then if required then - self:throw('MISS_NAME', self:getLastPos(), self:getLastPos()) + self:throw('MISS_NAME', self:getLastPos()) end return nil end @@ -27,7 +28,11 @@ function Ast:parseID(nodeType, required) self:throw('UNICODE_NAME', pos, pos + #token) end if self:isKeyWord(token) then - self:throw('KEYWORD', pos, pos + #token) + if required then + self:throw('KEYWORD', pos, pos + #token) + else + return nil + end end self.lexer:next() return class.new(nodeType, { diff --git a/test/ast/exp.lua b/test/ast/exp.lua index f9bfc3d..0e3e631 100644 --- a/test/ast/exp.lua +++ b/test/ast/exp.lua @@ -540,128 +540,44 @@ TEST '(...)[1]' TEST 'function () end' { - type = "function", - start = 0, - bstart = 11, - finish = 15, - keyword = { - [1] = 0, - [2] = 8, - [3] = 12, - [4] = 15, - }, - args = { - type = "funcargs", - start = 9, - finish = 11, - parent = "", - }, + start = 0, + finish = 15, + symbolPos1 = 9, + symbolPos2 = 10, + symbolPos3 = 12, } + TEST 'function (...) end' { - type = "function", - start = 0, - bstart = 14, - finish = 18, - keyword = { - [1] = 0, - [2] = 8, - [3] = 15, - [4] = 18, - }, - vararg = "", - args = { - type = "funcargs", - start = 9, - finish = 14, - parent = "", - [1] = { - type = "...", - start = 10, - finish = 13, - parent = "", - [1] = "...", - }, - }, + params = { + [1] = { + id = '...' + } + } } + TEST 'function (a, ...) end' { - type = "function", - start = 0, - bstart = 17, - finish = 21, - keyword = { - [1] = 0, - [2] = 8, - [3] = 18, - [4] = 21, - }, - vararg = "", - args = { - type = "funcargs", - start = 9, - finish = 17, - parent = "", - [1] = { - type = "local", - start = 10, - finish = 11, - effect = 11, - parent = "", - [1] = "a", - }, - [2] = { - type = "...", - start = 13, - finish = 16, - parent = "", - [1] = "...", + params = { + [1] = { + id = 'a' }, - }, - locals = "", -} - -CHECK 'notify' -{ - type = "getglobal", - start = 0, - finish = 6, - [1] = "notify", + [2] = { + id = '...' + } + } } -CHECK 'a ^ - b' +TEST 'a ^ - b' { - type = "binary", - start = 0, - finish = 7, - op = { - type = "^", - start = 2, - finish = 3, - }, - [1] = { - type = "getglobal", - start = 0, - finish = 1, - parent = "", - [1] = "a", - }, - [2] = { - type = "unary", - start = 4, - finish = 7, - parent = "", - op = { - type = "-", - start = 4, - finish = 5, - }, - [1] = { - type = "getglobal", - start = 6, - finish = 7, - parent = "", - [1] = "b", - }, + op = '^', + exp1 = { + id = 'a' }, + exp2 = { + op = '-', + exp = { + id = 'b' + } + } } From bbd5de01bf0cf7b03b496b743fa06f8fd0a0d06d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Tue, 19 Sep 2023 18:14:16 +0800 Subject: [PATCH 30/94] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/state.lua | 29 ++++++++++++++++++++++++++++ test/ast/init.lua | 1 + test/ast/state.lua | 41 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 test/ast/state.lua diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua index 617ca8a..8e90f6b 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state.lua @@ -1,11 +1,40 @@ local class = require 'class' +---@class LuaParser.Node.Assign: LuaParser.Node.Base +---@field exps LuaParser.Node.Exp[] +---@field vars LuaParser.Node.Var[] +local Assign = class.declare('LuaParser.Node.Assign', 'LuaParser.Node.Base') + +---@class LuaParser.Node.SingleExp +---@field exp LuaParser.Node.Exp + ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' ---@alias LuaParser.Node.State ---| LuaParser.Node.LocalDef +---| LuaParser.Node.ExpAsState +---@return LuaParser.Node.State? function Ast:parseState() return self:parseLocal() + or self:parseExpAsState() +end + +---@alias LuaParser.Node.ExpAsState +---| LuaParser.Node.Call +---| LuaParser.Node.Assign +---| LuaParser.Node.SingleExp + +---@return LuaParser.Node.ExpAsState? +function Ast:parseExpAsState() + local exp = self:parseExp() + if not exp then + return nil + end + + if class.type(exp) == 'LuaParser.Node.Call' then + ---@cast exp LuaParser.Node.Call + return exp + end end diff --git a/test/ast/init.lua b/test/ast/init.lua index bfdfc58..a75cbb5 100644 --- a/test/ast/init.lua +++ b/test/ast/init.lua @@ -20,3 +20,4 @@ require 'test.ast.comment' require 'test.ast.local' require 'test.ast.table' require 'test.ast.exp' +require 'test.ast.state' diff --git a/test/ast/state.lua b/test/ast/state.lua new file mode 100644 index 0000000..d69d62c --- /dev/null +++ b/test/ast/state.lua @@ -0,0 +1,41 @@ +local class = require 'class' + +local function TEST(code) + return function (expect) + local ast = class.new 'LuaParser.Ast' (code) + local node = ast:parseState() + assert(node) + Match(node, expect) + end +end + +TEST [[local x, y = 1, 2, 3]] +{ + start = 0, + finish = 20, + vars = { + [1] = { + id = 'x', + value = { + value = 1, + } + }, + [2] = { + id = 'y', + value = { + value = 2, + } + }, + }, + values = { + [1] = { + value = 1, + }, + [2] = { + value = 2, + }, + [3] = { + value = 3, + }, + } +} From 20fb3d3504d4ad3242fc80d6dad2dbc2b7b5f549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 20 Sep 2023 01:15:13 +0800 Subject: [PATCH 31/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=E8=B5=8B=E5=80=BC?= =?UTF-8?q?=E8=AF=AD=E5=8F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/binary.lua | 8 +++- src/parser/ast/exp.lua | 7 +-- src/parser/ast/state.lua | 80 ++++++++++++++++++++++++++++++++-- src/parser/ast/unary.lua | 2 +- test/ast/state.lua | 91 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 178 insertions(+), 10 deletions(-) diff --git a/src/parser/ast/binary.lua b/src/parser/ast/binary.lua index 0cd97ab..40deaa6 100644 --- a/src/parser/ast/binary.lua +++ b/src/parser/ast/binary.lua @@ -55,9 +55,10 @@ local Ast = class.declare 'LuaParser.Ast' ---@param curExp LuaParser.Node.Exp ---@param curLevel? integer +---@param asState? boolean # 是否作为语句解析 ---@return LuaParser.Node.Binary? ---@return integer? opLevel -function Ast:parseBinary(curExp, curLevel) +function Ast:parseBinary(curExp, curLevel, asState) local token, _, pos = self.lexer:peek() if not BinarySymbol[token] then return nil @@ -80,6 +81,9 @@ function Ast:parseBinary(curExp, curLevel) return nil end if op == '=' then + if asState then + return nil + end self:throw('ERR_EQ_AS_ASSIGN', pos, pos + #op) op = '==' end @@ -103,7 +107,7 @@ function Ast:parseBinary(curExp, curLevel) self.lexer:next() self:skipSpace() - local exp2 = self:parseExp(true, myLevel) + local exp2 = self:parseExp(true, false, myLevel) local binary = class.new('LuaParser.Node.Binary', { ast = self, diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index f8db560..3a5f5eb 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -46,10 +46,11 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.Binary -- 解析表达式 ----@param required? true +---@param required? boolean ---@param curLevel? integer # 新表达式的符号优先级必须比这个大 +---@param asState? boolean # 是否作为语句解析 ---@return LuaParser.Node.Exp? -function Ast:parseExp(required, curLevel) +function Ast:parseExp(required, asState, curLevel) local curExp local unary, unaryLevel = self:parseUnary() @@ -68,7 +69,7 @@ function Ast:parseExp(required, curLevel) end while true do - local binary = self:parseBinary(curExp, curLevel) + local binary = self:parseBinary(curExp, curLevel, asState) if not binary then break end diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua index 8e90f6b..825b024 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state.lua @@ -2,11 +2,12 @@ local class = require 'class' ---@class LuaParser.Node.Assign: LuaParser.Node.Base ---@field exps LuaParser.Node.Exp[] ----@field vars LuaParser.Node.Var[] +---@field values LuaParser.Node.Var[] local Assign = class.declare('LuaParser.Node.Assign', 'LuaParser.Node.Base') ---@class LuaParser.Node.SingleExp ---@field exp LuaParser.Node.Exp +local SingleExp = class.declare('LuaParser.Node.SingleExp', 'LuaParser.Node.Base') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' @@ -18,7 +19,7 @@ local Ast = class.declare 'LuaParser.Ast' ---@return LuaParser.Node.State? function Ast:parseState() return self:parseLocal() - or self:parseExpAsState() + or self:parseStateStartWithExp() end ---@alias LuaParser.Node.ExpAsState @@ -27,8 +28,8 @@ end ---| LuaParser.Node.SingleExp ---@return LuaParser.Node.ExpAsState? -function Ast:parseExpAsState() - local exp = self:parseExp() +function Ast:parseStateStartWithExp() + local exp = self:parseExp(false, true) if not exp then return nil end @@ -37,4 +38,75 @@ function Ast:parseExpAsState() ---@cast exp LuaParser.Node.Call return exp end + + if class.type(exp) == 'LuaParser.Node.Var' + or class.type(exp) == 'LuaParser.Node.Field' then + ---@cast exp LuaParser.Node.Field + self:skipSpace() + local assign = self:parseAssign(exp) + if assign then + return assign + end + end + + local state = class.new('LuaParser.Node.SingleExp', { + start = exp.start, + finish = exp.finish, + exp = exp, + }) + exp.parent = state + + return state +end + +---@param first LuaParser.Node.Field +---@return LuaParser.Node.Assign? +function Ast:parseAssign(first) + local token = self.lexer:peek() + if token ~= '=' and token ~= ',' then + return nil + end + + local assign = class.new('LuaParser.Node.Assign', { + start = first.start, + exps = {}, + }) + + assign.exps[1] = first + first.parent = assign + while true do + local comma = self.lexer:consume ',' + if not comma then + break + end + self:skipSpace() + local exp = self:parseExp(true, true) + if not exp then + break + end + assign.exps[#assign.exps+1] = exp + self:skipSpace() + end + + local eqPos = self:assertSymbol '=' + + if eqPos then + self:skipSpace() + local values = self:parseExpList(true) + assign.values = values + + for i = 1, #values do + local value = values[i] + value.parent = assign + + local exp = assign.exps[i] + if exp then + exp.value = value + end + end + end + + assign.finish = self:getLastPos() + + return assign end diff --git a/src/parser/ast/unary.lua b/src/parser/ast/unary.lua index aa408b0..ff6b3c5 100644 --- a/src/parser/ast/unary.lua +++ b/src/parser/ast/unary.lua @@ -62,7 +62,7 @@ function Ast:parseUnary() end local myLevel = UnarySymbol[token] - local exp = self:parseExp(true, myLevel) + local exp = self:parseExp(true, false, myLevel) local unary = class.new('LuaParser.Node.Unary', { ast = self, start = pos, diff --git a/test/ast/state.lua b/test/ast/state.lua index d69d62c..90606b9 100644 --- a/test/ast/state.lua +++ b/test/ast/state.lua @@ -11,6 +11,7 @@ end TEST [[local x, y = 1, 2, 3]] { + __class__ = 'LuaParser.Node.LocalDef', start = 0, finish = 20, vars = { @@ -39,3 +40,93 @@ TEST [[local x, y = 1, 2, 3]] }, } } + +TEST [[x = 1]] +{ + __class__ = 'LuaParser.Node.Assign', + start = 0, + finish = 5, + exps = { + [1] = { + start = 0, + finish = 1, + id = 'x', + value = { + value = 1, + } + }, + }, + values = { + [1] = { + value = 1, + }, + } +} + +TEST [[x.x, y.y = 1, 2, 3]] +{ + __class__ = 'LuaParser.Node.Assign', + exps = { + [1] = { + subtype = 'field', + last = { + id = 'x', + }, + key = { + id = 'x', + }, + value = { + value = 1, + }, + }, + [2] = { + subtype = 'field', + key = { + id = 'y', + }, + value = { + value = 2, + }, + }, + }, + values = { + [1] = { + value = 1, + }, + [2] = { + value = 2, + }, + [3] = { + value = 3, + }, + }, +} + +TEST [[x.y()]] +{ + __class__ = 'LuaParser.Node.Call', +} + +TEST [[x.y().z = 1]] +{ + __class__ = 'LuaParser.Node.Assign', + exps = { + [1] = { + subtype = 'field', + last = { + __class__ = 'LuaParser.Node.Call', + }, + key = { + id = 'z', + }, + value = { + value = 1, + }, + }, + }, + values = { + [1] = { + value = 1, + } + } +} From ad55a9e4a534ebcbd6c05eafb4bcb0e2fd9a701a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 20 Sep 2023 16:58:45 +0800 Subject: [PATCH 32/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=20label=20=E5=92=8C=20?= =?UTF-8?q?goto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 1 + src/parser/ast/id.lua | 1 + src/parser/ast/label.lua | 72 ++++++++++++++++++++++++++++++++++++++++ src/parser/ast/state.lua | 25 +++++++++++--- test/ast/state.lua | 24 ++++++++++++++ 5 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 src/parser/ast/label.lua diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 52ece70..83f2642 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -20,6 +20,7 @@ require 'parser.ast.function' require 'parser.ast.field' require 'parser.ast.unary' require 'parser.ast.binary' +require 'parser.ast.label' ---@class LuaParser.Ast ---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast diff --git a/src/parser/ast/id.lua b/src/parser/ast/id.lua index 326d974..a1dca76 100644 --- a/src/parser/ast/id.lua +++ b/src/parser/ast/id.lua @@ -9,6 +9,7 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.FieldID ---| LuaParser.Node.TableFieldID ---| LuaParser.Node.Param +---| LuaParser.Node.LabelName ---@generic T: LuaParser.Node.ID ---@param nodeType `T` diff --git a/src/parser/ast/label.lua b/src/parser/ast/label.lua new file mode 100644 index 0000000..ac7a6ae --- /dev/null +++ b/src/parser/ast/label.lua @@ -0,0 +1,72 @@ +local class = require 'class' + +---@class LuaParser.Node.Label: LuaParser.Node.Base +---@field label LuaParser.Node.LabelName +---@field symbolPos? integer # 右边标签符号的位置 +local Label = class.declare('LuaParser.Node.Label', 'LuaParser.Node.Base') + +---@class LuaParser.Node.LabelName: LuaParser.Node.Base +---@field parent LuaParser.Node.Label | LuaParser.Node.Goto +---@field id string +local LabelName = class.declare('LuaParser.Node.LabelName', 'LuaParser.Node.Base') + +---@class LuaParser.Node.Goto: LuaParser.Node.Base +---@field label LuaParser.Node.LabelName +local Goto = class.declare('LuaParser.Node.Goto', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@return LuaParser.Node.Label? +function Ast:parseLabel() + local pos = self.lexer:consume '::' + if not pos then + return nil + end + + self:skipSpace() + local labelName = self:parseID('LuaParser.Node.LabelName', true) + if not labelName then + return nil + end + + self:skipSpace() + local symbolPos = self:assertSymbol '::' + + local label = class.new('LuaParser.Node.Label', { + ast = self, + start = pos, + finish = self:getLastPos(), + symbolPos = symbolPos, + }) + + label.label = labelName + labelName.parent = label + + return label +end + +---@return LuaParser.Node.Goto? +function Ast:parseGoto() + local pos = self.lexer:consume 'goto' + if not pos then + return nil + end + + self:skipSpace() + local labelName = self:parseID('LuaParser.Node.LabelName', true) + if not labelName then + return nil + end + + local gotoNode = class.new('LuaParser.Node.Goto', { + ast = self, + start = pos, + finish = self:getLastPos(), + }) + + gotoNode.label = labelName + labelName.parent = gotoNode + + return gotoNode +end diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua index 825b024..c5999f6 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state.lua @@ -14,20 +14,35 @@ local Ast = class.declare 'LuaParser.Ast' ---@alias LuaParser.Node.State ---| LuaParser.Node.LocalDef ----| LuaParser.Node.ExpAsState +---| LuaParser.Node.StateStartWithExp +---| LuaParser.Node.Label +---| LuaParser.Node.Goto ---@return LuaParser.Node.State? function Ast:parseState() - return self:parseLocal() - or self:parseStateStartWithExp() + local token = self.lexer:peek() + + if token == 'local' then + return self:parseLocal() + end + + if token == '::' then + return self:parseLabel() + end + + if token == 'goto' then + return self:parseGoto() + end + + return self:parseStateStartWithExp() end ----@alias LuaParser.Node.ExpAsState +---@alias LuaParser.Node.StateStartWithExp ---| LuaParser.Node.Call ---| LuaParser.Node.Assign ---| LuaParser.Node.SingleExp ----@return LuaParser.Node.ExpAsState? +---@return LuaParser.Node.StateStartWithExp? function Ast:parseStateStartWithExp() local exp = self:parseExp(false, true) if not exp then diff --git a/test/ast/state.lua b/test/ast/state.lua index 90606b9..f704f14 100644 --- a/test/ast/state.lua +++ b/test/ast/state.lua @@ -130,3 +130,27 @@ TEST [[x.y().z = 1]] } } } + +TEST [[:: continue ::]] +{ + __class__ = 'LuaParser.Node.Label', + start = 0, + finish = 14, + label = { + start = 3, + finish = 11, + id = 'continue', + } +} + +TEST [[goto continue]] +{ + __class__ = 'LuaParser.Node.Goto', + start = 0, + finish = 13, + label = { + start = 5, + finish = 13, + id = 'continue', + } +} From 5bca6194333dab9d2c957ba2582ff0b9d4560166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 20 Sep 2023 17:41:21 +0800 Subject: [PATCH 33/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=20do?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 2 ++ src/parser/ast/block.lua | 22 ++-------------------- src/parser/ast/do.lua | 30 ++++++++++++++++++++++++++++++ src/parser/ast/function.lua | 30 +++++++++++++----------------- src/parser/ast/if.lua | 11 +++++++++++ src/parser/ast/state.lua | 12 ++++++++++++ test/ast/state.lua | 10 ++++++++++ 7 files changed, 80 insertions(+), 37 deletions(-) create mode 100644 src/parser/ast/do.lua create mode 100644 src/parser/ast/if.lua diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 83f2642..97bce1d 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -21,6 +21,8 @@ require 'parser.ast.field' require 'parser.ast.unary' require 'parser.ast.binary' require 'parser.ast.label' +require 'parser.ast.do' +require 'parser.ast.if' ---@class LuaParser.Ast ---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast diff --git a/src/parser/ast/block.lua b/src/parser/ast/block.lua index f557b96..28caf57 100644 --- a/src/parser/ast/block.lua +++ b/src/parser/ast/block.lua @@ -4,24 +4,6 @@ local class = require 'class' ---@field childs LuaParser.Node.State[] local Block = class.declare('LuaParser.Node.Block', 'LuaParser.Node.Base') ----@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' - ----@return LuaParser.Node.Block -function Ast:parseBlock() - local childs = {} - - local block = class.new('LuaParser.Node.Block', { - ast = self, - start = self:getLastPos(), - finish = self:getLastPos(), - childs = childs, - }) - - for i = 1, #childs do - local child = childs[i] - child.parent = block - end - - return block +function Block:parseChilds() + self.childs = {} end diff --git a/src/parser/ast/do.lua b/src/parser/ast/do.lua new file mode 100644 index 0000000..5f8d54f --- /dev/null +++ b/src/parser/ast/do.lua @@ -0,0 +1,30 @@ +local class = require 'class' + +---@class LuaParser.Node.Do: LuaParser.Node.Block +local Do = class.declare('LuaParser.Node.Do', 'LuaParser.Node.Block') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@return LuaParser.Node.Do? +function Ast:parseDo() + local pos = self.lexer:consume 'do' + if not pos then + return nil + end + + local doNode = class.new('LuaParser.Node.Do', { + ast = self, + start = pos, + }) + + self:skipSpace() + doNode:parseChilds() + + self:skipSpace() + self:assertSymbol 'end' + + doNode.finish = self:getLastPos() + + return doNode +end diff --git a/src/parser/ast/function.lua b/src/parser/ast/function.lua index 00195d0..90ff8b7 100644 --- a/src/parser/ast/function.lua +++ b/src/parser/ast/function.lua @@ -10,14 +10,13 @@ local Param = class.declare('LuaParser.Node.Param', 'LuaParser.Node.Base') ---@field parent LuaParser.Node.Function local FuncName = class.declare('LuaParser.Node.FuncName', 'LuaParser.Node.Base') ----@class LuaParser.Node.Function: LuaParser.Node.Base +---@class LuaParser.Node.Function: LuaParser.Node.Block ---@field name? LuaParser.Node.Term ---@field params? LuaParser.Node.Local[] ---@field symbolPos1? integer # 左括号 ---@field symbolPos2? integer # 右括号 ---@field symbolPos3? integer # `end` ----@field block? LuaParser.Node.Block -local Function = class.declare('LuaParser.Node.Function', 'LuaParser.Node.Base') +local Function = class.declare('LuaParser.Node.Function', 'LuaParser.Node.Block') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' @@ -49,25 +48,26 @@ function Ast:parseFunction() self:skipSpace() local symbolPos2 = self:assertSymbol ')' - local block - if symbolPos2 then - block = self:parseBlock() - end - - self:skipSpace() - local symbolPos3 = self:assertSymbol 'end' - local func = class.new('LuaParser.Node.Function', { ast = self, start = pos, - finish = self:getLastPos(), name = name, params = params, symbolPos1 = symbolPos1, symbolPos2 = symbolPos2, - symbolPos3 = symbolPos3, }) + if symbolPos2 then + self:skipSpace() + func:parseChilds() + end + + self:skipSpace() + local symbolPos3 = self:assertSymbol 'end' + + func.symbolPos3 = symbolPos3 + func.finish = self:getLastPos() + if name then name.parent = func end @@ -80,10 +80,6 @@ function Ast:parseFunction() end end - if block then - block.parent = func - end - return func end diff --git a/src/parser/ast/if.lua b/src/parser/ast/if.lua new file mode 100644 index 0000000..7b64d24 --- /dev/null +++ b/src/parser/ast/if.lua @@ -0,0 +1,11 @@ +local class = require 'class' + +---@class LuaParser.Node.If: LuaParser.Node.Base +local If = class.declare('LuaParser.Node.If', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@return LuaParser.Node.If? +function Ast:parseIf() +end diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua index c5999f6..b24067a 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state.lua @@ -17,6 +17,8 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.StateStartWithExp ---| LuaParser.Node.Label ---| LuaParser.Node.Goto +---| LuaParser.Node.If +---| LuaParser.Node.Do ---@return LuaParser.Node.State? function Ast:parseState() @@ -26,6 +28,16 @@ function Ast:parseState() return self:parseLocal() end + if token == 'if' + or token == 'elseif' + or token == 'else' then + return self:parseIf() + end + + if token == 'do' then + return self:parseDo() + end + if token == '::' then return self:parseLabel() end diff --git a/test/ast/state.lua b/test/ast/state.lua index f704f14..f733eb1 100644 --- a/test/ast/state.lua +++ b/test/ast/state.lua @@ -154,3 +154,13 @@ TEST [[goto continue]] id = 'continue', } } + +TEST [[ +do +end +]] +{ + __class__ = 'LuaParser.Node.Do', + left = 0, + right = 10003, +} From c00c9bb6d096e03510937800150ccf458d3e3ff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 20 Sep 2023 18:25:40 +0800 Subject: [PATCH 34/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=20if?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/base.lua | 14 ++++- src/parser/ast/block.lua | 13 +++- src/parser/ast/do.lua | 2 +- src/parser/ast/function.lua | 2 +- src/parser/ast/if.lua | 122 ++++++++++++++++++++++++++++++++++++ src/parser/ast/state.lua | 6 +- test/ast/state.lua | 50 ++++++++++++--- 7 files changed, 190 insertions(+), 19 deletions(-) diff --git a/src/parser/ast/base.lua b/src/parser/ast/base.lua index 47215c5..08104d3 100644 --- a/src/parser/ast/base.lua +++ b/src/parser/ast/base.lua @@ -1,6 +1,7 @@ local class = require 'class' ---@class LuaParser.Node.Base: Class.Base +---@field type string ---@field ast LuaParser.Ast ---@field start integer # 开始位置(偏移) ---@field finish integer # 结束位置(偏移) @@ -27,6 +28,13 @@ local Base = class.declare 'LuaParser.Node.Base' local rowcolMulti = 10000 +---@param self LuaParser.Node.Base +---@return string +---@return true +Base.__getter.type = function (self) + return class.type(self):match '[^.]+$', true +end + ---@param self LuaParser.Node.Base ---@return integer ---@return true @@ -89,7 +97,7 @@ end ---@return string ---@return true Base.__getter.parent = function (self) - error('未设置父节点:' .. class.type(self)) + error('未设置父节点:' .. self.type) end ---@param self LuaParser.Node.Base @@ -97,7 +105,7 @@ end ---@return true Base.__getter.parentBlock = function (self) local parent = self.parent - if class.type(parent) == 'LuaParser.Node.Block' then + if parent.isBlock then return parent, true end return parent.parentBlock, true @@ -108,7 +116,7 @@ end ---@return true Base.__getter.parentFunction = function (self) local parent = self.parentFunction - if class.type(parent) == 'LuaParser.Node.Function' then + if parent.type == 'function' then return parent, true end return parent.parentFunction, true diff --git a/src/parser/ast/block.lua b/src/parser/ast/block.lua index 28caf57..7a28532 100644 --- a/src/parser/ast/block.lua +++ b/src/parser/ast/block.lua @@ -3,7 +3,16 @@ local class = require 'class' ---@class LuaParser.Node.Block: LuaParser.Node.Base ---@field childs LuaParser.Node.State[] local Block = class.declare('LuaParser.Node.Block', 'LuaParser.Node.Base') +Block.isBlock = true -function Block:parseChilds() - self.childs = {} +Block.__getter.childs = function () + return {}, true +end + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@param block LuaParser.Node.Block +function Ast:parseBlockChilds(block) + end diff --git a/src/parser/ast/do.lua b/src/parser/ast/do.lua index 5f8d54f..38bd0ad 100644 --- a/src/parser/ast/do.lua +++ b/src/parser/ast/do.lua @@ -19,7 +19,7 @@ function Ast:parseDo() }) self:skipSpace() - doNode:parseChilds() + self:parseBlockChilds(doNode) self:skipSpace() self:assertSymbol 'end' diff --git a/src/parser/ast/function.lua b/src/parser/ast/function.lua index 90ff8b7..0982fed 100644 --- a/src/parser/ast/function.lua +++ b/src/parser/ast/function.lua @@ -59,7 +59,7 @@ function Ast:parseFunction() if symbolPos2 then self:skipSpace() - func:parseChilds() + self:parseBlockChilds(func) end self:skipSpace() diff --git a/src/parser/ast/if.lua b/src/parser/ast/if.lua index 7b64d24..6ceecd0 100644 --- a/src/parser/ast/if.lua +++ b/src/parser/ast/if.lua @@ -1,11 +1,133 @@ local class = require 'class' ---@class LuaParser.Node.If: LuaParser.Node.Base +---@field childs LuaParser.Node.IfChild[] local If = class.declare('LuaParser.Node.If', 'LuaParser.Node.Base') +---@class LuaParser.Node.IfChild: LuaParser.Node.Block +---@field subtype 'if' | 'elseif' | 'else' +---@field condition? LuaParser.Node.Exp +---@field symbolPos? integer # then 的位置 +local IfChild = class.declare('LuaParser.Node.IfChild', 'LuaParser.Node.Block') + ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' ---@return LuaParser.Node.If? function Ast:parseIf() + local token, _, pos = self.lexer:peek() + if token ~= 'if' then + return nil + end + + local ifNode = class.new('LuaParser.Node.If', { + ast = self, + start = pos, + childs = {}, + }) + + while true do + local child = self:parseIfChild() + if not child then + break + end + child.parent = ifNode + ifNode.childs[#ifNode.childs+1] = child + end + + self:skipSpace() + self:assertSymbol 'end' + + ifNode.finish = self:getLastPos() + + return ifNode +end + +---@return LuaParser.Node.IfChild? +function Ast:parseIfChild() + return self:parseIfChildIf() + or self:parseIfChildElseIf() + or self:parseIfChildElse() +end + +---@return LuaParser.Node.IfChild? +function Ast:parseIfChildIf() + local pos = self.lexer:consume 'if' + if not pos then + return nil + end + + self:skipSpace() + local condition = self:parseExp() + + local node = class.new('LuaParser.Node.IfChild', { + ast = self, + subtype = 'if', + start = pos, + condition = condition, + }) + + if condition then + self:skipSpace() + node.symbolPos = self:assertSymbol 'then' + + self:skipSpace() + self:parseBlockChilds(node) + end + + node.finish = self:getLastPos() + + return node +end + +---@return LuaParser.Node.IfChild? +function Ast:parseIfChildElseIf() + local pos = self.lexer:consume 'elseif' + if not pos then + return nil + end + + self:skipSpace() + local condition = self:parseExp() + + local node = class.new('LuaParser.Node.IfChild', { + ast = self, + subtype = 'elseif', + start = pos, + condition = condition, + }) + + if condition then + self:skipSpace() + node.symbolPos = self:assertSymbol 'then' + + self:skipSpace() + self:parseBlockChilds(node) + end + + node.finish = self:getLastPos() + + return node +end + +---@return LuaParser.Node.IfChild? +function Ast:parseIfChildElse() + local pos = self.lexer:consume 'else' + if not pos then + return nil + end + + self:skipSpace() + + local node = class.new('LuaParser.Node.IfChild', { + ast = self, + subtype = 'else', + start = pos, + }) + + self:parseBlockChilds(node) + + node.finish = self:getLastPos() + + return node end diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua index b24067a..c810695 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state.lua @@ -61,13 +61,13 @@ function Ast:parseStateStartWithExp() return nil end - if class.type(exp) == 'LuaParser.Node.Call' then + if exp.type == 'Call' then ---@cast exp LuaParser.Node.Call return exp end - if class.type(exp) == 'LuaParser.Node.Var' - or class.type(exp) == 'LuaParser.Node.Field' then + if exp.type == 'Var' + or exp.type == 'Field' then ---@cast exp LuaParser.Node.Field self:skipSpace() local assign = self:parseAssign(exp) diff --git a/test/ast/state.lua b/test/ast/state.lua index f733eb1..06842b9 100644 --- a/test/ast/state.lua +++ b/test/ast/state.lua @@ -11,7 +11,7 @@ end TEST [[local x, y = 1, 2, 3]] { - __class__ = 'LuaParser.Node.LocalDef', + type = 'LocalDef', start = 0, finish = 20, vars = { @@ -43,7 +43,7 @@ TEST [[local x, y = 1, 2, 3]] TEST [[x = 1]] { - __class__ = 'LuaParser.Node.Assign', + type = 'Assign', start = 0, finish = 5, exps = { @@ -65,7 +65,7 @@ TEST [[x = 1]] TEST [[x.x, y.y = 1, 2, 3]] { - __class__ = 'LuaParser.Node.Assign', + type = 'Assign', exps = { [1] = { subtype = 'field', @@ -104,17 +104,17 @@ TEST [[x.x, y.y = 1, 2, 3]] TEST [[x.y()]] { - __class__ = 'LuaParser.Node.Call', + type = 'Call', } TEST [[x.y().z = 1]] { - __class__ = 'LuaParser.Node.Assign', + type = 'Assign', exps = { [1] = { subtype = 'field', last = { - __class__ = 'LuaParser.Node.Call', + type = 'Call', }, key = { id = 'z', @@ -133,7 +133,7 @@ TEST [[x.y().z = 1]] TEST [[:: continue ::]] { - __class__ = 'LuaParser.Node.Label', + type = 'Label', start = 0, finish = 14, label = { @@ -145,7 +145,7 @@ TEST [[:: continue ::]] TEST [[goto continue]] { - __class__ = 'LuaParser.Node.Goto', + type = 'Goto', start = 0, finish = 13, label = { @@ -160,7 +160,39 @@ do end ]] { - __class__ = 'LuaParser.Node.Do', + type = 'Do', left = 0, right = 10003, } + +TEST [[ +if x then +elseif y then +else +end +]] +{ + type = 'If', + left = 0, + right = 30003, + childs = { + [1] = { + type = 'IfChild', + subtype = 'if', + condition = { + id = 'x', + }, + }, + [2] = { + type = 'IfChild', + subtype = 'elseif', + condition = { + id = 'y', + }, + }, + [3] = { + type = 'IfChild', + subtype = 'else', + }, + } +} From 6efbd5761a15896a19e909a9b2f01c97dea89a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 20 Sep 2023 18:38:07 +0800 Subject: [PATCH 35/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=20break=20=E5=92=8C=20?= =?UTF-8?q?continue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 1 + src/parser/ast/break.lua | 41 ++++++++++++++++++++++++++++++++++++++++ src/parser/ast/state.lua | 10 ++++++++++ test/ast/state.lua | 7 +++++++ 4 files changed, 59 insertions(+) create mode 100644 src/parser/ast/break.lua diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 97bce1d..0d4c4b3 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -23,6 +23,7 @@ require 'parser.ast.binary' require 'parser.ast.label' require 'parser.ast.do' require 'parser.ast.if' +require 'parser.ast.break' ---@class LuaParser.Ast ---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast diff --git a/src/parser/ast/break.lua b/src/parser/ast/break.lua new file mode 100644 index 0000000..640d873 --- /dev/null +++ b/src/parser/ast/break.lua @@ -0,0 +1,41 @@ +local class = require 'class' + +---@class LuaParser.Node.Break: LuaParser.Node.Base +local Break = class.declare('LuaParser.Node.Break', 'LuaParser.Node.Base') + +---@class LuaParser.Node.Continue: LuaParser.Node.Base +local Continue = class.declare('LuaParser.Node.Continue', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@return LuaParser.Node.Break? +function Ast:parseBreak() + local pos = self.lexer:consume 'break' + if not pos then + return + end + + return class.new('LuaParser.Node.Break', { + ast = self, + start = pos, + finish = self:getLastPos(), + }) +end + +---@return LuaParser.Node.Continue? +function Ast:parseContinue() + if not self.nssymbolMap['continue'] then + return nil + end + local pos = self.lexer:consume 'continue' + if not pos then + return + end + + return class.new('LuaParser.Node.Continue', { + ast = self, + start = pos, + finish = self:getLastPos(), + }) +end diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua index c810695..9b561ae 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state.lua @@ -19,6 +19,8 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.Goto ---| LuaParser.Node.If ---| LuaParser.Node.Do +---| LuaParser.Node.Break +---| LuaParser.Node.Continue ---@return LuaParser.Node.State? function Ast:parseState() @@ -38,6 +40,14 @@ function Ast:parseState() return self:parseDo() end + if token == 'break' then + return self:parseBreak() + end + + if token == 'continue' then + return self:parseContinue() + end + if token == '::' then return self:parseLabel() end diff --git a/test/ast/state.lua b/test/ast/state.lua index 06842b9..aacae9e 100644 --- a/test/ast/state.lua +++ b/test/ast/state.lua @@ -196,3 +196,10 @@ end }, } } + +TEST [[break]] +{ + type = 'Break', + start = 0, + finish = 5, +} From 20c6d7edefc4d159ade5afb5ee8a388c1b883fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 21 Sep 2023 00:04:33 +0800 Subject: [PATCH 36/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90return?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 1 + src/parser/ast/base.lua | 1 + src/parser/ast/return.lua | 31 +++++++++++++++++++++++++++++++ src/parser/ast/state.lua | 5 +++++ test/ast/state.lua | 31 +++++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+) create mode 100644 src/parser/ast/return.lua diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 0d4c4b3..930be08 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -24,6 +24,7 @@ require 'parser.ast.label' require 'parser.ast.do' require 'parser.ast.if' require 'parser.ast.break' +require 'parser.ast.return' ---@class LuaParser.Ast ---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast diff --git a/src/parser/ast/base.lua b/src/parser/ast/base.lua index 08104d3..34d39e0 100644 --- a/src/parser/ast/base.lua +++ b/src/parser/ast/base.lua @@ -24,6 +24,7 @@ local class = require 'class' ---@field toInteger? integer ---@field isTruly? boolean ---@field dummy? boolean +---@field index? integer local Base = class.declare 'LuaParser.Node.Base' local rowcolMulti = 10000 diff --git a/src/parser/ast/return.lua b/src/parser/ast/return.lua new file mode 100644 index 0000000..1d9859c --- /dev/null +++ b/src/parser/ast/return.lua @@ -0,0 +1,31 @@ +local class = require 'class' + +---@class LuaParser.Node.Return: LuaParser.Node.Base +---@field exps LuaParser.Node.Exp[] +local Return = class.declare('LuaParser.Node.Return', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@return LuaParser.Node.Return? +function Ast:parseReturn() + local pos = self.lexer:consume 'return' + if not pos then + return nil + end + + local exps = self:parseExpList() + + local ret = class.new('LuaParser.Node.Return', { + start = pos, + finish = self:getLastPos(), + exps = exps, + }) + + for i = 1, #exps do + local exp = exps[i] + exp.parent = ret + end + + return ret +end diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua index 9b561ae..95cb1cb 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state.lua @@ -21,6 +21,7 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.Do ---| LuaParser.Node.Break ---| LuaParser.Node.Continue +---| LuaParser.Node.Return ---@return LuaParser.Node.State? function Ast:parseState() @@ -44,6 +45,10 @@ function Ast:parseState() return self:parseBreak() end + if token == 'return' then + return self:parseReturn() + end + if token == 'continue' then return self:parseContinue() end diff --git a/test/ast/state.lua b/test/ast/state.lua index aacae9e..1ca180b 100644 --- a/test/ast/state.lua +++ b/test/ast/state.lua @@ -203,3 +203,34 @@ TEST [[break]] start = 0, finish = 5, } + +TEST [[return]] +{ + type = 'Return', + start = 0, + finish = 6, +} + +TEST [[return 1, 2, 3]] +{ + type = 'Return', + start = 0, + finish = 14, + exps = { + [1] = { + start = 7, + finish = 8, + value = 1, + }, + [2] = { + start = 10, + finish = 11, + value = 2, + }, + [3] = { + start = 13, + finish = 14, + value = 3, + } + } +} From 0f177a066f00e4c31f5eaf904287d34e73750fb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 21 Sep 2023 00:40:21 +0800 Subject: [PATCH 37/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=20for?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 1 + src/parser/ast/for.lua | 79 ++++++++++++++++++++++++++++++++++++++++ src/parser/ast/local.lua | 7 +++- src/parser/ast/state.lua | 7 ++++ test/ast/state.lua | 67 ++++++++++++++++++++++++++++++++++ 5 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 src/parser/ast/for.lua diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 930be08..3b5b0de 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -25,6 +25,7 @@ require 'parser.ast.do' require 'parser.ast.if' require 'parser.ast.break' require 'parser.ast.return' +require 'parser.ast.for' ---@class LuaParser.Ast ---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast diff --git a/src/parser/ast/for.lua b/src/parser/ast/for.lua new file mode 100644 index 0000000..ed54444 --- /dev/null +++ b/src/parser/ast/for.lua @@ -0,0 +1,79 @@ +local class = require 'class' + +---@class LuaParser.Node.For: LuaParser.Node.Block +---@field subtype 'loop' | 'in' | 'incomplete' +---@field vars LuaParser.Node.Local[] +---@field exps LuaParser.Node.Exp[] +---@field symbolPos1? integer # in 或 = 的位置 +---@field symbolPos2? integer # do 的位置 +---@field symbolPos3? integer # end 的位置 +local For = class.declare('LuaParser.Node.For', 'LuaParser.Node.Block') + +For.subtype = 'incomplete' +For.vars = {} +For.exps = {} + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@return LuaParser.Node.For? +function Ast:parseFor() + local pos = self.lexer:consume 'for' + if not pos then + return nil + end + + local forNode = class.new('LuaParser.Node.For', { + ast = self, + start = pos, + }) + + self:skipSpace() + local vars = self:parseIDList('LuaParser.Node.Local', true) + forNode.vars = vars + for i = 1, #vars do + local var = vars[i] + var.parent = forNode + var.index = i + end + + self:skipSpace() + local token, _, symbolPos = self.lexer:peek() + + forNode.symbolPos1 = symbolPos + if token == '=' then + forNode.subtype = 'loop' + elseif token == 'in' then + forNode.subtype = 'in' + else + return forNode + end + self.lexer:next() + + self:skipSpace() + local exps = self:parseExpList(true) + forNode.exps = exps + for i = 1, #exps do + local exp = exps[i] + exp.parent = forNode + exp.index = i + end + + self:skipSpace() + local symbolPos2 = self:assertSymbol 'do' + + if symbolPos2 then + forNode.symbolPos2 = symbolPos2 + + self:skipSpace() + self:parseBlockChilds(forNode) + + self:skipSpace() + local symbolPos3 = self:assertSymbol 'end' + forNode.symbolPos3 = symbolPos3 + end + + forNode.finish = self:getLastPos() + + return forNode +end diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index d06ac5c..fbd0a4d 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -2,12 +2,13 @@ local class = require 'class' ---@class LuaParser.Node.LocalDef: LuaParser.Node.Base ---@field vars LuaParser.Node.Local[] +---@field symbolPos? integer # 等号的位置 ---@field values? LuaParser.Node.Exp[] local LocalDef = class.declare('LuaParser.Node.LocalDef', 'LuaParser.Node.Base') ---@class LuaParser.Node.Local: LuaParser.Node.Base ---@field id string ----@field parent LuaParser.Node.LocalDef +---@field parent LuaParser.Node.LocalDef | LuaParser.Node.For ---@field index integer ---@field value? LuaParser.Node.Exp ---@field refs? LuaParser.Node.Var[] @@ -39,7 +40,9 @@ function Ast:parseLocal() end self:skipSpace() - if self.lexer:consume '=' then + local symbolPos = self:assertSymbol '=' + if symbolPos then + localdef.symbolPos = symbolPos self:skipSpace() local values = self:parseExpList(true) localdef.values = values diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua index 95cb1cb..487b335 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state.lua @@ -1,6 +1,7 @@ local class = require 'class' ---@class LuaParser.Node.Assign: LuaParser.Node.Base +---@field symbolPos? integer # 等号的位置 ---@field exps LuaParser.Node.Exp[] ---@field values LuaParser.Node.Var[] local Assign = class.declare('LuaParser.Node.Assign', 'LuaParser.Node.Base') @@ -22,6 +23,7 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.Break ---| LuaParser.Node.Continue ---| LuaParser.Node.Return +---| LuaParser.Node.For ---@return LuaParser.Node.State? function Ast:parseState() @@ -49,6 +51,10 @@ function Ast:parseState() return self:parseReturn() end + if token == 'for' then + return self:parseFor() + end + if token == 'continue' then return self:parseContinue() end @@ -133,6 +139,7 @@ function Ast:parseAssign(first) local eqPos = self:assertSymbol '=' if eqPos then + assign.symbolPos = eqPos self:skipSpace() local values = self:parseExpList(true) assign.values = values diff --git a/test/ast/state.lua b/test/ast/state.lua index 1ca180b..42f9885 100644 --- a/test/ast/state.lua +++ b/test/ast/state.lua @@ -14,6 +14,7 @@ TEST [[local x, y = 1, 2, 3]] type = 'LocalDef', start = 0, finish = 20, + symbolPos = 11, vars = { [1] = { id = 'x', @@ -46,6 +47,7 @@ TEST [[x = 1]] type = 'Assign', start = 0, finish = 5, + symbolPos = 2, exps = { [1] = { start = 0, @@ -66,6 +68,7 @@ TEST [[x = 1]] TEST [[x.x, y.y = 1, 2, 3]] { type = 'Assign', + symbolPos = 9, exps = { [1] = { subtype = 'field', @@ -110,6 +113,7 @@ TEST [[x.y()]] TEST [[x.y().z = 1]] { type = 'Assign', + symbolPos = 8, exps = { [1] = { subtype = 'field', @@ -234,3 +238,66 @@ TEST [[return 1, 2, 3]] } } } + +TEST [[ +for i = 1, 10, 1 do +end +]] +{ + type = 'For', + subtype= 'loop', + left = 0, + right = 10003, + vars = { + [1] = { + id = 'i', + }, + }, + exps = { + [1] = { + value = 1, + }, + [2] = { + value = 10, + }, + [3] = { + value = 1, + } + }, + symbolPos1 = 6, + symbolPos2 = 17, + symbolPos3 = 20, +} + +TEST [[ +for k, v in next, t, nil do +end +]] +{ + type = 'For', + subtype= 'in', + left = 0, + right = 10003, + vars = { + [1] = { + id = 'k', + }, + [2] = { + id = 'v', + }, + }, + exps = { + [1] = { + id = 'next', + }, + [2] = { + id = 't', + }, + [3] = { + type = 'Nil', + } + }, + symbolPos1 = 9, + symbolPos2 = 25, + symbolPos3 = 28, +} From be28d036480656624f35e17c7fb534e11296b78c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 21 Sep 2023 00:47:40 +0800 Subject: [PATCH 38/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=20while?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 1 + src/parser/ast/state.lua | 5 +++++ src/parser/ast/while.lua | 48 ++++++++++++++++++++++++++++++++++++++++ test/ast/state.lua | 15 +++++++++++++ 4 files changed, 69 insertions(+) create mode 100644 src/parser/ast/while.lua diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 3b5b0de..4afaeee 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -26,6 +26,7 @@ require 'parser.ast.if' require 'parser.ast.break' require 'parser.ast.return' require 'parser.ast.for' +require 'parser.ast.while' ---@class LuaParser.Ast ---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua index 487b335..0426a96 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state.lua @@ -24,6 +24,7 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.Continue ---| LuaParser.Node.Return ---| LuaParser.Node.For +---| LuaParser.Node.While ---@return LuaParser.Node.State? function Ast:parseState() @@ -55,6 +56,10 @@ function Ast:parseState() return self:parseFor() end + if token == 'while' then + return self:parseWhile() + end + if token == 'continue' then return self:parseContinue() end diff --git a/src/parser/ast/while.lua b/src/parser/ast/while.lua new file mode 100644 index 0000000..a59f34a --- /dev/null +++ b/src/parser/ast/while.lua @@ -0,0 +1,48 @@ +local class = require 'class' + +---@class LuaParser.Node.While: LuaParser.Node.Block +---@field condition LuaParser.Node.Exp +---@field symbolPos1? integer # do 的位置 +---@field symbolPos2? integer # end 的位置 +local While = class.declare('LuaParser.Node.While', 'LuaParser.Node.Block') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@return LuaParser.Node.While? +function Ast:parseWhile() + local pos = self.lexer:consume 'while' + if not pos then + return nil + end + + self:skipSpace() + local condition = self:parseExp(true) + if not condition then + return nil + end + + local whileNode = class.new('LuaParser.Node.While', { + ast = self, + start = pos, + condition = condition, + }) + condition.parent = whileNode + + self:skipSpace() + local symbolPos1 = self:assertSymbol 'do' + if symbolPos1 then + whileNode.symbolPos1 = symbolPos1 + + self:skipSpace() + self:parseBlockChilds(whileNode) + + self:skipSpace() + local symbolPos2 = self:assertSymbol 'end' + whileNode.symbolPos2 = symbolPos2 + end + + whileNode.finish = self:getLastPos() + + return whileNode +end diff --git a/test/ast/state.lua b/test/ast/state.lua index 42f9885..9cbfc70 100644 --- a/test/ast/state.lua +++ b/test/ast/state.lua @@ -301,3 +301,18 @@ end symbolPos2 = 25, symbolPos3 = 28, } + +TEST [[ +while true do +end +]] +{ + type = 'While', + left = 0, + right = 10003, + condition = { + value = true, + }, + symbolPos1 = 11, + symbolPos2 = 14, +} From 35faaec8724f0307ddea6df2c1948dfcc7fea609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 21 Sep 2023 00:59:08 +0800 Subject: [PATCH 39/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=20repeat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 5 ++++- src/parser/ast/repeat.lua | 42 +++++++++++++++++++++++++++++++++++++++ src/parser/ast/state.lua | 5 +++++ test/ast/state.lua | 14 +++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 src/parser/ast/repeat.lua diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 4afaeee..eba7e0b 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -27,6 +27,7 @@ require 'parser.ast.break' require 'parser.ast.return' require 'parser.ast.for' require 'parser.ast.while' +require 'parser.ast.repeat' ---@class LuaParser.Ast ---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast @@ -112,7 +113,9 @@ end ---@private ---@param inExp? boolean # 在表达式中 function M:skipSpace(inExp) - self.lastRightCI = self.lexer.ci + if self.lexer.ci ~= self.lastSpaceCI then + self.lastRightCI = self.lexer.ci + end repeat until not self:skipNL() and not self:skipComment(inExp) and not self:skipUnknown() diff --git a/src/parser/ast/repeat.lua b/src/parser/ast/repeat.lua new file mode 100644 index 0000000..1a5d0a9 --- /dev/null +++ b/src/parser/ast/repeat.lua @@ -0,0 +1,42 @@ +local class = require 'class' + +---@class LuaParser.Node.Repeat: LuaParser.Node.Block +---@field condition? LuaParser.Node.Exp +---@field symbolPos? integer # until 的位置 +local Repeat = class.declare('LuaParser.Node.Repeat', 'LuaParser.Node.Block') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@return LuaParser.Node.Repeat? +function Ast:parseRepeat() + local pos = self.lexer:consume 'repeat' + if not pos then + return nil + end + + local repeatNode = class.new('LuaParser.Node.Repeat', { + ast = self, + start = pos, + }) + + self:skipSpace() + self:parseBlockChilds(repeatNode) + + self:skipSpace() + local symbolPos = self:assertSymbol 'until' + if symbolPos then + repeatNode.symbolPos = symbolPos + + self:skipSpace() + local condition = self:parseExp(true) + if condition then + repeatNode.condition = condition + condition.parent = repeatNode + end + end + + repeatNode.finish = self:getLastPos() + + return repeatNode +end diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua index 0426a96..6910169 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state.lua @@ -25,6 +25,7 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.Return ---| LuaParser.Node.For ---| LuaParser.Node.While +---| LuaParser.Node.Repeat ---@return LuaParser.Node.State? function Ast:parseState() @@ -72,6 +73,10 @@ function Ast:parseState() return self:parseGoto() end + if token == 'repeat' then + return self:parseRepeat() + end + return self:parseStateStartWithExp() end diff --git a/test/ast/state.lua b/test/ast/state.lua index 9cbfc70..cbec239 100644 --- a/test/ast/state.lua +++ b/test/ast/state.lua @@ -316,3 +316,17 @@ end symbolPos1 = 11, symbolPos2 = 14, } + +TEST [[ +repeat +until false +]] +{ + type = 'Repeat', + left = 0, + right = 10011, + symbolPos = 7, + condition = { + value = false, + }, +} From ee15a7208525cfba1116874e5ab672be0dc183a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 21 Sep 2023 01:46:24 +0800 Subject: [PATCH 40/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/function.lua | 31 +++++++++++++++++++++----- src/parser/ast/state.lua | 5 +++++ test/ast/state.lua | 44 +++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 5 deletions(-) diff --git a/src/parser/ast/function.lua b/src/parser/ast/function.lua index 0982fed..eb8b60b 100644 --- a/src/parser/ast/function.lua +++ b/src/parser/ast/function.lua @@ -6,12 +6,12 @@ local class = require 'class' ---@field id string local Param = class.declare('LuaParser.Node.Param', 'LuaParser.Node.Base') ----@class LuaParser.Node.FuncName: LuaParser.Node.Base ----@field parent LuaParser.Node.Function -local FuncName = class.declare('LuaParser.Node.FuncName', 'LuaParser.Node.Base') +---@alias LuaParser.Node.FuncName +---| LuaParser.Node.Var +---| LuaParser.Node.Field ---@class LuaParser.Node.Function: LuaParser.Node.Block ----@field name? LuaParser.Node.Term +---@field name? LuaParser.Node.FuncName ---@field params? LuaParser.Node.Local[] ---@field symbolPos1? integer # 左括号 ---@field symbolPos2? integer # 右括号 @@ -85,7 +85,28 @@ end ---@return LuaParser.Node.FuncName? function Ast:parseFunctionName() - + local head = self:parseVar() + + if not head then + return nil + end + + ---@type LuaParser.Node.FuncName + local current = head + + while true do + self:skipSpace() + + local chain = self:parseField(current) + + if chain then + current = chain + else + break + end + end + + return current end ---@return LuaParser.Node.Param[] diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua index 6910169..69d9d47 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state.lua @@ -26,6 +26,7 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.For ---| LuaParser.Node.While ---| LuaParser.Node.Repeat +---| LuaParser.Node.Function ---@return LuaParser.Node.State? function Ast:parseState() @@ -61,6 +62,10 @@ function Ast:parseState() return self:parseWhile() end + if token == 'function' then + return self:parseFunction() + end + if token == 'continue' then return self:parseContinue() end diff --git a/test/ast/state.lua b/test/ast/state.lua index cbec239..d6f1a99 100644 --- a/test/ast/state.lua +++ b/test/ast/state.lua @@ -330,3 +330,47 @@ until false value = false, }, } + +TEST [[ +function f(x, y) +end +]] +{ + type = 'Function', + name = { + id = 'f' + }, + params = { + [1] = { + id = 'x' + }, + [2] = { + id = 'y' + } + } +} + +TEST [[ +function f.n(x, y) +end +]] +{ + type = 'Function', + name = { + type = 'Field', + last = { + id = 'f' + }, + key = { + id = 'n', + }, + }, + params = { + [1] = { + id = 'x' + }, + [2] = { + id = 'y' + } + } +} From 24e971af964b1d175b6e7f30f6f9d19c0a6e253c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 21 Sep 2023 15:03:31 +0800 Subject: [PATCH 41/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=20local=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/function.lua | 14 +- src/parser/ast/label.lua | 3 + src/parser/ast/local.lua | 10 +- test/ast/block.lua | 14 + test/ast/init.lua | 1 + test/ast/lua.lua | 1379 +++++++++++++++++++++++++++++++++++ test/ast/state.lua | 49 +- 7 files changed, 1463 insertions(+), 7 deletions(-) create mode 100644 test/ast/block.lua create mode 100644 test/ast/lua.lua diff --git a/src/parser/ast/function.lua b/src/parser/ast/function.lua index eb8b60b..93eab5b 100644 --- a/src/parser/ast/function.lua +++ b/src/parser/ast/function.lua @@ -9,6 +9,7 @@ local Param = class.declare('LuaParser.Node.Param', 'LuaParser.Node.Base') ---@alias LuaParser.Node.FuncName ---| LuaParser.Node.Var ---| LuaParser.Node.Field +---| LuaParser.Node.Local ---@class LuaParser.Node.Function: LuaParser.Node.Block ---@field name? LuaParser.Node.FuncName @@ -21,15 +22,21 @@ local Function = class.declare('LuaParser.Node.Function', 'LuaParser.Node.Block' ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@param isLocal? boolean ---@return LuaParser.Node.Function? -function Ast:parseFunction() +function Ast:parseFunction(isLocal) local pos = self.lexer:consume 'function' if not pos then return nil end self:skipSpace() - local name = self:parseFunctionName() + local name + if isLocal then + name = self:parseID('LuaParser.Node.Local', true) + else + name = self:parseFunctionName() + end self:skipSpace() local symbolPos1 = self.lexer:consume '(' @@ -70,6 +77,7 @@ function Ast:parseFunction() if name then name.parent = func + name.value = func end if params then @@ -91,7 +99,7 @@ function Ast:parseFunctionName() return nil end - ---@type LuaParser.Node.FuncName + ---@type LuaParser.Node.Var|LuaParser.Node.Field local current = head while true do diff --git a/src/parser/ast/label.lua b/src/parser/ast/label.lua index ac7a6ae..77e0686 100644 --- a/src/parser/ast/label.lua +++ b/src/parser/ast/label.lua @@ -48,6 +48,9 @@ end ---@return LuaParser.Node.Goto? function Ast:parseGoto() + if not self:isKeyWord 'goto' then + return nil + end local pos = self.lexer:consume 'goto' if not pos then return nil diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index fbd0a4d..7254fda 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -8,7 +8,7 @@ local LocalDef = class.declare('LuaParser.Node.LocalDef', 'LuaParser.Node.Base') ---@class LuaParser.Node.Local: LuaParser.Node.Base ---@field id string ----@field parent LuaParser.Node.LocalDef | LuaParser.Node.For +---@field parent LuaParser.Node.LocalDef | LuaParser.Node.For | LuaParser.Node.Function ---@field index integer ---@field value? LuaParser.Node.Exp ---@field refs? LuaParser.Node.Var[] @@ -17,12 +17,17 @@ local Local = class.declare('LuaParser.Node.Local', 'LuaParser.Node.Base') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' ----@return LuaParser.Node.LocalDef? +---@return (LuaParser.Node.LocalDef | LuaParser.Node.Function)? function Ast:parseLocal() local pos = self.lexer:consume 'local' if not pos then return nil end + self:skipSpace() + + if self.lexer:peek() == 'function' then + return self:parseFunction(true) + end local localdef = class.new('LuaParser.Node.LocalDef', { ast = self, @@ -30,7 +35,6 @@ function Ast:parseLocal() refs = {}, }) - self:skipSpace() local vars = self:parseIDList('LuaParser.Node.Local', true) localdef.vars = vars for i = 1, #vars do diff --git a/test/ast/block.lua b/test/ast/block.lua new file mode 100644 index 0000000..9bf8c85 --- /dev/null +++ b/test/ast/block.lua @@ -0,0 +1,14 @@ +local class = require 'class' + +local function TEST(code) + return function (expect) + local ast = class.new 'LuaParser.Ast' (code) + local node = ast:parseBoolean() + assert(node) + Match(node, expect) + end +end + +TEST [[ + +]] diff --git a/test/ast/init.lua b/test/ast/init.lua index a75cbb5..e456d56 100644 --- a/test/ast/init.lua +++ b/test/ast/init.lua @@ -21,3 +21,4 @@ require 'test.ast.local' require 'test.ast.table' require 'test.ast.exp' require 'test.ast.state' +require 'test.ast.block' diff --git a/test/ast/lua.lua b/test/ast/lua.lua new file mode 100644 index 0000000..e171b13 --- /dev/null +++ b/test/ast/lua.lua @@ -0,0 +1,1379 @@ +CHECK'' +{ + type = "main", + start = 0, + finish = 0, + locals = "", +} + +CHECK';;;' +{ + type = "main", + start = 0, + finish = 3, + locals = "", +} + +CHECK';;;x = 1' +{ + type = "main", + start = 0, + finish = 8, + locals = "", + [1] = { + type = "setglobal", + start = 3, + finish = 4, + range = 8, + parent = "", + node = "", + value = { + type = "integer", + start = 7, + finish = 8, + parent = "", + [1] = 1, + }, + [1] = "x", + }, +} +CHECK'x, y, z = 1, 2, 3' +{ + type = "main", + start = 0, + finish = 17, + locals = "", + [1] = { + type = "setglobal", + start = 0, + finish = 1, + range = 11, + parent = "", + node = "", + value = { + type = "integer", + start = 10, + finish = 11, + parent = "", + [1] = 1, + }, + [1] = "x", + }, + [2] = { + type = "setglobal", + start = 3, + finish = 4, + range = 14, + parent = "", + node = "", + value = { + type = "integer", + start = 13, + finish = 14, + parent = "", + [1] = 2, + }, + [1] = "y", + }, + [3] = { + type = "setglobal", + start = 6, + finish = 7, + range = 17, + parent = "", + node = "", + value = { + type = "integer", + start = 16, + finish = 17, + parent = "", + [1] = 3, + }, + [1] = "z", + }, +} +CHECK'local x, y, z' +{ + type = "main", + start = 0, + finish = 13, + locals = "", + [1] = { + type = "local", + start = 6, + finish = 7, + effect = 13, + parent = "", + locPos = 0, + [1] = "x", + }, + [2] = { + type = "local", + start = 9, + finish = 10, + effect = 13, + parent = "", + [1] = "y", + }, + [3] = { + type = "local", + start = 12, + finish = 13, + effect = 13, + parent = "", + [1] = "z", + }, +} +CHECK'local x, y, z = 1, 2, 3' +{ + type = "main", + start = 0, + finish = 23, + locals = "", + [1] = { + type = "local", + start = 6, + finish = 7, + effect = 23, + range = 17, + parent = "", + locPos = 0, + value = { + type = "integer", + start = 16, + finish = 17, + parent = "", + [1] = 1, + }, + [1] = "x", + }, + [2] = { + type = "local", + start = 9, + finish = 10, + effect = 23, + range = 20, + parent = "", + value = { + type = "integer", + start = 19, + finish = 20, + parent = "", + [1] = 2, + }, + [1] = "y", + }, + [3] = { + type = "local", + start = 12, + finish = 13, + effect = 23, + range = 23, + parent = "", + value = { + type = "integer", + start = 22, + finish = 23, + parent = "", + [1] = 3, + }, + [1] = "z", + }, +} +CHECK'local x, y = y, x' +{ + type = "main", + start = 0, + finish = 17, + locals = "", + [1] = { + type = "local", + start = 6, + finish = 7, + effect = 17, + range = 14, + parent = "", + locPos = 0, + value = { + type = "getglobal", + start = 13, + finish = 14, + parent = "", + node = "", + [1] = "y", + }, + [1] = "x", + }, + [2] = { + type = "local", + start = 9, + finish = 10, + effect = 17, + range = 17, + parent = "", + value = { + type = "getglobal", + start = 16, + finish = 17, + parent = "", + node = "", + [1] = "x", + }, + [1] = "y", + }, +} +CHECK'local x, y = f()' +{ + type = "main", + start = 0, + finish = 16, + locals = "", + [1] = { + type = "local", + start = 6, + finish = 7, + effect = 16, + range = 16, + parent = "", + locPos = 0, + value = { + type = "select", + start = 13, + finish = 16, + parent = "", + vararg = "", + sindex = 1, + }, + [1] = "x", + }, + [2] = { + type = "local", + start = 9, + finish = 10, + effect = 16, + range = 16, + parent = "", + value = { + type = "select", + start = 13, + finish = 16, + parent = "", + vararg = "", + sindex = 2, + }, + [1] = "y", + }, +} +CHECK'local x, y = (f())' +{ + type = "main", + start = 0, + finish = 18, + locals = "", + [1] = { + type = "local", + start = 6, + finish = 7, + effect = 18, + range = 18, + parent = "", + locPos = 0, + value = { + type = "paren", + start = 13, + finish = 18, + parent = "", + exp = { + type = "call", + start = 14, + finish = 17, + parent = "", + node = "", + }, + }, + [1] = "x", + }, + [2] = { + type = "local", + start = 9, + finish = 10, + effect = 18, + parent = "", + [1] = "y", + }, +} +CHECK'local x, y = f(), nil' +{ + type = "main", + start = 0, + finish = 21, + locals = "", + [1] = { + type = "local", + start = 6, + finish = 7, + effect = 21, + range = 16, + parent = "", + locPos = 0, + value = { + type = "select", + start = 13, + finish = 16, + parent = "", + vararg = "", + sindex = 1, + }, + [1] = "x", + }, + [2] = { + type = "local", + start = 9, + finish = 10, + effect = 21, + range = 21, + parent = "", + value = { + type = "nil", + start = 18, + finish = 21, + parent = "", + }, + [1] = "y", + }, +} +CHECK'local x, y = ...' +{ + type = "main", + start = 0, + finish = 16, + locals = "", + [1] = { + type = "local", + start = 6, + finish = 7, + effect = 16, + range = 16, + parent = "", + locPos = 0, + value = { + type = "select", + start = 13, + finish = 16, + parent = "", + vararg = "", + sindex = 1, + }, + [1] = "x", + }, + [2] = { + type = "local", + start = 9, + finish = 10, + effect = 16, + range = 16, + parent = "", + value = { + type = "select", + start = 13, + finish = 16, + parent = "", + vararg = "", + sindex = 2, + }, + [1] = "y", + }, +} +CHECK'local x, y = (...)' +{ + type = "main", + start = 0, + finish = 18, + locals = "", + [1] = { + type = "local", + start = 6, + finish = 7, + effect = 18, + range = 18, + parent = "", + locPos = 0, + value = { + type = "paren", + start = 13, + finish = 18, + parent = "", + exp = { + type = "varargs", + start = 14, + finish = 17, + parent = "", + }, + }, + [1] = "x", + }, + [2] = { + type = "local", + start = 9, + finish = 10, + effect = 18, + parent = "", + [1] = "y", + }, +} +CHECK'local x, y = ..., nil' +{ + type = "main", + start = 0, + finish = 21, + locals = "", + [1] = { + type = "local", + start = 6, + finish = 7, + effect = 21, + range = 16, + parent = "", + locPos = 0, + value = { + type = "select", + start = 13, + finish = 16, + parent = "", + vararg = "", + sindex = 1, + }, + [1] = "x", + }, + [2] = { + type = "local", + start = 9, + finish = 10, + effect = 21, + range = 21, + parent = "", + value = { + type = "nil", + start = 18, + finish = 21, + parent = "", + }, + [1] = "y", + }, +} +CHECK'local x , y = 1' +{ + type = "main", + start = 0, + finish = 30, + locals = "", + [1] = { + type = "local", + start = 6, + finish = 7, + effect = 30, + range = 30, + parent = "", + locPos = 0, + value = { + type = "integer", + start = 29, + finish = 30, + parent = "", + [1] = 1, + }, + attrs = { + type = "localattrs", + parent = "", + [1] = { + type = "localattr", + start = 8, + finish = 15, + parent = "", + [1] = "const", + }, + }, + [1] = "x", + }, + [2] = { + type = "local", + start = 17, + finish = 18, + effect = 30, + parent = "", + attrs = { + type = "localattrs", + parent = "", + [1] = { + type = "localattr", + start = 19, + finish = 26, + parent = "", + [1] = "close", + }, + }, + [1] = "y", + }, +} +CHECK[[ +x = 1 +y = 2 +]] +{ + type = "main", + start = 0, + finish = 20000, + locals = "", + [1] = { + type = "setglobal", + start = 0, + finish = 1, + range = 5, + parent = "", + node = "", + value = { + type = "integer", + start = 4, + finish = 5, + parent = "", + [1] = 1, + }, + [1] = "x", + }, + [2] = { + type = "setglobal", + start = 10000, + finish = 10001, + range = 10005, + parent = "", + node = "", + value = { + type = "integer", + start = 10004, + finish = 10005, + parent = "", + [1] = 2, + }, + [1] = "y", + }, +} + +CHECK[[ +x, y = 1, 2 +]] +{ + type = "main", + start = 0, + finish = 10000, + locals = "", + [1] = { + type = "setglobal", + start = 0, + finish = 1, + range = 8, + parent = "", + node = "", + value = { + type = "integer", + start = 7, + finish = 8, + parent = "", + [1] = 1, + }, + [1] = "x", + }, + [2] = { + type = "setglobal", + start = 3, + finish = 4, + range = 11, + parent = "", + node = "", + value = { + type = "integer", + start = 10, + finish = 11, + parent = "", + [1] = 2, + }, + [1] = "y", + }, +} +CHECK[[ +local function a() + return +end]] +{ + type = "main", + start = 0, + finish = 20003, + locals = "", + [1] = { + type = "local", + start = 15, + vstart = 6, + finish = 16, + effect = 16, + range = 20003, + parent = "", + locPos = 0, + value = { + type = "function", + start = 6, + bstart = 18, + finish = 20003, + keyword = { + [1] = 6, + [2] = 14, + [3] = 20000, + [4] = 20003, + }, + parent = "", + args = { + type = "funcargs", + start = 16, + finish = 18, + parent = "", + }, + returns = "", + hasReturn = true, + [1] = { + type = "return", + start = 10004, + finish = 10010, + parent = "", + }, + }, + [1] = "a", + }, +} +CHECK[[ +local function f() + return f() +end]] +{ + type = "main", + start = 0, + finish = 20003, + locals = "", + [1] = { + type = "local", + start = 15, + vstart = 6, + finish = 16, + effect = 16, + range = 20003, + parent = "", + locPos = 0, + value = { + type = "function", + start = 6, + bstart = 18, + finish = 20003, + keyword = { + [1] = 6, + [2] = 14, + [3] = 20000, + [4] = 20003, + }, + parent = "", + args = { + type = "funcargs", + start = 16, + finish = 18, + parent = "", + }, + returns = "", + hasReturn = true, + [1] = { + type = "return", + start = 10004, + finish = 10014, + parent = "", + [1] = { + type = "call", + start = 10011, + finish = 10014, + parent = "", + node = "", + }, + }, + }, + ref = "", + [1] = "f", + }, +} +CHECK[[ +local function a(b, c) + return +end]] +{ + type = "main", + start = 0, + finish = 20003, + locals = "", + [1] = { + type = "local", + start = 15, + vstart = 6, + finish = 16, + effect = 16, + range = 20003, + parent = "", + locPos = 0, + value = { + type = "function", + start = 6, + bstart = 22, + finish = 20003, + keyword = { + [1] = 6, + [2] = 14, + [3] = 20000, + [4] = 20003, + }, + parent = "", + args = { + type = "funcargs", + start = 16, + finish = 22, + parent = "", + [1] = { + type = "local", + start = 17, + finish = 18, + effect = 18, + parent = "", + [1] = "b", + }, + [2] = { + type = "local", + start = 20, + finish = 21, + effect = 21, + parent = "", + [1] = "c", + }, + }, + locals = "", + returns = "", + hasReturn = true, + [1] = { + type = "return", + start = 10004, + finish = 10010, + parent = "", + }, + }, + [1] = "a", + }, +} + +CHECK[[ +local x, y, z = 1, 2 +local function f() +end +y, z = 3, 4 +]] +{ + type = "main", + start = 0, + finish = 40000, + locals = "", + [1] = { + type = "local", + start = 6, + finish = 7, + effect = 20, + range = 17, + parent = "", + locPos = 0, + value = { + type = "integer", + start = 16, + finish = 17, + parent = "", + [1] = 1, + }, + [1] = "x", + }, + [2] = { + type = "local", + start = 9, + finish = 10, + effect = 20, + range = 20, + parent = "", + value = { + type = "integer", + start = 19, + finish = 20, + parent = "", + [1] = 2, + }, + ref = "", + [1] = "y", + }, + [3] = { + type = "local", + start = 12, + finish = 13, + effect = 20, + parent = "", + ref = "", + [1] = "z", + }, + [4] = { + type = "local", + start = 10015, + vstart = 10006, + finish = 10016, + effect = 10016, + range = 20003, + parent = "", + locPos = 10000, + value = { + type = "function", + start = 10006, + bstart = 10018, + finish = 20003, + keyword = { + [1] = 10006, + [2] = 10014, + [3] = 20000, + [4] = 20003, + }, + parent = "", + args = { + type = "funcargs", + start = 10016, + finish = 10018, + parent = "", + }, + }, + [1] = "f", + }, + [5] = { + type = "setlocal", + start = 30000, + finish = 30001, + range = 30008, + parent = "", + node = "", + value = { + type = "integer", + start = 30007, + finish = 30008, + parent = "", + [1] = 3, + }, + [1] = "y", + }, + [6] = { + type = "setlocal", + start = 30003, + finish = 30004, + range = 30011, + parent = "", + node = "", + value = { + type = "integer", + start = 30010, + finish = 30011, + parent = "", + [1] = 4, + }, + [1] = "z", + }, +} +CHECK[[ +local f = require +local z = f +z'xxx' +]] +{ + type = "main", + start = 0, + finish = 30000, + locals = "", + [1] = { + type = "local", + start = 6, + finish = 7, + effect = 17, + range = 17, + special = "require", + parent = "", + locPos = 0, + value = { + type = "getglobal", + start = 10, + finish = 17, + special = "require", + parent = "", + node = "", + [1] = "require", + }, + ref = "", + [1] = "f", + }, + [2] = { + type = "local", + start = 10006, + finish = 10007, + effect = 10011, + range = 10011, + special = "require", + parent = "", + locPos = 10000, + value = { + type = "getlocal", + start = 10010, + finish = 10011, + special = "require", + parent = "", + node = "", + [1] = "f", + }, + ref = "", + [1] = "z", + }, + [3] = { + type = "call", + start = 20000, + finish = 20006, + parent = "", + node = "", + args = { + type = "callargs", + start = 20001, + finish = 20006, + parent = "", + [1] = { + type = "string", + start = 20001, + finish = 20006, + parent = "", + [1] = "xxx", + [2] = "'", + }, + }, + }, +} +CHECK[[ +A:B(1):C(2) +]] +{ + type = "main", + start = 0, + finish = 10000, + locals = "", + [1] = { + type = "call", + start = 0, + finish = 11, + parent = "", + node = "", + args = { + type = "callargs", + start = 8, + finish = 11, + parent = "", + [1] = { + type = "self", + start = 6, + finish = 7, + parent = "", + [1] = "self", + }, + [2] = { + type = "integer", + start = 9, + finish = 10, + parent = "", + [1] = 2, + }, + }, + }, +} + +CHECK [[ + +local s = [ [=[111]=] ] +]] +{ + type = "main", + start = 0, + finish = 20000, + locals = "", + [1] = { + type = "local", + start = 10006, + finish = 10007, + effect = 10007, + parent = "", + locPos = 10000, + [1] = "s", + }, + [2] = { + type = "string", + start = 10012, + finish = 10021, + parent = "", + [1] = "111", + [2] = "[=[", + }, +} +CHECK([[ +local x = 1 // 2 +]], { + nonstandardSymbol = { ['//'] = true } +}) +{ + type = "main", + start = 0, + finish = 10000, + locals = "", + [1] = { + type = "local", + start = 6, + finish = 7, + effect = 11, + range = 11, + parent = "", + locPos = 0, + value = { + type = "integer", + start = 10, + finish = 11, + parent = "", + [1] = 1, + }, + [1] = "x", + }, +} + +CHECK([[ +local x = { + 1, // BAD + 2, // GOOD + 3, // GOOD +} +]], { + nonstandardSymbol = { ['//'] = true } +}) +{ + type = "main", + start = 0, + finish = 50000, + locals = "", + [1] = { + type = "local", + start = 6, + finish = 7, + effect = 40001, + range = 40001, + parent = "", + locPos = 0, + value = { + type = "table", + start = 10, + finish = 40001, + parent = "", + [1] = { + type = "tableexp", + start = 10004, + finish = 10005, + tindex = 1, + parent = "", + value = { + type = "integer", + start = 10004, + finish = 10005, + parent = "", + [1] = 1, + }, + }, + [2] = { + type = "tableexp", + start = 20004, + finish = 20005, + tindex = 2, + parent = "", + value = { + type = "integer", + start = 20004, + finish = 20005, + parent = "", + [1] = 2, + }, + }, + [3] = { + type = "tableexp", + start = 30004, + finish = 30005, + tindex = 3, + parent = "", + value = { + type = "integer", + start = 30004, + finish = 30005, + parent = "", + [1] = 3, + }, + }, + }, + [1] = "x", + }, +} + +CHECK [[ +local x +return { + x = 1, +} +]] +{ + type = "main", + start = 0, + finish = 40000, + locals = "", + returns = "", + [1] = { + type = "local", + start = 6, + finish = 7, + effect = 7, + parent = "", + locPos = 0, + [1] = "x", + }, + [2] = { + type = "return", + start = 10000, + finish = 30001, + parent = "", + [1] = { + type = "table", + start = 10007, + finish = 30001, + parent = "", + [1] = { + type = "tablefield", + start = 20004, + finish = 20005, + range = 20009, + parent = "", + node = "", + field = { + type = "field", + start = 20004, + finish = 20005, + parent = "", + [1] = "x", + }, + value = { + type = "integer", + start = 20008, + finish = 20009, + parent = "", + [1] = 1, + }, + }, + }, + }, +} + +CHECK [[ +local x +a = { + x +} +]] +{ + type = "main", + start = 0, + finish = 40000, + locals = "", + [1] = { + type = "local", + start = 6, + finish = 7, + effect = 7, + parent = "", + locPos = 0, + ref = "", + [1] = "x", + }, + [2] = { + type = "setglobal", + start = 10000, + finish = 10001, + range = 30001, + parent = "", + node = "", + value = { + type = "table", + start = 10004, + finish = 30001, + parent = "", + [1] = { + type = "tableexp", + start = 20004, + finish = 20005, + tindex = 1, + parent = "", + value = { + type = "getlocal", + start = 20004, + finish = 20005, + parent = "", + node = "", + [1] = "x", + }, + }, + }, + [1] = "a", + }, +} + +CHECK [[ +x, y, z = 1, func() +]] +{ + type = "main", + start = 0, + finish = 10000, + locals = "", + [1] = { + type = "setglobal", + start = 0, + finish = 1, + range = 11, + parent = "", + node = "", + value = { + type = "integer", + start = 10, + finish = 11, + parent = "", + [1] = 1, + }, + [1] = "x", + }, + [2] = { + type = "setglobal", + start = 3, + finish = 4, + range = 19, + parent = "", + node = "", + value = { + type = "select", + start = 13, + finish = 19, + parent = "", + vararg = "", + sindex = 1, + }, + [1] = "y", + }, + [3] = { + type = "setglobal", + start = 6, + finish = 7, + range = 19, + parent = "", + node = "", + value = { + type = "select", + start = 13, + finish = 19, + parent = "", + vararg = "", + sindex = 2, + }, + [1] = "z", + }, +} + +CHECK [[ +local x, y +-- comments +]] +{ + type = "main", + start = 0, + finish = 20000, + locals = "", + [1] = { + type = "local", + start = 6, + finish = 7, + effect = 10, + parent = "", + locPos = 0, + [1] = "x", + }, + [2] = { + type = "local", + start = 9, + finish = 10, + effect = 10, + parent = "", + [1] = "y", + }, +} + +CHECK [[ +local _ENV = nil +]] +{ + type = "main", + start = 0, + finish = 10000, + locals = "", + [1] = { + type = "local", + start = 6, + finish = 10, + effect = 16, + range = 16, + parent = "", + locPos = 0, + value = { + type = "nil", + start = 13, + finish = 16, + parent = "", + }, + [1] = "_ENV", + }, +} + +CHECK [[ +_ENV = nil +]] +{ + type = "main", + start = 0, + finish = 10000, + locals = "", + [1] = { + type = "local", + start = 0, + finish = 4, + effect = 10, + range = 10, + parent = "", + node = "", + locPos = 0, + value = { + type = "nil", + start = 7, + finish = 10, + parent = "", + }, + [1] = "_ENV", + }, +} diff --git a/test/ast/state.lua b/test/ast/state.lua index d6f1a99..bd9ba5c 100644 --- a/test/ast/state.lua +++ b/test/ast/state.lua @@ -338,7 +338,8 @@ end { type = 'Function', name = { - id = 'f' + type = 'Var', + id = 'f' }, params = { [1] = { @@ -374,3 +375,49 @@ end } } } + +TEST [[ +local f = function (x, y) +end +]] +{ + type = 'LocalDef', + vars = { + [1] = { + id = 'f', + } + }, + values = { + [1] = { + type = 'Function', + params = { + [1] = { + id = 'x' + }, + [2] = { + id = 'y' + } + } + } + } +} + +TEST [[ +local function f(x, y) +end +]] +{ + type = 'Function', + name = { + type = 'Local', + id = 'f', + }, + params = { + [1] = { + id = 'x' + }, + [2] = { + id = 'y' + } + } +} From 1d816da2d8e90aa8fe234f687a0f59bc8210a738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 21 Sep 2023 16:13:23 +0800 Subject: [PATCH 42/94] =?UTF-8?q?=E7=AE=80=E5=8D=95=E8=A7=A3=E6=9E=90=20bl?= =?UTF-8?q?ock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 10 ++++++++++ src/parser/ast/binary.lua | 3 +-- src/parser/ast/block.lua | 23 ++++++++++++++++++++++- src/parser/ast/boolean.lua | 6 ++---- src/parser/ast/break.lua | 6 ++---- src/parser/ast/call.lua | 8 +++----- src/parser/ast/comment.lua | 6 ++---- src/parser/ast/do.lua | 6 +++--- src/parser/ast/error.lua | 3 +-- src/parser/ast/exp.lua | 11 +++++++---- src/parser/ast/field.lua | 15 +++++---------- src/parser/ast/for.lua | 5 ++--- src/parser/ast/function.lua | 6 ++---- src/parser/ast/id.lua | 2 +- src/parser/ast/if.lua | 12 ++++-------- src/parser/ast/label.lua | 6 ++---- src/parser/ast/local.lua | 3 +-- src/parser/ast/nil.lua | 3 +-- src/parser/ast/number.lua | 6 ++---- src/parser/ast/repeat.lua | 3 +-- src/parser/ast/return.lua | 2 +- src/parser/ast/state.lua | 4 ++-- src/parser/ast/string.lua | 6 ++---- src/parser/ast/table.lua | 6 ++---- src/parser/ast/unary.lua | 3 +-- src/parser/ast/var.lua | 3 +-- src/parser/ast/while.lua | 3 +-- test/ast/block.lua | 24 ++++++++++++++++++++++-- test/ast/state.lua | 1 + 29 files changed, 107 insertions(+), 88 deletions(-) diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index eba7e0b..4e37ec1 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -134,6 +134,16 @@ function M:getLastPos() return pos + #token end +-- 创建一个节点 +---@generic T: string +---@param type `T` +---@param data table +---@return T +function M:createNode(type, data) + data.ast = self + return class.new(type, data) +end + -- 编译Lua代码 function M:parseMain() self:skipSpace() diff --git a/src/parser/ast/binary.lua b/src/parser/ast/binary.lua index 40deaa6..0437845 100644 --- a/src/parser/ast/binary.lua +++ b/src/parser/ast/binary.lua @@ -109,8 +109,7 @@ function Ast:parseBinary(curExp, curLevel, asState) local exp2 = self:parseExp(true, false, myLevel) - local binary = class.new('LuaParser.Node.Binary', { - ast = self, + local binary = self:createNode('LuaParser.Node.Binary', { start = curExp.start, finish = self:getLastPos(), op = op, diff --git a/src/parser/ast/block.lua b/src/parser/ast/block.lua index 7a28532..bc0f2af 100644 --- a/src/parser/ast/block.lua +++ b/src/parser/ast/block.lua @@ -12,7 +12,28 @@ end ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +local FinishMap = { + ['end'] = true, + ['elseif'] = true, + ['else'] = true, + ['until'] = true, + ['}'] = true, + [')'] = true, +} + ---@param block LuaParser.Node.Block function Ast:parseBlockChilds(block) - + while true do + local token = self.lexer:peek() + if FinishMap[token] then + break + end + local state = self:parseState() + if not state then + break + end + state.parent = block + block.childs[#block.childs+1] = state + self:skipSpace() + end end diff --git a/src/parser/ast/boolean.lua b/src/parser/ast/boolean.lua index 1a8c580..62c30c4 100644 --- a/src/parser/ast/boolean.lua +++ b/src/parser/ast/boolean.lua @@ -14,8 +14,7 @@ function Ast:parseBoolean() if token == 'true' then local start, finish = self.lexer:range() self.lexer:next() - return class.new('LuaParser.Node.Boolean', { - ast = self, + return self:createNode('LuaParser.Node.Boolean', { start = start, finish = finish, value = true, @@ -24,8 +23,7 @@ function Ast:parseBoolean() if token == 'false' then local start, finish = self.lexer:range() self.lexer:next() - return class.new('LuaParser.Node.Boolean', { - ast = self, + return self:createNode('LuaParser.Node.Boolean', { start = start, finish = finish, value = false, diff --git a/src/parser/ast/break.lua b/src/parser/ast/break.lua index 640d873..b68d5d7 100644 --- a/src/parser/ast/break.lua +++ b/src/parser/ast/break.lua @@ -16,8 +16,7 @@ function Ast:parseBreak() return end - return class.new('LuaParser.Node.Break', { - ast = self, + return self:createNode('LuaParser.Node.Break', { start = pos, finish = self:getLastPos(), }) @@ -33,8 +32,7 @@ function Ast:parseContinue() return end - return class.new('LuaParser.Node.Continue', { - ast = self, + return self:createNode('LuaParser.Node.Continue', { start = pos, finish = self:getLastPos(), }) diff --git a/src/parser/ast/call.lua b/src/parser/ast/call.lua index 97e0ad9..ee73066 100644 --- a/src/parser/ast/call.lua +++ b/src/parser/ast/call.lua @@ -14,10 +14,9 @@ local Ast = class.declare 'LuaParser.Ast' function Ast:parseCall(last) local pos = self.lexer:consume '(' if pos then - local exps = self:parseExpList() + local exps = self:parseExpList(false, true) self:assertSymbol ')' - local call = class.new('LuaParser.Node.Call', { - ast = self, + local call = self:createNode('LuaParser.Node.Call', { start = last.start, finish = self:getLastPos(), node = last, @@ -34,8 +33,7 @@ function Ast:parseCall(last) local literalArg = self:parseString() or self:parseTable() if literalArg then - local call = class.new('LuaParser.Node.Call', { - ast = self, + local call = self:createNode('LuaParser.Node.Call', { start = last.start, finish = self:getLastPos(), node = last, diff --git a/src/parser/ast/comment.lua b/src/parser/ast/comment.lua index 8ff0a77..70abd20 100644 --- a/src/parser/ast/comment.lua +++ b/src/parser/ast/comment.lua @@ -47,8 +47,7 @@ function Ast:parseShortComment(inExp) end local offset = self.code:find('[\r\n]', pos + 2) or (#self.code + 1) self.lexer:fastForward(offset) - return class.new('LuaParser.Node.Comment', { - ast = self, + return self:createNode('LuaParser.Node.Comment', { subtype = 'short', start = pos, finish = offset - 1, @@ -86,8 +85,7 @@ function Ast:parseLongComment() self:throwMissSymbol(#self.code, finishQuo) self.lexer:fastForward(#self.code) end - return class.new('LuaParser.Node.Comment', { - ast = self, + return self:createNode('LuaParser.Node.Comment', { subtype = 'long', start = pos, finish = offset and (offset + #finishQuo - 1) or #self.code, diff --git a/src/parser/ast/do.lua b/src/parser/ast/do.lua index 38bd0ad..1600e6b 100644 --- a/src/parser/ast/do.lua +++ b/src/parser/ast/do.lua @@ -1,6 +1,7 @@ local class = require 'class' ---@class LuaParser.Node.Do: LuaParser.Node.Block +---@field symbolPos? integer # end 的位置 local Do = class.declare('LuaParser.Node.Do', 'LuaParser.Node.Block') ---@class LuaParser.Ast @@ -13,8 +14,7 @@ function Ast:parseDo() return nil end - local doNode = class.new('LuaParser.Node.Do', { - ast = self, + local doNode = self:createNode('LuaParser.Node.Do', { start = pos, }) @@ -22,7 +22,7 @@ function Ast:parseDo() self:parseBlockChilds(doNode) self:skipSpace() - self:assertSymbol 'end' + doNode.symbolPos = self:assertSymbol 'end' doNode.finish = self:getLastPos() diff --git a/src/parser/ast/error.lua b/src/parser/ast/error.lua index 338c9a2..472c0c5 100644 --- a/src/parser/ast/error.lua +++ b/src/parser/ast/error.lua @@ -15,9 +15,8 @@ local Ast = class.declare 'LuaParser.Ast' ---@param finish integer? ---@param extra table? function Ast:throw(errorCode, start, finish, extra) - self.errors[#self.errors+1] = class.new('LuaParser.Node.Error', { + self.errors[#self.errors+1] = self:createNode('LuaParser.Node.Error', { code = errorCode, - ast = self, start = start, finish = finish or start, extra = extra, diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index 3a5f5eb..ffa68c3 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -80,9 +80,10 @@ function Ast:parseExp(required, asState, curLevel) end -- 解析表达式列表,以逗号分隔 ----@param atLeastOne? true +---@param atLeastOne? boolean +---@param greedy? boolean ---@return LuaParser.Node.Exp[] -function Ast:parseExpList(atLeastOne) +function Ast:parseExpList(atLeastOne, greedy) ---@type LuaParser.Node.Exp[] local list = {} local first = self:parseExp(atLeastOne) @@ -101,6 +102,9 @@ function Ast:parseExpList(atLeastOne) break end else + if not greedy then + break + end if tp == 'Word' and self:isKeyWord(token) then break end @@ -180,8 +184,7 @@ function Ast:parseParen() if not exp then return nil end - local paren = class.new('LuaParser.Node.Paren', { - ast = self, + local paren = self:createNode('LuaParser.Node.Paren', { start = pos, exp = exp, }) diff --git a/src/parser/ast/field.lua b/src/parser/ast/field.lua index 141d94f..c6cd06e 100644 --- a/src/parser/ast/field.lua +++ b/src/parser/ast/field.lua @@ -42,8 +42,7 @@ function Ast:parseField(last) self:skipSpace() local key = self:parseID('LuaParser.Node.FieldID', true) if key then - local field = class.new('LuaParser.Node.Field', { - ast = self, + local field = self:createNode('LuaParser.Node.Field', { start = last.start, finish = key.finish, subtype = (token == '.') and 'field' or 'method', @@ -71,8 +70,7 @@ function Ast:parseField(last) if key then self:skipSpace() local symbolPos2 = self:assertSymbol(']') - local field = class.new('LuaParser.Node.Field', { - ast = self, + local field = self:createNode('LuaParser.Node.Field', { start = last.start, finish = self:getLastPos(), subtype = 'index', @@ -111,8 +109,7 @@ function Ast:parseTableFieldAsField() end self:skipSpace() local value = self:parseExp(true) - local tfield = class.new('LuaParser.Node.TableField', { - ast = self, + local tfield = self:createNode('LuaParser.Node.TableField', { subtype = 'field', key = key, value = value, @@ -147,8 +144,7 @@ function Ast:parseTableFieldAsIndex() self:assertSymbol '=' self:skipSpace() local value = self:parseExp(true) - local tfield = class.new('LuaParser.Node.TableField', { - ast = self, + local tfield = self:createNode('LuaParser.Node.TableField', { subtype = 'index', key = key, value = value, @@ -172,8 +168,7 @@ function Ast:parseTableFieldAsExp() if not exp then return nil end - local tfield = class.new('LuaParser.Node.TableField', { - ast = self, + local tfield = self:createNode('LuaParser.Node.TableField', { subtype = 'exp', value = exp, start = exp.start, diff --git a/src/parser/ast/for.lua b/src/parser/ast/for.lua index ed54444..8ade32e 100644 --- a/src/parser/ast/for.lua +++ b/src/parser/ast/for.lua @@ -23,8 +23,7 @@ function Ast:parseFor() return nil end - local forNode = class.new('LuaParser.Node.For', { - ast = self, + local forNode = self:createNode('LuaParser.Node.For', { start = pos, }) @@ -51,7 +50,7 @@ function Ast:parseFor() self.lexer:next() self:skipSpace() - local exps = self:parseExpList(true) + local exps = self:parseExpList(true, true) forNode.exps = exps for i = 1, #exps do local exp = exps[i] diff --git a/src/parser/ast/function.lua b/src/parser/ast/function.lua index 93eab5b..d0ea1db 100644 --- a/src/parser/ast/function.lua +++ b/src/parser/ast/function.lua @@ -55,8 +55,7 @@ function Ast:parseFunction(isLocal) self:skipSpace() local symbolPos2 = self:assertSymbol ')' - local func = class.new('LuaParser.Node.Function', { - ast = self, + local func = self:createNode('LuaParser.Node.Function', { start = pos, name = name, params = params, @@ -157,8 +156,7 @@ function Ast:parseParam() end local pos = self.lexer:consume '...' if pos then - return class.new('LuaParser.Node.Param', { - ast = self, + return self:createNode('LuaParser.Node.Param', { start = pos, finish = pos, id = '...', diff --git a/src/parser/ast/id.lua b/src/parser/ast/id.lua index a1dca76..dc7ac27 100644 --- a/src/parser/ast/id.lua +++ b/src/parser/ast/id.lua @@ -36,7 +36,7 @@ function Ast:parseID(nodeType, required) end end self.lexer:next() - return class.new(nodeType, { + return self:createNode(nodeType, { id = token, start = pos, finish = pos + #token, diff --git a/src/parser/ast/if.lua b/src/parser/ast/if.lua index 6ceecd0..2514982 100644 --- a/src/parser/ast/if.lua +++ b/src/parser/ast/if.lua @@ -20,8 +20,7 @@ function Ast:parseIf() return nil end - local ifNode = class.new('LuaParser.Node.If', { - ast = self, + local ifNode = self:createNode('LuaParser.Node.If', { start = pos, childs = {}, }) @@ -60,8 +59,7 @@ function Ast:parseIfChildIf() self:skipSpace() local condition = self:parseExp() - local node = class.new('LuaParser.Node.IfChild', { - ast = self, + local node = self:createNode('LuaParser.Node.IfChild', { subtype = 'if', start = pos, condition = condition, @@ -90,8 +88,7 @@ function Ast:parseIfChildElseIf() self:skipSpace() local condition = self:parseExp() - local node = class.new('LuaParser.Node.IfChild', { - ast = self, + local node = self:createNode('LuaParser.Node.IfChild', { subtype = 'elseif', start = pos, condition = condition, @@ -119,8 +116,7 @@ function Ast:parseIfChildElse() self:skipSpace() - local node = class.new('LuaParser.Node.IfChild', { - ast = self, + local node = self:createNode('LuaParser.Node.IfChild', { subtype = 'else', start = pos, }) diff --git a/src/parser/ast/label.lua b/src/parser/ast/label.lua index 77e0686..268ef07 100644 --- a/src/parser/ast/label.lua +++ b/src/parser/ast/label.lua @@ -33,8 +33,7 @@ function Ast:parseLabel() self:skipSpace() local symbolPos = self:assertSymbol '::' - local label = class.new('LuaParser.Node.Label', { - ast = self, + local label = self:createNode('LuaParser.Node.Label', { start = pos, finish = self:getLastPos(), symbolPos = symbolPos, @@ -62,8 +61,7 @@ function Ast:parseGoto() return nil end - local gotoNode = class.new('LuaParser.Node.Goto', { - ast = self, + local gotoNode = self:createNode('LuaParser.Node.Goto', { start = pos, finish = self:getLastPos(), }) diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index 7254fda..4950f0d 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -29,8 +29,7 @@ function Ast:parseLocal() return self:parseFunction(true) end - local localdef = class.new('LuaParser.Node.LocalDef', { - ast = self, + local localdef = self:createNode('LuaParser.Node.LocalDef', { start = pos, refs = {}, }) diff --git a/src/parser/ast/nil.lua b/src/parser/ast/nil.lua index 45e8cd9..ab57750 100644 --- a/src/parser/ast/nil.lua +++ b/src/parser/ast/nil.lua @@ -18,8 +18,7 @@ function Ast:parseNil() end local start, finish = self.lexer:range() self.lexer:next() - return class.new('LuaParser.Node.Nil', { - ast = self, + return self:createNode('LuaParser.Node.Nil', { start = start, finish = finish, }) diff --git a/src/parser/ast/number.lua b/src/parser/ast/number.lua index ae84ede..a4ea010 100644 --- a/src/parser/ast/number.lua +++ b/src/parser/ast/number.lua @@ -188,8 +188,7 @@ function Ast:buildFloat(value, startPos, curOffset) valuei = value end self:fastForwardNumber(curOffset) - return class.new('LuaParser.Node.Float', { - ast = self, + return self:createNode('LuaParser.Node.Float', { start = startPos, finish = curOffset - 1, value = valuei and 0.0 or value, @@ -218,8 +217,7 @@ function Ast:buildInteger(value, startPos, curOffset) end end self:fastForwardNumber(curOffset) - return class.new('LuaParser.Node.Integer', { - ast = self, + return self:createNode('LuaParser.Node.Integer', { start = startPos, finish = curOffset - 1, value = valuei and 0 or value, diff --git a/src/parser/ast/repeat.lua b/src/parser/ast/repeat.lua index 1a5d0a9..22fb61f 100644 --- a/src/parser/ast/repeat.lua +++ b/src/parser/ast/repeat.lua @@ -15,8 +15,7 @@ function Ast:parseRepeat() return nil end - local repeatNode = class.new('LuaParser.Node.Repeat', { - ast = self, + local repeatNode = self:createNode('LuaParser.Node.Repeat', { start = pos, }) diff --git a/src/parser/ast/return.lua b/src/parser/ast/return.lua index 1d9859c..0908de6 100644 --- a/src/parser/ast/return.lua +++ b/src/parser/ast/return.lua @@ -16,7 +16,7 @@ function Ast:parseReturn() local exps = self:parseExpList() - local ret = class.new('LuaParser.Node.Return', { + local ret = self:createNode('LuaParser.Node.Return', { start = pos, finish = self:getLastPos(), exps = exps, diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua index 69d9d47..72a2781 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state.lua @@ -112,7 +112,7 @@ function Ast:parseStateStartWithExp() end end - local state = class.new('LuaParser.Node.SingleExp', { + local state = self:createNode('LuaParser.Node.SingleExp', { start = exp.start, finish = exp.finish, exp = exp, @@ -130,7 +130,7 @@ function Ast:parseAssign(first) return nil end - local assign = class.new('LuaParser.Node.Assign', { + local assign = self:createNode('LuaParser.Node.Assign', { start = first.start, exps = {}, }) diff --git a/src/parser/ast/string.lua b/src/parser/ast/string.lua index 9e9d6ec..0d7c780 100644 --- a/src/parser/ast/string.lua +++ b/src/parser/ast/string.lua @@ -245,8 +245,7 @@ function Ast:parseShortString() quo = '"' end - return class.new('LuaParser.Node.String', { - ast = self, + return self:createNode('LuaParser.Node.String', { start = pos, finish = finishPos, quo = quo, @@ -285,8 +284,7 @@ function Ast:parseLongString() self.lexer:fastForward(finishPos) - return class.new('LuaParser.Node.String', { - ast = self, + return self:createNode('LuaParser.Node.String', { start = pos, finish = finishPos, quo = quo, diff --git a/src/parser/ast/table.lua b/src/parser/ast/table.lua index 1ca6548..4927b28 100644 --- a/src/parser/ast/table.lua +++ b/src/parser/ast/table.lua @@ -20,8 +20,7 @@ function Ast:parseTable() self:assertSymbol '}' - local table = class.new('LuaParser.Node.Table', { - ast = self, + local table = self:createNode('LuaParser.Node.Table', { start = pos, finish = self:getLastPos(), fields = fields, @@ -32,8 +31,7 @@ function Ast:parseTable() field.parent = table if field.subtype == 'exp' then expi = expi + 1 - field.key = class.new('LuaParser.Node.Integer', { - ast = self, + field.key = self:createNode('LuaParser.Node.Integer', { dummy = true, value = expi, start = field.start, diff --git a/src/parser/ast/unary.lua b/src/parser/ast/unary.lua index ff6b3c5..6ba0694 100644 --- a/src/parser/ast/unary.lua +++ b/src/parser/ast/unary.lua @@ -63,8 +63,7 @@ function Ast:parseUnary() local myLevel = UnarySymbol[token] local exp = self:parseExp(true, false, myLevel) - local unary = class.new('LuaParser.Node.Unary', { - ast = self, + local unary = self:createNode('LuaParser.Node.Unary', { start = pos, finish = self:getLastPos(), op = token, diff --git a/src/parser/ast/var.lua b/src/parser/ast/var.lua index ec55723..0be858f 100644 --- a/src/parser/ast/var.lua +++ b/src/parser/ast/var.lua @@ -30,8 +30,7 @@ function Ast:parseVarargs() if not pos then return nil end - return class.new('LuaParser.Node.Varargs', { - ast = self, + return self:createNode('LuaParser.Node.Varargs', { start = pos, finish = pos + 3, }) diff --git a/src/parser/ast/while.lua b/src/parser/ast/while.lua index a59f34a..36c2ee4 100644 --- a/src/parser/ast/while.lua +++ b/src/parser/ast/while.lua @@ -22,8 +22,7 @@ function Ast:parseWhile() return nil end - local whileNode = class.new('LuaParser.Node.While', { - ast = self, + local whileNode = self:createNode('LuaParser.Node.While', { start = pos, condition = condition, }) diff --git a/test/ast/block.lua b/test/ast/block.lua index 9bf8c85..7823df5 100644 --- a/test/ast/block.lua +++ b/test/ast/block.lua @@ -3,12 +3,32 @@ local class = require 'class' local function TEST(code) return function (expect) local ast = class.new 'LuaParser.Ast' (code) - local node = ast:parseBoolean() + local node = ast:parseState() assert(node) Match(node, expect) end end TEST [[ - +do + x = 1 + y = 2 +end ]] +{ + type = 'Do', + left = 0, + right = 30003, + childs = { + [1] = { + type = 'Assign', + left = 10004, + right = 10009, + }, + [2] = { + type = 'Assign', + left = 20004, + right = 20009, + }, + } +} diff --git a/test/ast/state.lua b/test/ast/state.lua index bd9ba5c..6984f9c 100644 --- a/test/ast/state.lua +++ b/test/ast/state.lua @@ -167,6 +167,7 @@ end type = 'Do', left = 0, right = 10003, + symbolPos = 3, } TEST [[ From 99e8f29a6fc492248c79110031b4cb31f1a9a4c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 21 Sep 2023 17:57:45 +0800 Subject: [PATCH 43/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=B1=80=E9=83=A8?= =?UTF-8?q?=E5=8F=98=E9=87=8F=E7=9A=84=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 6 +++ src/parser/ast/block.lua | 13 ++++++ src/parser/ast/for.lua | 2 +- src/parser/ast/id.lua | 9 +++- src/parser/ast/local.lua | 99 +++++++++++++++++++++++++++++++++++++++- test/ast/local.lua | 32 +++++++++++++ 6 files changed, 157 insertions(+), 4 deletions(-) diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 4e37ec1..54a41ec 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -54,6 +54,12 @@ function M:__init(code, version, options) -- 注释 ---@type LuaParser.Node.Comment[] self.comments = {} + -- 代码块 + ---@type LuaParser.Node.Block[] + self.blocks = {} + -- 当前代码块 + ---@type LuaParser.Node.Block + self.curBlock = nil local major, minor = self.version:match 'Lua (%d+)%.(%d+)' ---@type integer diff --git a/src/parser/ast/block.lua b/src/parser/ast/block.lua index bc0f2af..b2bdada 100644 --- a/src/parser/ast/block.lua +++ b/src/parser/ast/block.lua @@ -21,6 +21,19 @@ local FinishMap = { [')'] = true, } +---@param block LuaParser.Node.Block +function Ast:blockStart(block) + self.blocks[#self.blocks+1] = block + self.curBlock = block +end + +---@param block LuaParser.Node.Block +function Ast:blockFinish(block) + assert(self.curBlock == block) + self.blocks[#self.blocks] = nil + self.curBlock = self.blocks[#self.blocks] +end + ---@param block LuaParser.Node.Block function Ast:parseBlockChilds(block) while true do diff --git a/src/parser/ast/for.lua b/src/parser/ast/for.lua index 8ade32e..ba33a25 100644 --- a/src/parser/ast/for.lua +++ b/src/parser/ast/for.lua @@ -28,7 +28,7 @@ function Ast:parseFor() }) self:skipSpace() - local vars = self:parseIDList('LuaParser.Node.Local', true) + local vars = self:parseLocalList() forNode.vars = vars for i = 1, #vars do local var = vars[i] diff --git a/src/parser/ast/id.lua b/src/parser/ast/id.lua index dc7ac27..ea74cdd 100644 --- a/src/parser/ast/id.lua +++ b/src/parser/ast/id.lua @@ -10,6 +10,7 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.TableFieldID ---| LuaParser.Node.Param ---| LuaParser.Node.LabelName +---| LuaParser.Node.AttrName ---@generic T: LuaParser.Node.ID ---@param nodeType `T` @@ -45,9 +46,10 @@ end ---@generic T: LuaParser.Node.ID ---@param nodeType `T` ----@param atLeastOne? true +---@param atLeastOne? boolean +---@param greedy? boolean ---@return T[] -function Ast:parseIDList(nodeType, atLeastOne) +function Ast:parseIDList(nodeType, atLeastOne, greedy) ---@type LuaParser.Node.ID[] local list = {} local first = self:parseID(nodeType, atLeastOne) @@ -66,6 +68,9 @@ function Ast:parseIDList(nodeType, atLeastOne) break end else + if not greedy then + break + end if tp == 'Word' and self:isKeyWord(token) then break end diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index 4950f0d..469a46c 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -12,8 +12,19 @@ local LocalDef = class.declare('LuaParser.Node.LocalDef', 'LuaParser.Node.Base') ---@field index integer ---@field value? LuaParser.Node.Exp ---@field refs? LuaParser.Node.Var[] +---@field attr? LuaParser.Node.Attr local Local = class.declare('LuaParser.Node.Local', 'LuaParser.Node.Base') +---@class LuaParser.Node.Attr: LuaParser.Node.Base +---@field name LuaParser.Node.AttrName +---@field symbolPos? integer # > 的位置 +local Attr = class.declare('LuaParser.Node.Attr', 'LuaParser.Node.Base') + +---@class LuaParser.Node.AttrName: LuaParser.Node.Base +---@field parent LuaParser.Node.Attr +---@field id string +local AttrName = class.declare('LuaParser.Node.AttrName', 'LuaParser.Node.Base') + ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' @@ -34,7 +45,7 @@ function Ast:parseLocal() refs = {}, }) - local vars = self:parseIDList('LuaParser.Node.Local', true) + local vars = self:parseLocalList(true) localdef.vars = vars for i = 1, #vars do local var = vars[i] @@ -64,3 +75,89 @@ function Ast:parseLocal() return localdef end + +---@param parseAttr? boolean +---@return LuaParser.Node.Local[] +function Ast:parseLocalList(parseAttr) + ---@type LuaParser.Node.ID[] + local list = {} + local first = self:parseID('LuaParser.Node.Local', true) + list[#list+1] = first + if parseAttr then + self:skipSpace() + local attr = self:parseLocalAttr() + if attr then + first.attr = attr + first.finish = attr.finish + end + end + while true do + self:skipSpace() + local token, tp = self.lexer:peek() + if not token then + break + end + if tp == 'Symbol' then + if token == ',' then + self.lexer:next() + self:skipSpace() + else + break + end + else + break + end + local loc = self:parseID('LuaParser.Node.Local', true) + if loc then + list[#list+1] = loc + if parseAttr then + self:skipSpace() + local attr = self:parseLocalAttr() + if attr then + loc.attr = attr + loc.finish = attr.finish + end + end + end + end + return list +end + +---@return LuaParser.Node.Attr? +function Ast:parseLocalAttr() + local pos = self.lexer:consume '<' + if not pos then + return nil + end + + self:skipSpace() + local attrName = self:parseID('LuaParser.Node.AttrName', true) + if not attrName then + return nil + end + + local attrNode = self:createNode('LuaParser.Node.Attr', { + start = pos, + name = attrName, + }) + attrName.parent = attrNode + + if attrName.id ~= 'const' + and attrName.id ~= 'close' then + self:throw('UNKNOWN_ATTRIBUTE', attrName.start, attrName.finish) + end + + self:skipSpace() + local symbolPos = self.lexer:consume '>' + attrNode.symbolPos = symbolPos + + attrNode.finish = self:getLastPos() + + if not symbolPos and self.lexer:peek() == '>=' then + local _, _, ltPos = self.lexer:peek() + ---@cast ltPos integer + self:throw('MISS_SPACE_BETWEEN', ltPos, ltPos + 2) + end + + return attrNode +end diff --git a/test/ast/local.lua b/test/ast/local.lua index eed1d6f..fc873e2 100644 --- a/test/ast/local.lua +++ b/test/ast/local.lua @@ -118,3 +118,35 @@ TEST [[local x, y = 1, 2, 3]] }, } } + +TEST [[local x , y < close > = 1, 2]] +{ + vars = { + [1] = { + id = 'x', + attr = { + start = 8, + finish = 15, + symbolPos = 14, + name = { + start = 9, + finish = 14, + id = 'const', + } + } + }, + [2] = { + id = 'y', + attr = { + start = 19, + finish = 28, + symbolPos = 27, + name = { + start = 21, + finish = 26, + id = 'close', + } + } + }, + }, +} From 19eb3f5c22d9c768dc1b51dbc6cda263ffbab6f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 21 Sep 2023 18:26:43 +0800 Subject: [PATCH 44/94] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 2 +- src/parser/ast/block.lua | 12 +++++++- src/parser/ast/do.lua | 4 ++- src/parser/ast/for.lua | 2 +- src/parser/ast/function.lua | 4 +-- src/parser/ast/if.lua | 6 ++-- src/parser/ast/local.lua | 41 ++++++++++++++++++++++----- src/parser/ast/repeat.lua | 2 +- src/parser/ast/while.lua | 2 +- src/parser/compile.lua | 4 +-- test/ast/block.lua | 55 +++++++++++++++++++++++++++++++++++++ test/ast/init.lua | 5 ++++ 12 files changed, 119 insertions(+), 20 deletions(-) diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 54a41ec..c058337 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -58,7 +58,7 @@ function M:__init(code, version, options) ---@type LuaParser.Node.Block[] self.blocks = {} -- 当前代码块 - ---@type LuaParser.Node.Block + ---@type LuaParser.Node.Block? self.curBlock = nil local major, minor = self.version:match 'Lua (%d+)%.(%d+)' diff --git a/src/parser/ast/block.lua b/src/parser/ast/block.lua index b2bdada..2dbc7a2 100644 --- a/src/parser/ast/block.lua +++ b/src/parser/ast/block.lua @@ -2,6 +2,8 @@ local class = require 'class' ---@class LuaParser.Node.Block: LuaParser.Node.Base ---@field childs LuaParser.Node.State[] +---@field locals LuaParser.Node.Local[] +---@field localMap table local Block = class.declare('LuaParser.Node.Block', 'LuaParser.Node.Base') Block.isBlock = true @@ -9,6 +11,14 @@ Block.__getter.childs = function () return {}, true end +Block.__getter.locals = function () + return {}, true +end + +Block.__getter.localMap = function () + return {}, true +end + ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' @@ -35,7 +45,7 @@ function Ast:blockFinish(block) end ---@param block LuaParser.Node.Block -function Ast:parseBlockChilds(block) +function Ast:blockParseChilds(block) while true do local token = self.lexer:peek() if FinishMap[token] then diff --git a/src/parser/ast/do.lua b/src/parser/ast/do.lua index 1600e6b..d4a1dc6 100644 --- a/src/parser/ast/do.lua +++ b/src/parser/ast/do.lua @@ -19,7 +19,9 @@ function Ast:parseDo() }) self:skipSpace() - self:parseBlockChilds(doNode) + self:blockStart(doNode) + self:blockParseChilds(doNode) + self:blockFinish(doNode) self:skipSpace() doNode.symbolPos = self:assertSymbol 'end' diff --git a/src/parser/ast/for.lua b/src/parser/ast/for.lua index ba33a25..8435192 100644 --- a/src/parser/ast/for.lua +++ b/src/parser/ast/for.lua @@ -65,7 +65,7 @@ function Ast:parseFor() forNode.symbolPos2 = symbolPos2 self:skipSpace() - self:parseBlockChilds(forNode) + self:blockParseChilds(forNode) self:skipSpace() local symbolPos3 = self:assertSymbol 'end' diff --git a/src/parser/ast/function.lua b/src/parser/ast/function.lua index d0ea1db..7f5d3a0 100644 --- a/src/parser/ast/function.lua +++ b/src/parser/ast/function.lua @@ -1,6 +1,6 @@ local class = require 'class' ----@class LuaParser.Node.Param: LuaParser.Node.Base +---@class LuaParser.Node.Param: LuaParser.Node.Local ---@field parent LuaParser.Node.Function ---@field index integer ---@field id string @@ -65,7 +65,7 @@ function Ast:parseFunction(isLocal) if symbolPos2 then self:skipSpace() - self:parseBlockChilds(func) + self:blockParseChilds(func) end self:skipSpace() diff --git a/src/parser/ast/if.lua b/src/parser/ast/if.lua index 2514982..1317300 100644 --- a/src/parser/ast/if.lua +++ b/src/parser/ast/if.lua @@ -70,7 +70,7 @@ function Ast:parseIfChildIf() node.symbolPos = self:assertSymbol 'then' self:skipSpace() - self:parseBlockChilds(node) + self:blockParseChilds(node) end node.finish = self:getLastPos() @@ -99,7 +99,7 @@ function Ast:parseIfChildElseIf() node.symbolPos = self:assertSymbol 'then' self:skipSpace() - self:parseBlockChilds(node) + self:blockParseChilds(node) end node.finish = self:getLastPos() @@ -121,7 +121,7 @@ function Ast:parseIfChildElse() start = pos, }) - self:parseBlockChilds(node) + self:blockParseChilds(node) node.finish = self:getLastPos() diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index 469a46c..be4f3d1 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -11,10 +11,19 @@ local LocalDef = class.declare('LuaParser.Node.LocalDef', 'LuaParser.Node.Base') ---@field parent LuaParser.Node.LocalDef | LuaParser.Node.For | LuaParser.Node.Function ---@field index integer ---@field value? LuaParser.Node.Exp ----@field refs? LuaParser.Node.Var[] +---@field gets? LuaParser.Node.Var[] +---@field sets? LuaParser.Node.Var[] ---@field attr? LuaParser.Node.Attr local Local = class.declare('LuaParser.Node.Local', 'LuaParser.Node.Base') +Local.__getter.sets = function () + return {}, true +end + +Local.__getter.gets = function () + return {}, true +end + ---@class LuaParser.Node.Attr: LuaParser.Node.Base ---@field name LuaParser.Node.AttrName ---@field symbolPos? integer # > 的位置 @@ -42,16 +51,10 @@ function Ast:parseLocal() local localdef = self:createNode('LuaParser.Node.LocalDef', { start = pos, - refs = {}, }) local vars = self:parseLocalList(true) localdef.vars = vars - for i = 1, #vars do - local var = vars[i] - var.parent = localdef - var.index = i - end self:skipSpace() local symbolPos = self:assertSymbol '=' @@ -73,9 +76,33 @@ function Ast:parseLocal() localdef.finish = self:getLastPos() + for i = 1, #vars do + local var = vars[i] + var.parent = localdef + var.index = i + self:initLocal(var) + end + return localdef end +---@param loc LuaParser.Node.Local +function Ast:initLocal(loc) + local block = self.curBlock + if not block then + return + end + + block.locals[#block.locals+1] = loc + + local name = loc.id + block.localMap[name] = loc +end + +function Ast:getLocal() + +end + ---@param parseAttr? boolean ---@return LuaParser.Node.Local[] function Ast:parseLocalList(parseAttr) diff --git a/src/parser/ast/repeat.lua b/src/parser/ast/repeat.lua index 22fb61f..10d8f2e 100644 --- a/src/parser/ast/repeat.lua +++ b/src/parser/ast/repeat.lua @@ -20,7 +20,7 @@ function Ast:parseRepeat() }) self:skipSpace() - self:parseBlockChilds(repeatNode) + self:blockParseChilds(repeatNode) self:skipSpace() local symbolPos = self:assertSymbol 'until' diff --git a/src/parser/ast/while.lua b/src/parser/ast/while.lua index 36c2ee4..5dba206 100644 --- a/src/parser/ast/while.lua +++ b/src/parser/ast/while.lua @@ -34,7 +34,7 @@ function Ast:parseWhile() whileNode.symbolPos1 = symbolPos1 self:skipSpace() - self:parseBlockChilds(whileNode) + self:blockParseChilds(whileNode) self:skipSpace() local symbolPos2 = self:assertSymbol 'end' diff --git a/src/parser/compile.lua b/src/parser/compile.lua index 2de5ff0..2368677 100644 --- a/src/parser/compile.lua +++ b/src/parser/compile.lua @@ -3,7 +3,7 @@ local class = require 'class' require 'parser.ast.ast' ---@class LuaParser -local Ast = class.get 'LuaParser' +local LuaParser = class.get 'LuaParser' ---@alias LuaParser.LuaVersion ---| 'Lua 5.1' @@ -29,7 +29,7 @@ local Ast = class.get 'LuaParser' ---@param version? LuaParser.LuaVersion # 默认为 '5.4' ---@param options? LuaParser.CompileOptions ---@return LuaParser.Ast -function Ast.compile(code, version, options) +function LuaParser.compile(code, version, options) local ast = class.new 'LuaParser.Ast' (code, version, options) ast:parseMain() return ast diff --git a/test/ast/block.lua b/test/ast/block.lua index 7823df5..4f3e647 100644 --- a/test/ast/block.lua +++ b/test/ast/block.lua @@ -32,3 +32,58 @@ end }, } } +do return end +TEST [[ +do + local x = x + x = 1 + print(x) +end +]] +{ + childs = { + [1] = { + type = 'LocalDef', + vars = { + [1] = { + id = 'x', + sets = { + [1] = { + left = 20004, + } + }, + gets = { + [1] = { + left = 30010, + } + }, + value = { + loc = NIL, + } + }, + }, + }, + [2] = { + type = 'Assign', + exps = { + [1] = { + id = 'x', + loc = { + left = 10004, + } + } + } + }, + [3] = { + type = 'Call', + args = { + [1] = { + id = 'x', + loc = { + left = 10004, + } + } + } + }, + } +} diff --git a/test/ast/init.lua b/test/ast/init.lua index e456d56..ca0a943 100644 --- a/test/ast/init.lua +++ b/test/ast/init.lua @@ -1,7 +1,12 @@ +NIL = {''} + ---@param result any ---@param expect any function Match(result, expect) for k, v in pairs(expect) do + if v == NIL then + assert(result[k] == nil) + end if type(v) == 'table' then Match(result[k], v) else From 6710459ec19f1a9adf0bbbbef7e3ea0f83407fea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 21 Sep 2023 18:33:32 +0800 Subject: [PATCH 45/94] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 2 ++ src/parser/ast/binary.lua | 1 + src/parser/ast/block.lua | 3 +++ src/parser/ast/boolean.lua | 1 + src/parser/ast/break.lua | 2 ++ src/parser/ast/call.lua | 1 + src/parser/ast/comment.lua | 3 +++ src/parser/ast/do.lua | 1 + src/parser/ast/error.lua | 1 + src/parser/ast/exp.lua | 4 ++++ src/parser/ast/field.lua | 5 +++++ src/parser/ast/for.lua | 1 + src/parser/ast/function.lua | 4 ++++ src/parser/ast/id.lua | 3 +++ src/parser/ast/if.lua | 5 +++++ src/parser/ast/label.lua | 2 ++ src/parser/ast/local.lua | 5 +++++ src/parser/ast/nil.lua | 1 + src/parser/ast/number.lua | 4 ++++ src/parser/ast/repeat.lua | 1 + src/parser/ast/return.lua | 1 + src/parser/ast/state.lua | 3 +++ src/parser/ast/string.lua | 3 +++ src/parser/ast/table.lua | 2 ++ src/parser/ast/unary.lua | 1 + src/parser/ast/var.lua | 2 ++ src/parser/ast/while.lua | 1 + test/ast/Nil.lua | 1 + test/ast/block.lua | 1 + test/ast/boolean.lua | 1 + test/ast/comment.lua | 1 + test/ast/exp.lua | 1 + test/ast/local.lua | 1 + test/ast/number.lua | 1 + test/ast/state.lua | 1 + test/ast/string.lua | 1 + test/ast/table.lua | 1 + 37 files changed, 73 insertions(+) diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index c058337..bc3b78d 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -129,6 +129,7 @@ function M:skipSpace(inExp) end -- 获取上个词的右侧位置(不包括换行符和注释) +---@private ---@return integer function M:getLastPos() local ci = self.lexer.ci @@ -141,6 +142,7 @@ function M:getLastPos() end -- 创建一个节点 +---@private ---@generic T: string ---@param type `T` ---@param data table diff --git a/src/parser/ast/binary.lua b/src/parser/ast/binary.lua index 0437845..969b7d3 100644 --- a/src/parser/ast/binary.lua +++ b/src/parser/ast/binary.lua @@ -53,6 +53,7 @@ local Binary = class.declare('LuaParser.Node.Binary', 'LuaParser.Node.Base') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@private ---@param curExp LuaParser.Node.Exp ---@param curLevel? integer ---@param asState? boolean # 是否作为语句解析 diff --git a/src/parser/ast/block.lua b/src/parser/ast/block.lua index 2dbc7a2..bca43cc 100644 --- a/src/parser/ast/block.lua +++ b/src/parser/ast/block.lua @@ -31,12 +31,14 @@ local FinishMap = { [')'] = true, } +---@private ---@param block LuaParser.Node.Block function Ast:blockStart(block) self.blocks[#self.blocks+1] = block self.curBlock = block end +---@private ---@param block LuaParser.Node.Block function Ast:blockFinish(block) assert(self.curBlock == block) @@ -44,6 +46,7 @@ function Ast:blockFinish(block) self.curBlock = self.blocks[#self.blocks] end +---@private ---@param block LuaParser.Node.Block function Ast:blockParseChilds(block) while true do diff --git a/src/parser/ast/boolean.lua b/src/parser/ast/boolean.lua index 62c30c4..1029c8e 100644 --- a/src/parser/ast/boolean.lua +++ b/src/parser/ast/boolean.lua @@ -8,6 +8,7 @@ local Boolean = class.declare('LuaParser.Node.Boolean', 'LuaParser.Node.Literal' local Ast = class.declare 'LuaParser.Ast' -- 解析布尔值 +---@private ---@return LuaParser.Node.Boolean? function Ast:parseBoolean() local token = self.lexer:peek() diff --git a/src/parser/ast/break.lua b/src/parser/ast/break.lua index b68d5d7..dfcc4fd 100644 --- a/src/parser/ast/break.lua +++ b/src/parser/ast/break.lua @@ -9,6 +9,7 @@ local Continue = class.declare('LuaParser.Node.Continue', 'LuaParser.Node.Base') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@private ---@return LuaParser.Node.Break? function Ast:parseBreak() local pos = self.lexer:consume 'break' @@ -22,6 +23,7 @@ function Ast:parseBreak() }) end +---@private ---@return LuaParser.Node.Continue? function Ast:parseContinue() if not self.nssymbolMap['continue'] then diff --git a/src/parser/ast/call.lua b/src/parser/ast/call.lua index ee73066..0ca0141 100644 --- a/src/parser/ast/call.lua +++ b/src/parser/ast/call.lua @@ -9,6 +9,7 @@ local Call = class.declare('LuaParser.Node.Call', 'LuaParser.Node.Base') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@private ---@param last LuaParser.Node.Term ---@return LuaParser.Node.Call? function Ast:parseCall(last) diff --git a/src/parser/ast/comment.lua b/src/parser/ast/comment.lua index 70abd20..0cc8eae 100644 --- a/src/parser/ast/comment.lua +++ b/src/parser/ast/comment.lua @@ -25,6 +25,7 @@ end ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@private ---@param inExp? boolean ---@return LuaParser.Node.Comment? function Ast:parseComment(inExp) @@ -32,6 +33,7 @@ function Ast:parseComment(inExp) or self:parseShortComment(inExp) end +---@private ---@param inExp? boolean ---@return LuaParser.Node.Comment? function Ast:parseShortComment(inExp) @@ -56,6 +58,7 @@ function Ast:parseShortComment(inExp) return nil end +---@private ---@return LuaParser.Node.Comment? function Ast:parseLongComment() local token, _, pos = self.lexer:peek() diff --git a/src/parser/ast/do.lua b/src/parser/ast/do.lua index d4a1dc6..df4509d 100644 --- a/src/parser/ast/do.lua +++ b/src/parser/ast/do.lua @@ -7,6 +7,7 @@ local Do = class.declare('LuaParser.Node.Do', 'LuaParser.Node.Block') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@private ---@return LuaParser.Node.Do? function Ast:parseDo() local pos = self.lexer:consume 'do' diff --git a/src/parser/ast/error.lua b/src/parser/ast/error.lua index 472c0c5..8714644 100644 --- a/src/parser/ast/error.lua +++ b/src/parser/ast/error.lua @@ -39,6 +39,7 @@ function Ast:throwMissExp(start) end -- 断言下个符号,如果成功则消耗,否则报错 +---@private ---@param symbol string ---@return integer? pos function Ast:assertSymbol(symbol) diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index ffa68c3..901b9f9 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -46,6 +46,7 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.Binary -- 解析表达式 +---@private ---@param required? boolean ---@param curLevel? integer # 新表达式的符号优先级必须比这个大 ---@param asState? boolean # 是否作为语句解析 @@ -80,6 +81,7 @@ function Ast:parseExp(required, asState, curLevel) end -- 解析表达式列表,以逗号分隔 +---@private ---@param atLeastOne? boolean ---@param greedy? boolean ---@return LuaParser.Node.Exp[] @@ -138,6 +140,7 @@ end ---| LuaParser.Node.Call -- 解析表达式中的一项 +---@private ---@return LuaParser.Node.Term? function Ast:parseTerm() ---@type LuaParser.Node.TermHead? @@ -174,6 +177,7 @@ function Ast:parseTerm() return current end +---@private ---@return LuaParser.Node.Paren? function Ast:parseParen() local pos = self.lexer:consume '(' diff --git a/src/parser/ast/field.lua b/src/parser/ast/field.lua index c6cd06e..ff4f2ca 100644 --- a/src/parser/ast/field.lua +++ b/src/parser/ast/field.lua @@ -32,6 +32,7 @@ local TableFieldID = class.declare('LuaParser.Node.TableFieldID', 'LuaParser.Nod ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@private ---@param last LuaParser.Node.Term ---@return LuaParser.Node.Field? function Ast:parseField(last) @@ -88,6 +89,7 @@ function Ast:parseField(last) return nil end +---@private ---@return LuaParser.Node.TableField? function Ast:parseTableField() return self:parseTableFieldAsField() @@ -95,6 +97,7 @@ function Ast:parseTableField() or self:parseTableFieldAsExp() end +---@private ---@return LuaParser.Node.TableField? function Ast:parseTableFieldAsField() local savePoint = self.lexer:savePoint() @@ -123,6 +126,7 @@ function Ast:parseTableFieldAsField() return tfield end +---@private ---@return LuaParser.Node.TableField? function Ast:parseTableFieldAsIndex() local token, _, pos = self.lexer:peek() @@ -162,6 +166,7 @@ function Ast:parseTableFieldAsIndex() return tfield end +---@private ---@return LuaParser.Node.TableField? function Ast:parseTableFieldAsExp() local exp = self:parseExp() diff --git a/src/parser/ast/for.lua b/src/parser/ast/for.lua index 8435192..937bec9 100644 --- a/src/parser/ast/for.lua +++ b/src/parser/ast/for.lua @@ -16,6 +16,7 @@ For.exps = {} ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@private ---@return LuaParser.Node.For? function Ast:parseFor() local pos = self.lexer:consume 'for' diff --git a/src/parser/ast/function.lua b/src/parser/ast/function.lua index 7f5d3a0..75671c0 100644 --- a/src/parser/ast/function.lua +++ b/src/parser/ast/function.lua @@ -22,6 +22,7 @@ local Function = class.declare('LuaParser.Node.Function', 'LuaParser.Node.Block' ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@private ---@param isLocal? boolean ---@return LuaParser.Node.Function? function Ast:parseFunction(isLocal) @@ -90,6 +91,7 @@ function Ast:parseFunction(isLocal) return func end +---@private ---@return LuaParser.Node.FuncName? function Ast:parseFunctionName() local head = self:parseVar() @@ -116,6 +118,7 @@ function Ast:parseFunctionName() return current end +---@private ---@return LuaParser.Node.Param[] function Ast:parseParamList() ---@type LuaParser.Node.Param[] @@ -148,6 +151,7 @@ function Ast:parseParamList() return list end +---@private ---@return LuaParser.Node.Param? function Ast:parseParam() local param = self:parseID('LuaParser.Node.Param') diff --git a/src/parser/ast/id.lua b/src/parser/ast/id.lua index ea74cdd..9fa2faa 100644 --- a/src/parser/ast/id.lua +++ b/src/parser/ast/id.lua @@ -12,6 +12,7 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.LabelName ---| LuaParser.Node.AttrName +---@private ---@generic T: LuaParser.Node.ID ---@param nodeType `T` ---@param required? true @@ -44,6 +45,7 @@ function Ast:parseID(nodeType, required) }) end +---@private ---@generic T: LuaParser.Node.ID ---@param nodeType `T` ---@param atLeastOne? boolean @@ -109,6 +111,7 @@ Ast.keyWordMap = { ['while'] = true, } +---@private ---@param word string ---@return boolean function Ast:isKeyWord(word) diff --git a/src/parser/ast/if.lua b/src/parser/ast/if.lua index 1317300..3deab13 100644 --- a/src/parser/ast/if.lua +++ b/src/parser/ast/if.lua @@ -13,6 +13,7 @@ local IfChild = class.declare('LuaParser.Node.IfChild', 'LuaParser.Node.Block') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@private ---@return LuaParser.Node.If? function Ast:parseIf() local token, _, pos = self.lexer:peek() @@ -42,6 +43,7 @@ function Ast:parseIf() return ifNode end +---@private ---@return LuaParser.Node.IfChild? function Ast:parseIfChild() return self:parseIfChildIf() @@ -49,6 +51,7 @@ function Ast:parseIfChild() or self:parseIfChildElse() end +---@private ---@return LuaParser.Node.IfChild? function Ast:parseIfChildIf() local pos = self.lexer:consume 'if' @@ -78,6 +81,7 @@ function Ast:parseIfChildIf() return node end +---@private ---@return LuaParser.Node.IfChild? function Ast:parseIfChildElseIf() local pos = self.lexer:consume 'elseif' @@ -107,6 +111,7 @@ function Ast:parseIfChildElseIf() return node end +---@private ---@return LuaParser.Node.IfChild? function Ast:parseIfChildElse() local pos = self.lexer:consume 'else' diff --git a/src/parser/ast/label.lua b/src/parser/ast/label.lua index 268ef07..11490d5 100644 --- a/src/parser/ast/label.lua +++ b/src/parser/ast/label.lua @@ -17,6 +17,7 @@ local Goto = class.declare('LuaParser.Node.Goto', 'LuaParser.Node.Base') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@private ---@return LuaParser.Node.Label? function Ast:parseLabel() local pos = self.lexer:consume '::' @@ -45,6 +46,7 @@ function Ast:parseLabel() return label end +---@private ---@return LuaParser.Node.Goto? function Ast:parseGoto() if not self:isKeyWord 'goto' then diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index be4f3d1..75364d3 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -37,6 +37,7 @@ local AttrName = class.declare('LuaParser.Node.AttrName', 'LuaParser.Node.Base') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@private ---@return (LuaParser.Node.LocalDef | LuaParser.Node.Function)? function Ast:parseLocal() local pos = self.lexer:consume 'local' @@ -86,6 +87,7 @@ function Ast:parseLocal() return localdef end +---@private ---@param loc LuaParser.Node.Local function Ast:initLocal(loc) local block = self.curBlock @@ -99,10 +101,12 @@ function Ast:initLocal(loc) block.localMap[name] = loc end +---@private function Ast:getLocal() end +---@private ---@param parseAttr? boolean ---@return LuaParser.Node.Local[] function Ast:parseLocalList(parseAttr) @@ -150,6 +154,7 @@ function Ast:parseLocalList(parseAttr) return list end +---@private ---@return LuaParser.Node.Attr? function Ast:parseLocalAttr() local pos = self.lexer:consume '<' diff --git a/src/parser/ast/nil.lua b/src/parser/ast/nil.lua index ab57750..2605e42 100644 --- a/src/parser/ast/nil.lua +++ b/src/parser/ast/nil.lua @@ -10,6 +10,7 @@ Nil.isTruly = false local Ast = class.declare 'LuaParser.Ast' -- 解析 nil +---@private ---@return LuaParser.Node.Nil? function Ast:parseNil() local token = self.lexer:peek() diff --git a/src/parser/ast/number.lua b/src/parser/ast/number.lua index a4ea010..e8766df 100644 --- a/src/parser/ast/number.lua +++ b/src/parser/ast/number.lua @@ -110,6 +110,7 @@ end local Ast = class.declare 'LuaParser.Ast' -- 解析数字(可以带负号) +---@private ---@return LuaParser.Node.Float | LuaParser.Node.Integer | nil function Ast:parseNumber() -- 快速判断是否为数字 @@ -227,6 +228,7 @@ function Ast:buildInteger(value, startPos, curOffset) end -- 解析十六进制数字(不支持负号) +---@private ---@return LuaParser.Node.Float | LuaParser.Node.Integer | nil function Ast:parseNumber16() local token, _, pos = self.lexer:peek() @@ -278,6 +280,7 @@ function Ast:parseNumber16() end -- 解析二进制数字(不支持负号) +---@private ---@return LuaParser.Node.Integer | nil function Ast:parseNumber2() local token, _, pos = self.lexer:peek() @@ -305,6 +308,7 @@ function Ast:parseNumber2() end -- 解析十进制数字(不支持负号) +---@private ---@return LuaParser.Node.Float | LuaParser.Node.Integer | nil function Ast:parseNumber10() local token, tp, pos = self.lexer:peek() diff --git a/src/parser/ast/repeat.lua b/src/parser/ast/repeat.lua index 10d8f2e..e8d60ec 100644 --- a/src/parser/ast/repeat.lua +++ b/src/parser/ast/repeat.lua @@ -8,6 +8,7 @@ local Repeat = class.declare('LuaParser.Node.Repeat', 'LuaParser.Node.Block') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@private ---@return LuaParser.Node.Repeat? function Ast:parseRepeat() local pos = self.lexer:consume 'repeat' diff --git a/src/parser/ast/return.lua b/src/parser/ast/return.lua index 0908de6..31266b7 100644 --- a/src/parser/ast/return.lua +++ b/src/parser/ast/return.lua @@ -7,6 +7,7 @@ local Return = class.declare('LuaParser.Node.Return', 'LuaParser.Node.Base') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@private ---@return LuaParser.Node.Return? function Ast:parseReturn() local pos = self.lexer:consume 'return' diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua index 72a2781..207030d 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state.lua @@ -28,6 +28,7 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.Repeat ---| LuaParser.Node.Function +---@private ---@return LuaParser.Node.State? function Ast:parseState() local token = self.lexer:peek() @@ -90,6 +91,7 @@ end ---| LuaParser.Node.Assign ---| LuaParser.Node.SingleExp +---@private ---@return LuaParser.Node.StateStartWithExp? function Ast:parseStateStartWithExp() local exp = self:parseExp(false, true) @@ -122,6 +124,7 @@ function Ast:parseStateStartWithExp() return state end +---@private ---@param first LuaParser.Node.Field ---@return LuaParser.Node.Assign? function Ast:parseAssign(first) diff --git a/src/parser/ast/string.lua b/src/parser/ast/string.lua index 0d7c780..5e8359b 100644 --- a/src/parser/ast/string.lua +++ b/src/parser/ast/string.lua @@ -56,6 +56,7 @@ end local Ast = class.declare 'LuaParser.Ast' -- 解析字符串 +---@private ---@return LuaParser.Node.String? function Ast:parseString() local token = self.lexer:peek() @@ -71,6 +72,7 @@ function Ast:parseString() end -- 解析短字符串 +---@private ---@return LuaParser.Node.String? function Ast:parseShortString() local quo, _, pos = self.lexer:peek() @@ -255,6 +257,7 @@ function Ast:parseShortString() end -- 解析长字符串 +---@private ---@return LuaParser.Node.String? function Ast:parseLongString() local _, _, pos = self.lexer:peek() diff --git a/src/parser/ast/table.lua b/src/parser/ast/table.lua index 4927b28..db0e707 100644 --- a/src/parser/ast/table.lua +++ b/src/parser/ast/table.lua @@ -7,6 +7,7 @@ local Table = class.declare('LuaParser.Node.Table', 'LuaParser.Node.Base') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@private ---@return LuaParser.Node.Table? function Ast:parseTable() local pos = self.lexer:consume '{' @@ -44,6 +45,7 @@ function Ast:parseTable() return table end +---@private ---@return LuaParser.Node.TableField[] function Ast:parseTableFields() local fields = {} diff --git a/src/parser/ast/unary.lua b/src/parser/ast/unary.lua index 6ba0694..9ab1a1b 100644 --- a/src/parser/ast/unary.lua +++ b/src/parser/ast/unary.lua @@ -22,6 +22,7 @@ local Unary = class.declare('LuaParser.Node.Unary', 'LuaParser.Node.Base') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@private ---@return LuaParser.Node.Unary? ---@return integer? opLevel function Ast:parseUnary() diff --git a/src/parser/ast/var.lua b/src/parser/ast/var.lua index 0be858f..495d9a9 100644 --- a/src/parser/ast/var.lua +++ b/src/parser/ast/var.lua @@ -14,6 +14,7 @@ local Varargs = class.declare('LuaParser.Node.Varargs', 'LuaParser.Node.Base') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@private ---@return LuaParser.Node.Var? function Ast:parseVar() local var = self:parseID('LuaParser.Node.Var') @@ -24,6 +25,7 @@ function Ast:parseVar() return var end +---@private ---@return LuaParser.Node.Varargs? function Ast:parseVarargs() local pos = self.lexer:consume '...' diff --git a/src/parser/ast/while.lua b/src/parser/ast/while.lua index 5dba206..b31262e 100644 --- a/src/parser/ast/while.lua +++ b/src/parser/ast/while.lua @@ -9,6 +9,7 @@ local While = class.declare('LuaParser.Node.While', 'LuaParser.Node.Block') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@private ---@return LuaParser.Node.While? function Ast:parseWhile() local pos = self.lexer:consume 'while' diff --git a/test/ast/Nil.lua b/test/ast/Nil.lua index ba3007d..e2008cd 100644 --- a/test/ast/Nil.lua +++ b/test/ast/Nil.lua @@ -2,6 +2,7 @@ local class = require 'class' local function TEST(code) return function (expect) + ---@class LuaParser.Ast local ast = class.new 'LuaParser.Ast' (code) local node = ast:parseNil() assert(node) diff --git a/test/ast/block.lua b/test/ast/block.lua index 4f3e647..d6e6477 100644 --- a/test/ast/block.lua +++ b/test/ast/block.lua @@ -2,6 +2,7 @@ local class = require 'class' local function TEST(code) return function (expect) + ---@class LuaParser.Ast local ast = class.new 'LuaParser.Ast' (code) local node = ast:parseState() assert(node) diff --git a/test/ast/boolean.lua b/test/ast/boolean.lua index 6f5ebb9..48d18e8 100644 --- a/test/ast/boolean.lua +++ b/test/ast/boolean.lua @@ -2,6 +2,7 @@ local class = require 'class' local function TEST(code) return function (expect) + ---@class LuaParser.Ast local ast = class.new 'LuaParser.Ast' (code) local node = ast:parseBoolean() assert(node) diff --git a/test/ast/comment.lua b/test/ast/comment.lua index 431b7c6..ad8af53 100644 --- a/test/ast/comment.lua +++ b/test/ast/comment.lua @@ -2,6 +2,7 @@ local class = require 'class' local function TEST(code) return function (expect) + ---@class LuaParser.Ast local ast = class.new 'LuaParser.Ast' (code) local node = ast:parseComment() assert(node) diff --git a/test/ast/exp.lua b/test/ast/exp.lua index 0e3e631..8b2c2b5 100644 --- a/test/ast/exp.lua +++ b/test/ast/exp.lua @@ -2,6 +2,7 @@ local class = require 'class' local function TEST(code) return function (expect) + ---@class LuaParser.Ast local ast = class.new 'LuaParser.Ast' (code) local node = ast:parseExp() assert(node) diff --git a/test/ast/local.lua b/test/ast/local.lua index fc873e2..2d78ed1 100644 --- a/test/ast/local.lua +++ b/test/ast/local.lua @@ -2,6 +2,7 @@ local class = require 'class' local function TEST(code) return function (expect) + ---@class LuaParser.Ast local ast = class.new 'LuaParser.Ast' (code) local node = ast:parseLocal() assert(node) diff --git a/test/ast/number.lua b/test/ast/number.lua index 4d829fe..3d66ac7 100644 --- a/test/ast/number.lua +++ b/test/ast/number.lua @@ -2,6 +2,7 @@ local class = require 'class' local function TEST(code) return function (expect) + ---@class LuaParser.Ast local ast = class.new 'LuaParser.Ast' (code) local node = ast:parseNumber() assert(node) diff --git a/test/ast/state.lua b/test/ast/state.lua index 6984f9c..ac19bb5 100644 --- a/test/ast/state.lua +++ b/test/ast/state.lua @@ -2,6 +2,7 @@ local class = require 'class' local function TEST(code) return function (expect) + ---@class LuaParser.Ast local ast = class.new 'LuaParser.Ast' (code) local node = ast:parseState() assert(node) diff --git a/test/ast/string.lua b/test/ast/string.lua index f998837..51dd433 100644 --- a/test/ast/string.lua +++ b/test/ast/string.lua @@ -2,6 +2,7 @@ local class = require 'class' local function TEST(code) return function (expect) + ---@class LuaParser.Ast local ast = class.new 'LuaParser.Ast' (code) local node = ast:parseString() assert(node) diff --git a/test/ast/table.lua b/test/ast/table.lua index 3fed4ac..daf3801 100644 --- a/test/ast/table.lua +++ b/test/ast/table.lua @@ -2,6 +2,7 @@ local class = require 'class' local function TEST(code) return function (expect) + ---@class LuaParser.Ast local ast = class.new 'LuaParser.Ast' (code) local node = ast:parseTable() assert(node) From e5025c877e5e887d664924bbdfa14cbc60b49ca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 21 Sep 2023 18:39:46 +0800 Subject: [PATCH 46/94] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/local.lua | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index 75364d3..bffbb08 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -102,8 +102,17 @@ function Ast:initLocal(loc) end ---@private -function Ast:getLocal() - +---@param name string +---@return LuaParser.Node.Local? +function Ast:getLocal(name) + for i = #self.blocks, 1, -1 do + local block = self.blocks[i] + local loc = block.localMap[name] + if loc then + return loc + end + end + return nil end ---@private From dc9c2dfc618303ddf346fd4dd20ceff839e17c16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Fri, 22 Sep 2023 14:31:21 +0800 Subject: [PATCH 47/94] =?UTF-8?q?=E8=AE=B0=E5=BD=95=E5=B1=80=E9=83=A8?= =?UTF-8?q?=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/base.lua | 4 ++-- src/parser/ast/block.lua | 23 +++++++++++++++--- src/parser/ast/function.lua | 2 ++ src/parser/ast/local.lua | 47 +++++++++++++++++++++++++++++-------- src/parser/ast/var.lua | 6 +++++ test/ast/block.lua | 6 ++--- test/ast/init.lua | 1 + 7 files changed, 71 insertions(+), 18 deletions(-) diff --git a/src/parser/ast/base.lua b/src/parser/ast/base.lua index 34d39e0..95cac0c 100644 --- a/src/parser/ast/base.lua +++ b/src/parser/ast/base.lua @@ -116,8 +116,8 @@ end ---@return LuaParser.Node.Function ---@return true Base.__getter.parentFunction = function (self) - local parent = self.parentFunction - if parent.type == 'function' then + local parent = self.parent + if parent.isFunction then return parent, true end return parent.parentFunction, true diff --git a/src/parser/ast/block.lua b/src/parser/ast/block.lua index bca43cc..3967cea 100644 --- a/src/parser/ast/block.lua +++ b/src/parser/ast/block.lua @@ -3,8 +3,9 @@ local class = require 'class' ---@class LuaParser.Node.Block: LuaParser.Node.Base ---@field childs LuaParser.Node.State[] ---@field locals LuaParser.Node.Local[] ----@field localMap table +---@field private localMap table local Block = class.declare('LuaParser.Node.Block', 'LuaParser.Node.Base') + Block.isBlock = true Block.__getter.childs = function () @@ -15,8 +16,24 @@ Block.__getter.locals = function () return {}, true end -Block.__getter.localMap = function () - return {}, true +---@param self LuaParser.Node.Block +---@return table +---@return true +Block.__getter.localMap = function (self) + local blocks = self.ast.blocks + ---@class LuaParser.Node.Block + local parentBlock = blocks[#blocks - 1] + if not parentBlock then + return {}, true + end + local parentLocalMap = parentBlock.localMap + return setmetatable({}, { + __index = function (t, k) + local v = parentLocalMap[k] or false + t[k] = v + return v + end + }), true end ---@class LuaParser.Ast diff --git a/src/parser/ast/function.lua b/src/parser/ast/function.lua index 75671c0..d4d09ac 100644 --- a/src/parser/ast/function.lua +++ b/src/parser/ast/function.lua @@ -19,6 +19,8 @@ local Param = class.declare('LuaParser.Node.Param', 'LuaParser.Node.Base') ---@field symbolPos3? integer # `end` local Function = class.declare('LuaParser.Node.Function', 'LuaParser.Node.Block') +Function.isFunction = true + ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index bffbb08..cb69c09 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -11,17 +11,45 @@ local LocalDef = class.declare('LuaParser.Node.LocalDef', 'LuaParser.Node.Base') ---@field parent LuaParser.Node.LocalDef | LuaParser.Node.For | LuaParser.Node.Function ---@field index integer ---@field value? LuaParser.Node.Exp +---@field refs? LuaParser.Node.Var[] ---@field gets? LuaParser.Node.Var[] ---@field sets? LuaParser.Node.Var[] ---@field attr? LuaParser.Node.Attr local Local = class.declare('LuaParser.Node.Local', 'LuaParser.Node.Base') -Local.__getter.sets = function () +-- 所有的引用对象 +Local.__getter.refs = function () return {}, true end -Local.__getter.gets = function () - return {}, true +-- 所有的赋值对象 +---@param self LuaParser.Node.Local +---@return LuaParser.Node.Var[] +---@return true +Local.__getter.sets = function (self) + local sets = {} + for _, ref in ipairs(self.refs) do + local parent = ref.parent + if parent.type == 'Assign' then + sets[#sets+1] = ref + end + end + return sets, true +end + +-- 所有的获取对象 +---@param self LuaParser.Node.Local +---@return LuaParser.Node.Var[] +---@return true +Local.__getter.gets = function (self) + local gets = {} + for _, ref in ipairs(self.refs) do + local parent = ref.parent + if parent.type ~= 'Assign' then + gets[#gets+1] = ref + end + end + return gets, true end ---@class LuaParser.Node.Attr: LuaParser.Node.Base @@ -90,6 +118,7 @@ end ---@private ---@param loc LuaParser.Node.Local function Ast:initLocal(loc) + ---@class LuaParser.Node.Block local block = self.curBlock if not block then return @@ -105,14 +134,12 @@ end ---@param name string ---@return LuaParser.Node.Local? function Ast:getLocal(name) - for i = #self.blocks, 1, -1 do - local block = self.blocks[i] - local loc = block.localMap[name] - if loc then - return loc - end + ---@class LuaParser.Node.Block + local block = self.curBlock + if not block then + return nil end - return nil + return block.localMap[name] or nil end ---@private diff --git a/src/parser/ast/var.lua b/src/parser/ast/var.lua index 495d9a9..9f78c32 100644 --- a/src/parser/ast/var.lua +++ b/src/parser/ast/var.lua @@ -22,6 +22,12 @@ function Ast:parseVar() return nil end + local loc = self:getLocal(var.id) + if loc then + var.loc = loc + loc.refs[#loc.refs+1] = var + end + return var end diff --git a/test/ast/block.lua b/test/ast/block.lua index d6e6477..15c3fdb 100644 --- a/test/ast/block.lua +++ b/test/ast/block.lua @@ -33,7 +33,7 @@ end }, } } -do return end + TEST [[ do local x = x @@ -70,7 +70,7 @@ end [1] = { id = 'x', loc = { - left = 10004, + left = 10010, } } } @@ -81,7 +81,7 @@ end [1] = { id = 'x', loc = { - left = 10004, + left = 10010, } } } diff --git a/test/ast/init.lua b/test/ast/init.lua index ca0a943..e1ee501 100644 --- a/test/ast/init.lua +++ b/test/ast/init.lua @@ -6,6 +6,7 @@ function Match(result, expect) for k, v in pairs(expect) do if v == NIL then assert(result[k] == nil) + return end if type(v) == 'table' then Match(result[k], v) From 5cf88530862aa7b9b6182d8ac83bd498b056625f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Fri, 22 Sep 2023 15:50:32 +0800 Subject: [PATCH 48/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=20if/for/repeat/while?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/for.lua | 3 + src/parser/ast/if.lua | 6 + src/parser/ast/local.lua | 18 +++ src/parser/ast/repeat.lua | 4 +- src/parser/ast/while.lua | 2 + test/ast/block.lua | 315 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 347 insertions(+), 1 deletion(-) diff --git a/src/parser/ast/for.lua b/src/parser/ast/for.lua index 937bec9..288029d 100644 --- a/src/parser/ast/for.lua +++ b/src/parser/ast/for.lua @@ -66,7 +66,10 @@ function Ast:parseFor() forNode.symbolPos2 = symbolPos2 self:skipSpace() + self:blockStart(forNode) + self:initLocals(vars) self:blockParseChilds(forNode) + self:blockFinish(forNode) self:skipSpace() local symbolPos3 = self:assertSymbol 'end' diff --git a/src/parser/ast/if.lua b/src/parser/ast/if.lua index 3deab13..3e1d942 100644 --- a/src/parser/ast/if.lua +++ b/src/parser/ast/if.lua @@ -73,7 +73,9 @@ function Ast:parseIfChildIf() node.symbolPos = self:assertSymbol 'then' self:skipSpace() + self:blockStart(node) self:blockParseChilds(node) + self:blockFinish(node) end node.finish = self:getLastPos() @@ -103,7 +105,9 @@ function Ast:parseIfChildElseIf() node.symbolPos = self:assertSymbol 'then' self:skipSpace() + self:blockStart(node) self:blockParseChilds(node) + self:blockFinish(node) end node.finish = self:getLastPos() @@ -126,7 +130,9 @@ function Ast:parseIfChildElse() start = pos, }) + self:blockStart(node) self:blockParseChilds(node) + self:blockFinish(node) node.finish = self:getLastPos() diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index cb69c09..ca2ccbe 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -130,6 +130,24 @@ function Ast:initLocal(loc) block.localMap[name] = loc end +---@private +---@param locs LuaParser.Node.Local[] +function Ast:initLocals(locs) + ---@class LuaParser.Node.Block + local block = self.curBlock + if not block then + return + end + + for i = 1, #locs do + local loc = locs[i] + block.locals[#block.locals+1] = loc + + local name = loc.id + block.localMap[name] = loc + end +end + ---@private ---@param name string ---@return LuaParser.Node.Local? diff --git a/src/parser/ast/repeat.lua b/src/parser/ast/repeat.lua index e8d60ec..700b9dd 100644 --- a/src/parser/ast/repeat.lua +++ b/src/parser/ast/repeat.lua @@ -17,10 +17,11 @@ function Ast:parseRepeat() end local repeatNode = self:createNode('LuaParser.Node.Repeat', { - start = pos, + start = pos, }) self:skipSpace() + self:blockStart(repeatNode) self:blockParseChilds(repeatNode) self:skipSpace() @@ -35,6 +36,7 @@ function Ast:parseRepeat() condition.parent = repeatNode end end + self:blockFinish(repeatNode) repeatNode.finish = self:getLastPos() diff --git a/src/parser/ast/while.lua b/src/parser/ast/while.lua index b31262e..3a73ba3 100644 --- a/src/parser/ast/while.lua +++ b/src/parser/ast/while.lua @@ -35,7 +35,9 @@ function Ast:parseWhile() whileNode.symbolPos1 = symbolPos1 self:skipSpace() + self:blockStart(whileNode) self:blockParseChilds(whileNode) + self:blockFinish(whileNode) self:skipSpace() local symbolPos2 = self:assertSymbol 'end' diff --git a/test/ast/block.lua b/test/ast/block.lua index 15c3fdb..deedd0f 100644 --- a/test/ast/block.lua +++ b/test/ast/block.lua @@ -88,3 +88,318 @@ end }, } } + +TEST [[ +do + local x + do + local x = x + print(x) + end +end +]] +{ + type = 'Do', + childs = { + [1] = { + type = 'LocalDef', + vars = { + [1] = { + id = 'x', + } + } + }, + [2] = { + type = 'Do', + childs = { + [1] = { + type = 'LocalDef', + vars = { + [1] = { + id = 'x', + } + }, + values = { + [1] = { + id = 'x', + loc = { + left = 10010, + } + } + } + }, + [2] = { + type = 'Call', + args = { + [1] = { + id = 'x', + loc = { + left = 30014, + } + } + } + } + } + } + } +} + +TEST [[ +do + local x + if x then + local x = x + print(x) + elseif x then + local x = x + print(x) + else + local x = x + print(x) + end +end +]] +{ + type = 'Do', + childs = { + [1] = { + type = 'LocalDef', + vars = { + [1] = { + id = 'x', + } + } + }, + [2] = { + type = 'If', + childs = { + [1] = { + subtype = 'if', + condition = { + id = 'x', + loc = { + left = 10010, + } + }, + childs = { + [1] = { + type = 'LocalDef', + vars = { + [1] = { + id = 'x', + } + }, + values = { + [1] = { + id = 'x', + loc = { + left = 10010, + } + } + } + }, + [2] = { + type = 'Call', + args = { + [1] = { + id = 'x', + loc = { + left = 30014, + } + } + } + } + } + }, + [2] = { + subtype = 'elseif', + condition = { + id = 'x', + loc = { + left = 10010, + } + }, + childs = { + [1] = { + type = 'LocalDef', + vars = { + [1] = { + id = 'x', + } + }, + values = { + [1] = { + id = 'x', + loc = { + left = 10010, + } + } + } + }, + [2] = { + type = 'Call', + args = { + [1] = { + id = 'x', + loc = { + left = 60014, + } + } + } + } + } + }, + [3] = { + subtype = 'else', + childs = { + [1] = { + type = 'LocalDef', + vars = { + [1] = { + id = 'x', + } + }, + values = { + [1] = { + id = 'x', + loc = { + left = 10010, + } + } + } + }, + [2] = { + type = 'Call', + args = { + [1] = { + id = 'x', + loc = { + left = 90014, + } + } + } + } + } + } + } + } + } +} + +TEST [[ +for i = 1, 10 do + print(i) +end +]] +{ + childs = { + [1] = { + type = 'Call', + args = { + [1] = { + id = 'i', + loc = { + left = 4, + } + } + } + } + } +} + +TEST [[ +for k, v in pairs(t) do + print(k, v) +end +]] +{ + childs = { + [1] = { + type = 'Call', + args = { + [1] = { + id = 'k', + loc = { + left = 4, + } + }, + [2] = { + id = 'v', + loc = { + left = 7, + } + } + } + } + } +} + +TEST [[ +while true do + local x + print(x) +end +]] +{ + childs = { + [1] = { + type = 'LocalDef', + vars = { + [1] = { + id = 'x', + } + } + }, + [2] = { + type = 'Call', + args = { + [1] = { + id = 'x', + loc = { + left = 10010, + } + } + } + } + } +} + +TEST [[ +do + local x + repeat + local x + until x +end +]] +{ + childs = { + [1] = { + type = 'LocalDef', + vars = { + [1] = { + id = 'x', + } + } + }, + [2] = { + type = 'Repeat', + condition = { + id = 'x', + loc = { + left = 30014, + } + }, + childs = { + [1] = { + type = 'LocalDef', + vars = { + [1] = { + id = 'x', + } + } + } + } + } + } +} From 01b15b976594ed79f3386fb19c6dcec49fd9fea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Fri, 22 Sep 2023 16:30:13 +0800 Subject: [PATCH 49/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/function.lua | 11 +++++++- test/ast/block.lua | 56 +++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/parser/ast/function.lua b/src/parser/ast/function.lua index d4d09ac..f715355 100644 --- a/src/parser/ast/function.lua +++ b/src/parser/ast/function.lua @@ -4,7 +4,7 @@ local class = require 'class' ---@field parent LuaParser.Node.Function ---@field index integer ---@field id string -local Param = class.declare('LuaParser.Node.Param', 'LuaParser.Node.Base') +local Param = class.declare('LuaParser.Node.Param', 'LuaParser.Node.Local') ---@alias LuaParser.Node.FuncName ---| LuaParser.Node.Var @@ -68,7 +68,16 @@ function Ast:parseFunction(isLocal) if symbolPos2 then self:skipSpace() + self:blockStart(func) + if isLocal and name then + ---@cast name LuaParser.Node.Local + self:initLocal(name) + end + if params then + self:initLocals(params) + end self:blockParseChilds(func) + self:blockFinish(func) end self:skipSpace() diff --git a/test/ast/block.lua b/test/ast/block.lua index deedd0f..67b9ad1 100644 --- a/test/ast/block.lua +++ b/test/ast/block.lua @@ -403,3 +403,59 @@ end } } } + +TEST [[ +function (x, y) + print(x, y) +end +]] +{ + params = { + [1] = { + id = 'x' + }, + [2] = { + id = 'y' + } + }, + childs = { + [1] = { + type = 'Call', + args = { + [1] = { + id = 'x', + loc = { + left = 10, + } + }, + [2] = { + id = 'y', + loc = { + left = 13, + } + } + } + } + } +} + +TEST [[ +local function f() + print(f) +end +]] +{ + childs = { + [1] = { + type = 'Call', + args = { + [1] = { + id = 'f', + loc = { + left = 15, + } + }, + } + } + } +} From 4d9cd4e98e8f47fb7cd02974419958c001b27924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Fri, 22 Sep 2023 18:26:11 +0800 Subject: [PATCH 50/94] =?UTF-8?q?=E5=BC=80=E5=A7=8B=E8=A7=A3=E6=9E=90main?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 23 +++++++--- src/parser/ast/block.lua | 1 + src/parser/ast/local.lua | 6 +++ src/parser/ast/main.lua | 57 ++++++++++++++++++++++++ src/parser/ast/var.lua | 7 +++ src/parser/compile.lua | 1 + test/ast/{lua.lua => ast.lua} | 81 ++++++++++++++++++++++------------- test/ast/init.lua | 1 + 8 files changed, 142 insertions(+), 35 deletions(-) create mode 100644 src/parser/ast/main.lua rename test/ast/{lua.lua => ast.lua} (96%) diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index bc3b78d..f599de5 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -28,8 +28,11 @@ require 'parser.ast.return' require 'parser.ast.for' require 'parser.ast.while' require 'parser.ast.repeat' +require 'parser.ast.main' ---@class LuaParser.Ast +---@field envMode 'fenv' | '_ENV' +---@field main LuaParser.Node.Main ---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast local M = class.declare 'LuaParser.Ast' @@ -65,6 +68,7 @@ function M:__init(code, version, options) ---@type integer self.versionNum = major * 10 + minor + local envMode = 'auto' if options then if options.nonestandardSymbols then for _, s in ipairs(options.nonestandardSymbols) do @@ -75,6 +79,20 @@ function M:__init(code, version, options) self.jit = options.jit -- 是否支持Unicode标识符 self.unicodeName = options.unicodeName + + envMode = options.envMode + end + + if envMode == '_ENV' + or envMode == 'fenv' then + ---@cast envMode '_ENV' | 'fenv' + self.envMode = envMode + else + if self.versionNum >= 52 then + self.envMode = '_ENV' + else + self.envMode = 'fenv' + end end end @@ -152,9 +170,4 @@ function M:createNode(type, data) return class.new(type, data) end --- 编译Lua代码 -function M:parseMain() - self:skipSpace() -end - return M diff --git a/src/parser/ast/block.lua b/src/parser/ast/block.lua index 3967cea..9411656 100644 --- a/src/parser/ast/block.lua +++ b/src/parser/ast/block.lua @@ -71,6 +71,7 @@ function Ast:blockParseChilds(block) if FinishMap[token] then break end + while self.lexer:consume ';' do end local state = self:parseState() if not state then break diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index ca2ccbe..74e2d11 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -14,6 +14,7 @@ local LocalDef = class.declare('LuaParser.Node.LocalDef', 'LuaParser.Node.Base') ---@field refs? LuaParser.Node.Var[] ---@field gets? LuaParser.Node.Var[] ---@field sets? LuaParser.Node.Var[] +---@field envRefs? LuaParser.Node.Var[] ---@field attr? LuaParser.Node.Attr local Local = class.declare('LuaParser.Node.Local', 'LuaParser.Node.Base') @@ -52,6 +53,11 @@ Local.__getter.gets = function (self) return gets, true end +-- _ENV的隐式引用 +Local.__getter.envRefs = function () + return {}, true +end + ---@class LuaParser.Node.Attr: LuaParser.Node.Base ---@field name LuaParser.Node.AttrName ---@field symbolPos? integer # > 的位置 diff --git a/src/parser/ast/main.lua b/src/parser/ast/main.lua new file mode 100644 index 0000000..1b1a412 --- /dev/null +++ b/src/parser/ast/main.lua @@ -0,0 +1,57 @@ +local class = require 'class' + +---@class LuaParser.Node.Main: LuaParser.Node.Function +---@field parent LuaParser.Ast +local Main = class.declare('LuaParser.Node.Main', 'LuaParser.Node.Function') + +Main.isMain = true + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +function Ast:skipShebang() + if self.code:sub(1, 2) == '#!' then + local pos = self.code:find('\n', 3, true) + if pos then + self.lexer:fastForward(pos + 1) + else + self.lexer:fastForward(#self.code + 1) + end + end +end + +function Ast:parseMain() + self:skipShebang() + + local main = self:createNode('LuaParser.Node.Main', { + start = 0, + finish = #self.code, + }) + + self:blockStart(main) + + local vararg = self:createNode('LuaParser.Node.Param', { + start = 0, + finish = 0, + dummy = true, + id = '...', + parent = main, + }) + self:initLocal(vararg) + if self.envMode == '_ENV' then + local env = self:createNode('LuaParser.Node.Local', { + start = 0, + finish = 0, + dummy = true, + id = '_ENV', + parent = main, + }) + self:initLocal(env) + end + + self:skipSpace() + self:blockParseChilds(main) + self:blockFinish(main) + + self.main = main +end diff --git a/src/parser/ast/var.lua b/src/parser/ast/var.lua index 9f78c32..b1c2630 100644 --- a/src/parser/ast/var.lua +++ b/src/parser/ast/var.lua @@ -3,6 +3,7 @@ local class = require 'class' ---@class LuaParser.Node.Var: LuaParser.Node.Base ---@field subtype 'global' | 'local' ---@field id string +---@field env? LuaParser.Node.Local ---@field loc? LuaParser.Node.Local ---@field next? LuaParser.Node.Field ---@field value? LuaParser.Node.Exp @@ -26,6 +27,12 @@ function Ast:parseVar() if loc then var.loc = loc loc.refs[#loc.refs+1] = var + elseif self.envMode == '_ENV' then + local env = self:getLocal('_ENV') + if env then + var.env = env + env.envRefs[#env.envRefs+1] = var + end end return var diff --git a/src/parser/compile.lua b/src/parser/compile.lua index 2368677..05969fa 100644 --- a/src/parser/compile.lua +++ b/src/parser/compile.lua @@ -23,6 +23,7 @@ local LuaParser = class.get 'LuaParser' ---@field jit? boolean # 是否为LuaJIT,默认为 false ---@field nonestandardSymbols? LuaParser.NonestandardSymbol[] # 支持的非标准符号 ---@field unicodeName? boolean # 是否支持Unicode标识符,默认为 false +---@field envMode? 'auto' | '_ENV' | 'fenv' # 环境模式,默认为 'auto',会根据版本自动选择 -- 编译lua代码 ---@param code string # lua代码 diff --git a/test/ast/lua.lua b/test/ast/ast.lua similarity index 96% rename from test/ast/lua.lua rename to test/ast/ast.lua index e171b13..2dd3e7d 100644 --- a/test/ast/lua.lua +++ b/test/ast/ast.lua @@ -1,41 +1,62 @@ -CHECK'' +local function TEST(code) + return function (expect) + local parser = require 'parser' + local ast = parser.compile(code) + assert(ast) + Match(ast, expect) + end +end + +TEST '' { - type = "main", - start = 0, - finish = 0, - locals = "", + main = { + type = 'Main', + start = 0, + finish = 0, + } } -CHECK';;;' +TEST ';;;' { - type = "main", - start = 0, - finish = 3, - locals = "", + main = { + start = 0, + finish = 3, + } } -CHECK';;;x = 1' +TEST ';;;x = 1' { - type = "main", - start = 0, - finish = 8, - locals = "", - [1] = { - type = "setglobal", - start = 3, - finish = 4, - range = 8, - parent = "", - node = "", - value = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 1, + main = { + locals = { + [1] = { + id = '...', + dummy = true, + }, + [2] = { + id = '_ENV', + dummy = true, + envRefs = { + [1] = { + start = 3 + } + } + } }, - [1] = "x", - }, + childs = { + [1] = { + type = 'Assign', + exps = { + [1] = { + id = 'x', + env = { + id = '_ENV', + start = 0, + } + } + } + } + } + } } CHECK'x, y, z = 1, 2, 3' { diff --git a/test/ast/init.lua b/test/ast/init.lua index e1ee501..a4eff03 100644 --- a/test/ast/init.lua +++ b/test/ast/init.lua @@ -28,3 +28,4 @@ require 'test.ast.table' require 'test.ast.exp' require 'test.ast.state' require 'test.ast.block' +require 'test.ast.ast' From 2b9c97d3dd37dff679547841568b04ad3be56377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Mon, 25 Sep 2023 11:58:36 +0800 Subject: [PATCH 51/94] =?UTF-8?q?=E8=B5=8B=E5=80=BC=E6=89=A9=E5=B1=95?= =?UTF-8?q?=E6=9C=80=E5=90=8E=E4=B8=80=E4=B8=AA=E5=87=BD=E6=95=B0=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E6=88=96=E4=B8=8D=E5=AE=9A=E5=8F=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/exp.lua | 1 + src/parser/ast/local.lua | 2 + src/parser/ast/state.lua | 35 +++++++- test/ast/ast.lua | 186 +-------------------------------------- test/ast/state.lua | 32 +++++++ 5 files changed, 67 insertions(+), 189 deletions(-) diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index 901b9f9..0013ba1 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -44,6 +44,7 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.Term ---| LuaParser.Node.Unary ---| LuaParser.Node.Binary +---| LuaParser.Node.Select -- 解析表达式 ---@private diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index 74e2d11..417cb5b 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -97,10 +97,12 @@ function Ast:parseLocal() localdef.symbolPos = symbolPos self:skipSpace() local values = self:parseExpList(true) + self:extendsAssignValues(values, #vars) localdef.values = values for i = 1, #values do local value = values[i] value.parent = localdef + value.index = i local var = vars[i] if var then diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua index 207030d..953f9d6 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state.lua @@ -10,6 +10,10 @@ local Assign = class.declare('LuaParser.Node.Assign', 'LuaParser.Node.Base') ---@field exp LuaParser.Node.Exp local SingleExp = class.declare('LuaParser.Node.SingleExp', 'LuaParser.Node.Base') +---@class LuaParser.Node.Select +---@field index integer +local Select = class.declare('LuaParser.Node.Select', 'LuaParser.Node.Base') + ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' @@ -133,12 +137,13 @@ function Ast:parseAssign(first) return nil end + local exps = {} local assign = self:createNode('LuaParser.Node.Assign', { start = first.start, - exps = {}, + exps = exps, }) - assign.exps[1] = first + exps[1] = first first.parent = assign while true do local comma = self.lexer:consume ',' @@ -150,7 +155,7 @@ function Ast:parseAssign(first) if not exp then break end - assign.exps[#assign.exps+1] = exp + exps[#exps+1] = exp self:skipSpace() end @@ -160,13 +165,15 @@ function Ast:parseAssign(first) assign.symbolPos = eqPos self:skipSpace() local values = self:parseExpList(true) + self:extendsAssignValues(values, #exps) assign.values = values for i = 1, #values do local value = values[i] value.parent = assign + value.index = i - local exp = assign.exps[i] + local exp = exps[i] if exp then exp.value = value end @@ -177,3 +184,23 @@ function Ast:parseAssign(first) return assign end + +---@param values LuaParser.Node.Exp[] +---@param varCount integer +function Ast:extendsAssignValues(values, varCount) + if #values >= varCount then + return + end + local lastValue = values[#values] + if lastValue.type ~= 'Call' and lastValue.type ~= 'Varargs' then + return + end + for i = #values + 1, varCount do + local sel = self:createNode('LuaParser.Node.Select', { + start = lastValue.start, + finish = lastValue.finish, + value = lastValue, + }) + values[i] = sel + end +end diff --git a/test/ast/ast.lua b/test/ast/ast.lua index 2dd3e7d..a1d4b1c 100644 --- a/test/ast/ast.lua +++ b/test/ast/ast.lua @@ -58,191 +58,7 @@ TEST ';;;x = 1' } } } -CHECK'x, y, z = 1, 2, 3' -{ - type = "main", - start = 0, - finish = 17, - locals = "", - [1] = { - type = "setglobal", - start = 0, - finish = 1, - range = 11, - parent = "", - node = "", - value = { - type = "integer", - start = 10, - finish = 11, - parent = "", - [1] = 1, - }, - [1] = "x", - }, - [2] = { - type = "setglobal", - start = 3, - finish = 4, - range = 14, - parent = "", - node = "", - value = { - type = "integer", - start = 13, - finish = 14, - parent = "", - [1] = 2, - }, - [1] = "y", - }, - [3] = { - type = "setglobal", - start = 6, - finish = 7, - range = 17, - parent = "", - node = "", - value = { - type = "integer", - start = 16, - finish = 17, - parent = "", - [1] = 3, - }, - [1] = "z", - }, -} -CHECK'local x, y, z' -{ - type = "main", - start = 0, - finish = 13, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 13, - parent = "", - locPos = 0, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 13, - parent = "", - [1] = "y", - }, - [3] = { - type = "local", - start = 12, - finish = 13, - effect = 13, - parent = "", - [1] = "z", - }, -} -CHECK'local x, y, z = 1, 2, 3' -{ - type = "main", - start = 0, - finish = 23, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 23, - range = 17, - parent = "", - locPos = 0, - value = { - type = "integer", - start = 16, - finish = 17, - parent = "", - [1] = 1, - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 23, - range = 20, - parent = "", - value = { - type = "integer", - start = 19, - finish = 20, - parent = "", - [1] = 2, - }, - [1] = "y", - }, - [3] = { - type = "local", - start = 12, - finish = 13, - effect = 23, - range = 23, - parent = "", - value = { - type = "integer", - start = 22, - finish = 23, - parent = "", - [1] = 3, - }, - [1] = "z", - }, -} -CHECK'local x, y = y, x' -{ - type = "main", - start = 0, - finish = 17, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 17, - range = 14, - parent = "", - locPos = 0, - value = { - type = "getglobal", - start = 13, - finish = 14, - parent = "", - node = "", - [1] = "y", - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 17, - range = 17, - parent = "", - value = { - type = "getglobal", - start = 16, - finish = 17, - parent = "", - node = "", - [1] = "x", - }, - [1] = "y", - }, -} + CHECK'local x, y = f()' { type = "main", diff --git a/test/ast/state.lua b/test/ast/state.lua index ac19bb5..5469307 100644 --- a/test/ast/state.lua +++ b/test/ast/state.lua @@ -136,6 +136,38 @@ TEST [[x.y().z = 1]] } } +TEST [[local x, y = call()]] +{ + values = { + [1] = { + type = 'Call', + }, + [2] = { + type = 'Select', + index = 2, + value = { + type = 'Call', + }, + }, + } +} + +TEST [[x, y = call()]] +{ + values = { + [1] = { + type = 'Call', + }, + [2] = { + type = 'Select', + index = 2, + value = { + type = 'Call', + }, + }, + } +} + TEST [[:: continue ::]] { type = 'Label', From e42bbf22cfd1ac73a265c9cd1b9aa596cbbfc4ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Tue, 26 Sep 2023 17:43:29 +0800 Subject: [PATCH 52/94] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/ast/ast.lua | 377 --------------------------------------------- test/ast/state.lua | 63 ++++++++ 2 files changed, 63 insertions(+), 377 deletions(-) diff --git a/test/ast/ast.lua b/test/ast/ast.lua index a1d4b1c..5137ab3 100644 --- a/test/ast/ast.lua +++ b/test/ast/ast.lua @@ -59,383 +59,6 @@ TEST ';;;x = 1' } } -CHECK'local x, y = f()' -{ - type = "main", - start = 0, - finish = 16, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 16, - range = 16, - parent = "", - locPos = 0, - value = { - type = "select", - start = 13, - finish = 16, - parent = "", - vararg = "", - sindex = 1, - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 16, - range = 16, - parent = "", - value = { - type = "select", - start = 13, - finish = 16, - parent = "", - vararg = "", - sindex = 2, - }, - [1] = "y", - }, -} -CHECK'local x, y = (f())' -{ - type = "main", - start = 0, - finish = 18, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 18, - range = 18, - parent = "", - locPos = 0, - value = { - type = "paren", - start = 13, - finish = 18, - parent = "", - exp = { - type = "call", - start = 14, - finish = 17, - parent = "", - node = "", - }, - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 18, - parent = "", - [1] = "y", - }, -} -CHECK'local x, y = f(), nil' -{ - type = "main", - start = 0, - finish = 21, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 21, - range = 16, - parent = "", - locPos = 0, - value = { - type = "select", - start = 13, - finish = 16, - parent = "", - vararg = "", - sindex = 1, - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 21, - range = 21, - parent = "", - value = { - type = "nil", - start = 18, - finish = 21, - parent = "", - }, - [1] = "y", - }, -} -CHECK'local x, y = ...' -{ - type = "main", - start = 0, - finish = 16, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 16, - range = 16, - parent = "", - locPos = 0, - value = { - type = "select", - start = 13, - finish = 16, - parent = "", - vararg = "", - sindex = 1, - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 16, - range = 16, - parent = "", - value = { - type = "select", - start = 13, - finish = 16, - parent = "", - vararg = "", - sindex = 2, - }, - [1] = "y", - }, -} -CHECK'local x, y = (...)' -{ - type = "main", - start = 0, - finish = 18, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 18, - range = 18, - parent = "", - locPos = 0, - value = { - type = "paren", - start = 13, - finish = 18, - parent = "", - exp = { - type = "varargs", - start = 14, - finish = 17, - parent = "", - }, - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 18, - parent = "", - [1] = "y", - }, -} -CHECK'local x, y = ..., nil' -{ - type = "main", - start = 0, - finish = 21, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 21, - range = 16, - parent = "", - locPos = 0, - value = { - type = "select", - start = 13, - finish = 16, - parent = "", - vararg = "", - sindex = 1, - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 21, - range = 21, - parent = "", - value = { - type = "nil", - start = 18, - finish = 21, - parent = "", - }, - [1] = "y", - }, -} -CHECK'local x , y = 1' -{ - type = "main", - start = 0, - finish = 30, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 30, - range = 30, - parent = "", - locPos = 0, - value = { - type = "integer", - start = 29, - finish = 30, - parent = "", - [1] = 1, - }, - attrs = { - type = "localattrs", - parent = "", - [1] = { - type = "localattr", - start = 8, - finish = 15, - parent = "", - [1] = "const", - }, - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 17, - finish = 18, - effect = 30, - parent = "", - attrs = { - type = "localattrs", - parent = "", - [1] = { - type = "localattr", - start = 19, - finish = 26, - parent = "", - [1] = "close", - }, - }, - [1] = "y", - }, -} -CHECK[[ -x = 1 -y = 2 -]] -{ - type = "main", - start = 0, - finish = 20000, - locals = "", - [1] = { - type = "setglobal", - start = 0, - finish = 1, - range = 5, - parent = "", - node = "", - value = { - type = "integer", - start = 4, - finish = 5, - parent = "", - [1] = 1, - }, - [1] = "x", - }, - [2] = { - type = "setglobal", - start = 10000, - finish = 10001, - range = 10005, - parent = "", - node = "", - value = { - type = "integer", - start = 10004, - finish = 10005, - parent = "", - [1] = 2, - }, - [1] = "y", - }, -} - -CHECK[[ -x, y = 1, 2 -]] -{ - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "setglobal", - start = 0, - finish = 1, - range = 8, - parent = "", - node = "", - value = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 1, - }, - [1] = "x", - }, - [2] = { - type = "setglobal", - start = 3, - finish = 4, - range = 11, - parent = "", - node = "", - value = { - type = "integer", - start = 10, - finish = 11, - parent = "", - [1] = 2, - }, - [1] = "y", - }, -} CHECK[[ local function a() return diff --git a/test/ast/state.lua b/test/ast/state.lua index 5469307..12f3b55 100644 --- a/test/ast/state.lua +++ b/test/ast/state.lua @@ -168,6 +168,69 @@ TEST [[x, y = call()]] } } +TEST [[x, y = (call())]] +{ + values = { + [1] = { + type = 'Paren' + }, + [2] = NIL, + } +} + +TEST [[x, y, z = call(), nil]] +{ + values = { + [1] = { + type = 'Call' + }, + [2] = { + type = 'Nil' + } + } +} + +TEST [[x, y = ...]] +{ + values = { + [1] = { + type = 'Varargs' + }, + [2] = { + type = 'Select', + index = 2, + value = { + type = 'Varargs', + } + } + } +} + +TEST [[x, y = ..., nil]] +{ + values = { + [1] = { + type = 'Varargs' + }, + [2] = { + type = 'Nil', + } + } +} + +TEST [[x, y = (...)]] +{ + values = { + [1] = { + type = 'Paren', + exp = { + type = 'Varargs', + }, + }, + [2] = NIL, + } +} + TEST [[:: continue ::]] { type = 'Label', From 678549416afd8006a3e09232fe2cb63accb9e5ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Tue, 26 Sep 2023 18:22:25 +0800 Subject: [PATCH 53/94] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/ast/ast.lua | 436 +-------------------------------------------- test/ast/block.lua | 49 +++++ 2 files changed, 56 insertions(+), 429 deletions(-) diff --git a/test/ast/ast.lua b/test/ast/ast.lua index 5137ab3..ec3195c 100644 --- a/test/ast/ast.lua +++ b/test/ast/ast.lua @@ -1,7 +1,10 @@ -local function TEST(code) +---@param code string +---@param optional? LuaParser.CompileOptions +---@return fun(table) +local function TEST(code, optional) return function (expect) local parser = require 'parser' - local ast = parser.compile(code) + local ast = parser.compile(code, nil, optional) assert(ast) Match(ast, expect) end @@ -59,435 +62,10 @@ TEST ';;;x = 1' } } -CHECK[[ -local function a() - return -end]] -{ - type = "main", - start = 0, - finish = 20003, - locals = "", - [1] = { - type = "local", - start = 15, - vstart = 6, - finish = 16, - effect = 16, - range = 20003, - parent = "", - locPos = 0, - value = { - type = "function", - start = 6, - bstart = 18, - finish = 20003, - keyword = { - [1] = 6, - [2] = 14, - [3] = 20000, - [4] = 20003, - }, - parent = "", - args = { - type = "funcargs", - start = 16, - finish = 18, - parent = "", - }, - returns = "", - hasReturn = true, - [1] = { - type = "return", - start = 10004, - finish = 10010, - parent = "", - }, - }, - [1] = "a", - }, -} -CHECK[[ -local function f() - return f() -end]] -{ - type = "main", - start = 0, - finish = 20003, - locals = "", - [1] = { - type = "local", - start = 15, - vstart = 6, - finish = 16, - effect = 16, - range = 20003, - parent = "", - locPos = 0, - value = { - type = "function", - start = 6, - bstart = 18, - finish = 20003, - keyword = { - [1] = 6, - [2] = 14, - [3] = 20000, - [4] = 20003, - }, - parent = "", - args = { - type = "funcargs", - start = 16, - finish = 18, - parent = "", - }, - returns = "", - hasReturn = true, - [1] = { - type = "return", - start = 10004, - finish = 10014, - parent = "", - [1] = { - type = "call", - start = 10011, - finish = 10014, - parent = "", - node = "", - }, - }, - }, - ref = "", - [1] = "f", - }, -} -CHECK[[ -local function a(b, c) - return -end]] -{ - type = "main", - start = 0, - finish = 20003, - locals = "", - [1] = { - type = "local", - start = 15, - vstart = 6, - finish = 16, - effect = 16, - range = 20003, - parent = "", - locPos = 0, - value = { - type = "function", - start = 6, - bstart = 22, - finish = 20003, - keyword = { - [1] = 6, - [2] = 14, - [3] = 20000, - [4] = 20003, - }, - parent = "", - args = { - type = "funcargs", - start = 16, - finish = 22, - parent = "", - [1] = { - type = "local", - start = 17, - finish = 18, - effect = 18, - parent = "", - [1] = "b", - }, - [2] = { - type = "local", - start = 20, - finish = 21, - effect = 21, - parent = "", - [1] = "c", - }, - }, - locals = "", - returns = "", - hasReturn = true, - [1] = { - type = "return", - start = 10004, - finish = 10010, - parent = "", - }, - }, - [1] = "a", - }, -} - -CHECK[[ -local x, y, z = 1, 2 -local function f() -end -y, z = 3, 4 -]] -{ - type = "main", - start = 0, - finish = 40000, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 20, - range = 17, - parent = "", - locPos = 0, - value = { - type = "integer", - start = 16, - finish = 17, - parent = "", - [1] = 1, - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 20, - range = 20, - parent = "", - value = { - type = "integer", - start = 19, - finish = 20, - parent = "", - [1] = 2, - }, - ref = "", - [1] = "y", - }, - [3] = { - type = "local", - start = 12, - finish = 13, - effect = 20, - parent = "", - ref = "", - [1] = "z", - }, - [4] = { - type = "local", - start = 10015, - vstart = 10006, - finish = 10016, - effect = 10016, - range = 20003, - parent = "", - locPos = 10000, - value = { - type = "function", - start = 10006, - bstart = 10018, - finish = 20003, - keyword = { - [1] = 10006, - [2] = 10014, - [3] = 20000, - [4] = 20003, - }, - parent = "", - args = { - type = "funcargs", - start = 10016, - finish = 10018, - parent = "", - }, - }, - [1] = "f", - }, - [5] = { - type = "setlocal", - start = 30000, - finish = 30001, - range = 30008, - parent = "", - node = "", - value = { - type = "integer", - start = 30007, - finish = 30008, - parent = "", - [1] = 3, - }, - [1] = "y", - }, - [6] = { - type = "setlocal", - start = 30003, - finish = 30004, - range = 30011, - parent = "", - node = "", - value = { - type = "integer", - start = 30010, - finish = 30011, - parent = "", - [1] = 4, - }, - [1] = "z", - }, -} -CHECK[[ -local f = require -local z = f -z'xxx' -]] -{ - type = "main", - start = 0, - finish = 30000, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 17, - range = 17, - special = "require", - parent = "", - locPos = 0, - value = { - type = "getglobal", - start = 10, - finish = 17, - special = "require", - parent = "", - node = "", - [1] = "require", - }, - ref = "", - [1] = "f", - }, - [2] = { - type = "local", - start = 10006, - finish = 10007, - effect = 10011, - range = 10011, - special = "require", - parent = "", - locPos = 10000, - value = { - type = "getlocal", - start = 10010, - finish = 10011, - special = "require", - parent = "", - node = "", - [1] = "f", - }, - ref = "", - [1] = "z", - }, - [3] = { - type = "call", - start = 20000, - finish = 20006, - parent = "", - node = "", - args = { - type = "callargs", - start = 20001, - finish = 20006, - parent = "", - [1] = { - type = "string", - start = 20001, - finish = 20006, - parent = "", - [1] = "xxx", - [2] = "'", - }, - }, - }, -} -CHECK[[ -A:B(1):C(2) -]] -{ - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "call", - start = 0, - finish = 11, - parent = "", - node = "", - args = { - type = "callargs", - start = 8, - finish = 11, - parent = "", - [1] = { - type = "self", - start = 6, - finish = 7, - parent = "", - [1] = "self", - }, - [2] = { - type = "integer", - start = 9, - finish = 10, - parent = "", - [1] = 2, - }, - }, - }, -} - -CHECK [[ - -local s = [ [=[111]=] ] -]] -{ - type = "main", - start = 0, - finish = 20000, - locals = "", - [1] = { - type = "local", - start = 10006, - finish = 10007, - effect = 10007, - parent = "", - locPos = 10000, - [1] = "s", - }, - [2] = { - type = "string", - start = 10012, - finish = 10021, - parent = "", - [1] = "111", - [2] = "[=[", - }, -} -CHECK([[ +TEST([[ local x = 1 // 2 ]], { - nonstandardSymbol = { ['//'] = true } + nonestandardSymbols = { '//' } }) { type = "main", diff --git a/test/ast/block.lua b/test/ast/block.lua index 67b9ad1..67afdc0 100644 --- a/test/ast/block.lua +++ b/test/ast/block.lua @@ -459,3 +459,52 @@ end } } } + +TEST [[ +local function a() + return +end]] +{ + type = 'Function', + childs = { + [1] = { + type = 'Return', + exps = { + [1] = NIL + } + } + } +} + +TEST [[ +local function f() + return f +end +]] +{ + type = 'Function', + locals = { + [1] = { + id = 'f', + gets = { + [1] = { + left = 10011, + } + } + } + }, + childs = { + [1] = { + type = 'Return', + exps = { + [1] = { + type = 'Var', + id = 'f', + loc = { + left = 15, + } + } + } + } + } +} From 44f4f0121665df8425ebdb1295f6ec2b17a67958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 27 Sep 2023 12:12:40 +0800 Subject: [PATCH 54/94] =?UTF-8?q?=E6=95=B4=E7=90=86=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/exp.lua | 2 +- src/parser/ast/main.lua | 2 +- src/parser/compile.lua | 2 +- test/ast/ast.lua | 417 ---------------------------------------- test/ast/init.lua | 2 +- test/ast/main.lua | 270 ++++++++++++++++++++++++++ 6 files changed, 274 insertions(+), 421 deletions(-) delete mode 100644 test/ast/ast.lua create mode 100644 test/ast/main.lua diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index 0013ba1..9d8fd01 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -163,7 +163,7 @@ function Ast:parseTerm() local current = head while true do - self:skipSpace() + self:skipSpace(true) local chain = self:parseField(current) or self:parseCall(current) diff --git a/src/parser/ast/main.lua b/src/parser/ast/main.lua index 1b1a412..d9c2f6e 100644 --- a/src/parser/ast/main.lua +++ b/src/parser/ast/main.lua @@ -53,5 +53,5 @@ function Ast:parseMain() self:blockParseChilds(main) self:blockFinish(main) - self.main = main + return main end diff --git a/src/parser/compile.lua b/src/parser/compile.lua index 05969fa..d261243 100644 --- a/src/parser/compile.lua +++ b/src/parser/compile.lua @@ -32,6 +32,6 @@ local LuaParser = class.get 'LuaParser' ---@return LuaParser.Ast function LuaParser.compile(code, version, options) local ast = class.new 'LuaParser.Ast' (code, version, options) - ast:parseMain() + ast.main = ast:parseMain() return ast end diff --git a/test/ast/ast.lua b/test/ast/ast.lua deleted file mode 100644 index ec3195c..0000000 --- a/test/ast/ast.lua +++ /dev/null @@ -1,417 +0,0 @@ ----@param code string ----@param optional? LuaParser.CompileOptions ----@return fun(table) -local function TEST(code, optional) - return function (expect) - local parser = require 'parser' - local ast = parser.compile(code, nil, optional) - assert(ast) - Match(ast, expect) - end -end - -TEST '' -{ - main = { - type = 'Main', - start = 0, - finish = 0, - } -} - -TEST ';;;' -{ - main = { - start = 0, - finish = 3, - } -} - -TEST ';;;x = 1' -{ - main = { - locals = { - [1] = { - id = '...', - dummy = true, - }, - [2] = { - id = '_ENV', - dummy = true, - envRefs = { - [1] = { - start = 3 - } - } - } - }, - childs = { - [1] = { - type = 'Assign', - exps = { - [1] = { - id = 'x', - env = { - id = '_ENV', - start = 0, - } - } - } - } - } - } -} - -TEST([[ -local x = 1 // 2 -]], { - nonestandardSymbols = { '//' } -}) -{ - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 11, - range = 11, - parent = "", - locPos = 0, - value = { - type = "integer", - start = 10, - finish = 11, - parent = "", - [1] = 1, - }, - [1] = "x", - }, -} - -CHECK([[ -local x = { - 1, // BAD - 2, // GOOD - 3, // GOOD -} -]], { - nonstandardSymbol = { ['//'] = true } -}) -{ - type = "main", - start = 0, - finish = 50000, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 40001, - range = 40001, - parent = "", - locPos = 0, - value = { - type = "table", - start = 10, - finish = 40001, - parent = "", - [1] = { - type = "tableexp", - start = 10004, - finish = 10005, - tindex = 1, - parent = "", - value = { - type = "integer", - start = 10004, - finish = 10005, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "tableexp", - start = 20004, - finish = 20005, - tindex = 2, - parent = "", - value = { - type = "integer", - start = 20004, - finish = 20005, - parent = "", - [1] = 2, - }, - }, - [3] = { - type = "tableexp", - start = 30004, - finish = 30005, - tindex = 3, - parent = "", - value = { - type = "integer", - start = 30004, - finish = 30005, - parent = "", - [1] = 3, - }, - }, - }, - [1] = "x", - }, -} - -CHECK [[ -local x -return { - x = 1, -} -]] -{ - type = "main", - start = 0, - finish = 40000, - locals = "", - returns = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 7, - parent = "", - locPos = 0, - [1] = "x", - }, - [2] = { - type = "return", - start = 10000, - finish = 30001, - parent = "", - [1] = { - type = "table", - start = 10007, - finish = 30001, - parent = "", - [1] = { - type = "tablefield", - start = 20004, - finish = 20005, - range = 20009, - parent = "", - node = "", - field = { - type = "field", - start = 20004, - finish = 20005, - parent = "", - [1] = "x", - }, - value = { - type = "integer", - start = 20008, - finish = 20009, - parent = "", - [1] = 1, - }, - }, - }, - }, -} - -CHECK [[ -local x -a = { - x -} -]] -{ - type = "main", - start = 0, - finish = 40000, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 7, - parent = "", - locPos = 0, - ref = "", - [1] = "x", - }, - [2] = { - type = "setglobal", - start = 10000, - finish = 10001, - range = 30001, - parent = "", - node = "", - value = { - type = "table", - start = 10004, - finish = 30001, - parent = "", - [1] = { - type = "tableexp", - start = 20004, - finish = 20005, - tindex = 1, - parent = "", - value = { - type = "getlocal", - start = 20004, - finish = 20005, - parent = "", - node = "", - [1] = "x", - }, - }, - }, - [1] = "a", - }, -} - -CHECK [[ -x, y, z = 1, func() -]] -{ - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "setglobal", - start = 0, - finish = 1, - range = 11, - parent = "", - node = "", - value = { - type = "integer", - start = 10, - finish = 11, - parent = "", - [1] = 1, - }, - [1] = "x", - }, - [2] = { - type = "setglobal", - start = 3, - finish = 4, - range = 19, - parent = "", - node = "", - value = { - type = "select", - start = 13, - finish = 19, - parent = "", - vararg = "", - sindex = 1, - }, - [1] = "y", - }, - [3] = { - type = "setglobal", - start = 6, - finish = 7, - range = 19, - parent = "", - node = "", - value = { - type = "select", - start = 13, - finish = 19, - parent = "", - vararg = "", - sindex = 2, - }, - [1] = "z", - }, -} - -CHECK [[ -local x, y --- comments -]] -{ - type = "main", - start = 0, - finish = 20000, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 10, - parent = "", - locPos = 0, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 10, - parent = "", - [1] = "y", - }, -} - -CHECK [[ -local _ENV = nil -]] -{ - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 10, - effect = 16, - range = 16, - parent = "", - locPos = 0, - value = { - type = "nil", - start = 13, - finish = 16, - parent = "", - }, - [1] = "_ENV", - }, -} - -CHECK [[ -_ENV = nil -]] -{ - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "local", - start = 0, - finish = 4, - effect = 10, - range = 10, - parent = "", - node = "", - locPos = 0, - value = { - type = "nil", - start = 7, - finish = 10, - parent = "", - }, - [1] = "_ENV", - }, -} diff --git a/test/ast/init.lua b/test/ast/init.lua index a4eff03..41b8299 100644 --- a/test/ast/init.lua +++ b/test/ast/init.lua @@ -28,4 +28,4 @@ require 'test.ast.table' require 'test.ast.exp' require 'test.ast.state' require 'test.ast.block' -require 'test.ast.ast' +require 'test.ast.main' diff --git a/test/ast/main.lua b/test/ast/main.lua new file mode 100644 index 0000000..980f6b5 --- /dev/null +++ b/test/ast/main.lua @@ -0,0 +1,270 @@ +local class = require 'class' + +---@param code string +---@param optional? LuaParser.CompileOptions +---@return fun(table) +local function TEST(code, optional) + return function (expect) + local ast = class.new 'LuaParser.Ast' (code, nil, optional) + local main = ast:parseMain() + assert(main) + Match(main, expect) + end +end + +TEST '' +{ + type = 'Main', + start = 0, + finish = 0, +} + +TEST ';;;' +{ + start = 0, + finish = 3, +} + +TEST ';;;x = 1' +{ + locals = { + [1] = { + id = '...', + dummy = true, + }, + [2] = { + id = '_ENV', + dummy = true, + envRefs = { + [1] = { + start = 3 + } + } + } + }, + childs = { + [1] = { + type = 'Assign', + exps = { + [1] = { + id = 'x', + env = { + id = '_ENV', + start = 0, + } + } + } + } + } +} + +TEST ([[ +local x = 1 // 2 +]], { + nonestandardSymbols = { '//' } +}) +{ + childs = { + [1] = { + values = { + [1] = { + type = 'Integer', + } + } + } + } +} + +TEST ([[ +local x = 1 // 2 +]]) +{ + childs = { + [1] = { + values = { + [1] = { + type = 'Binary', + op = '//' + } + } + } + } +} + +TEST ([[ +local x = { + 1, // BAD + 2, // GOOD + 3, // GOOD +} +]], { + nonestandardSymbols = { '//' } +}) +{ + childs = { + [1] = { + values = { + [1] = { + type = 'Table', + fields = { + [1] = { + subtype = 'exp', + value = { + value = 1, + } + }, + [2] = { + subtype = 'exp', + value = { + value = 2, + } + }, + [3] = { + subtype = 'exp', + value = { + value = 3, + } + } + } + } + } + } + } +} + +TEST [[ +local x +return { + x = 1, +} +]] +{ + childs = { + [2] = { + exps = { + [1] = { + type = 'Table', + fields = { + [1] = { + subtype = 'field', + key = { + id = 'x', + loc = NIL, + } + } + } + } + } + } + } +} + +TEST [[ +local x +a = { + x +} +]] +{ + childs = { + [2] = { + values = { + [1] = { + type = 'Table', + fields = { + [1] = { + subtype = 'exp', + value = { + id = 'x', + loc = { + left = 6 + } + } + } + } + } + } + } + } +} + +CHECK [[ +local x, y +-- comments +]] +{ + type = "main", + start = 0, + finish = 20000, + locals = "", + [1] = { + type = "local", + start = 6, + finish = 7, + effect = 10, + parent = "", + locPos = 0, + [1] = "x", + }, + [2] = { + type = "local", + start = 9, + finish = 10, + effect = 10, + parent = "", + [1] = "y", + }, +} + +CHECK [[ +local _ENV = nil +]] +{ + type = "main", + start = 0, + finish = 10000, + locals = "", + [1] = { + type = "local", + start = 6, + finish = 10, + effect = 16, + range = 16, + parent = "", + locPos = 0, + value = { + type = "nil", + start = 13, + finish = 16, + parent = "", + }, + [1] = "_ENV", + }, +} + +CHECK [[ +_ENV = nil +]] +{ + type = "main", + start = 0, + finish = 10000, + locals = "", + [1] = { + type = "local", + start = 0, + finish = 4, + effect = 10, + range = 10, + parent = "", + node = "", + locPos = 0, + value = { + type = "nil", + start = 7, + finish = 10, + parent = "", + }, + [1] = "_ENV", + }, +} From 637d7ea85b983bdbdd4d808d7443ee3894935baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 27 Sep 2023 14:31:29 +0800 Subject: [PATCH 55/94] =?UTF-8?q?=E6=95=B4=E7=90=86=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/ast/main.lua | 101 ++++++++++++---------------------------------- 1 file changed, 25 insertions(+), 76 deletions(-) diff --git a/test/ast/main.lua b/test/ast/main.lua index 980f6b5..5aa5511 100644 --- a/test/ast/main.lua +++ b/test/ast/main.lua @@ -188,83 +188,32 @@ a = { } } -CHECK [[ -local x, y --- comments -]] -{ - type = "main", - start = 0, - finish = 20000, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 10, - parent = "", - locPos = 0, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 10, - parent = "", - [1] = "y", - }, -} - -CHECK [[ -local _ENV = nil -]] -{ - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 10, - effect = 16, - range = 16, - parent = "", - locPos = 0, - value = { - type = "nil", - start = 13, - finish = 16, - parent = "", - }, - [1] = "_ENV", - }, -} - -CHECK [[ -_ENV = nil +TEST [[ +x = 1 +local _ENV +x = 1 ]] { - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "local", - start = 0, - finish = 4, - effect = 10, - range = 10, - parent = "", - node = "", - locPos = 0, - value = { - type = "nil", - start = 7, - finish = 10, - parent = "", + childs = { + [1] = { + exps = { + [1] = { + loc = NIL, + env = { + left = 0, + } + } + } }, - [1] = "_ENV", - }, + [3] = { + exps = { + [1] = { + loc = NIL, + env = { + left = 10006, + } + } + } + } + } } From f9683e302bd7bfe3b57f44985cb3d207e65bf357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 27 Sep 2023 16:12:00 +0800 Subject: [PATCH 56/94] =?UTF-8?q?=E6=95=B4=E7=90=86=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser.old/compile.lua | 3989 ------------------------------------ src/parser.old/guide.lua | 1292 ------------ src/parser.old/init.lua | 8 - src/parser.old/lines.lua | 24 - src/parser.old/luadoc.lua | 2070 ------------------- src/parser.old/relabel.lua | 361 ---- src/parser.old/tokens.lua | 48 - src/parser/ast/ast.lua | 1 + src/parser/ast/base.lua | 12 +- src/parser/ast/block.lua | 36 +- src/parser/ast/main.lua | 9 + src/parser/compile.lua | 1 + test/ast.old/Action.lua | 1570 -------------- test/ast.old/Boolean.lua | 14 - test/ast.old/Comment.lua | 43 - test/ast.old/Dirty.lua | 1366 ------------ test/ast.old/Exp.lua | 1582 -------------- test/ast.old/Lua.lua | 1379 ------------- test/ast.old/LuaDoc.lua | 919 --------- test/ast.old/Nil.lua | 12 - test/ast.old/Number.lua | 84 - test/ast.old/String.lua | 215 -- test/ast.old/init.lua | 230 --- test/main.lua | 3 +- test/perform/init.lua | 11 +- test/performance.lua | 3 +- 26 files changed, 43 insertions(+), 15239 deletions(-) delete mode 100644 src/parser.old/compile.lua delete mode 100644 src/parser.old/guide.lua delete mode 100644 src/parser.old/init.lua delete mode 100644 src/parser.old/lines.lua delete mode 100644 src/parser.old/luadoc.lua delete mode 100644 src/parser.old/relabel.lua delete mode 100644 src/parser.old/tokens.lua delete mode 100644 test/ast.old/Action.lua delete mode 100644 test/ast.old/Boolean.lua delete mode 100644 test/ast.old/Comment.lua delete mode 100644 test/ast.old/Dirty.lua delete mode 100644 test/ast.old/Exp.lua delete mode 100644 test/ast.old/Lua.lua delete mode 100644 test/ast.old/LuaDoc.lua delete mode 100644 test/ast.old/Nil.lua delete mode 100644 test/ast.old/Number.lua delete mode 100644 test/ast.old/String.lua delete mode 100644 test/ast.old/init.lua diff --git a/src/parser.old/compile.lua b/src/parser.old/compile.lua deleted file mode 100644 index 3b5799b..0000000 --- a/src/parser.old/compile.lua +++ /dev/null @@ -1,3989 +0,0 @@ -local tokens = require 'parser.lexer' -local guide = require 'parser.guide' - -local sbyte = string.byte -local sfind = string.find -local smatch = string.match -local sgsub = string.gsub -local ssub = string.sub -local schar = string.char -local supper = string.upper -local uchar = utf8.char -local tconcat = table.concat -local tinsert = table.insert -local tointeger = math.tointeger -local tonumber = tonumber -local maxinteger = math.maxinteger -local assert = assert - -_ENV = nil - ----@alias parser.position integer - ----@param str string ----@return table -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, - ['os.exit'] = 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, LocalLimited - -local LocalLimit = 200 - -local parseExp, parseAction - -local pushError - -local function addSpecial(name, obj) - if not State.specials then - State.specials = {} - end - if not State.specials[name] then - State.specials[name] = {} - end - State.specials[name][#State.specials[name]+1] = obj - obj.special = name -end - ----@param offset integer ----@param leftOrRight '"left"'|'"right"' -local function getPosition(offset, leftOrRight) - if not offset or offset > #Lua then - return LineMulti * Line + #Lua - LineOffset + 1 - end - if leftOrRight == 'left' then - return LineMulti * Line + offset - LineOffset - else - return LineMulti * Line + offset - LineOffset + 1 - end -end - ----@return string? word ----@return parser.position? startPosition ----@return parser.position? finishPosition -local function peekWord() - local word = Tokens[Index + 1] - if not word then - return nil - end - if not CharMapWord[ssub(word, 1, 1)] then - return nil - end - local startPos = getPosition(Tokens[Index] , 'left') - local finishPos = getPosition(Tokens[Index] + #word - 1, 'right') - return word, startPos, finishPos -end - -local function lastRightPosition() - if Index < 2 then - return 0 - end - local token = Tokens[Index - 1] - if NLMap[token] then - return LastTokenFinish - elseif token then - return getPosition(Tokens[Index - 2] + #token - 1, 'right') - else - return getPosition(#Lua, 'right') - end -end - -local function missSymbol(symbol, start, finish) - pushError { - type = 'MISS_SYMBOL', - start = start or lastRightPosition(), - finish = finish or start or lastRightPosition(), - info = { - symbol = symbol, - } - } -end - -local function missExp() - pushError { - type = 'MISS_EXP', - start = lastRightPosition(), - finish = lastRightPosition(), - } -end - -local function missName(pos) - pushError { - type = 'MISS_NAME', - start = pos or lastRightPosition(), - finish = pos or lastRightPosition(), - } -end - -local function missEnd(relatedStart, relatedFinish) - pushError { - type = 'MISS_SYMBOL', - start = lastRightPosition(), - finish = lastRightPosition(), - info = { - symbol = 'end', - related = { - { - start = relatedStart, - finish = relatedFinish, - } - } - } - } - pushError { - type = 'MISS_END', - start = relatedStart, - finish = relatedFinish, - } -end - -local function unknownSymbol(start, finish, word) - local token = word or Tokens[Index + 1] - if not token then - return false - end - pushError { - type = 'UNKNOWN_SYMBOL', - start = start or getPosition(Tokens[Index], 'left'), - finish = finish or getPosition(Tokens[Index] + #token - 1, 'right'), - info = { - symbol = token, - } - } - return true -end - -local function skipUnknownSymbol(stopSymbol) - if unknownSymbol() then - Index = Index + 2 - return true - end - return false -end - -local function skipNL() - local token = Tokens[Index + 1] - if NLMap[token] then - if Index >= 2 and not NLMap[Tokens[Index - 1]] then - LastTokenFinish = getPosition(Tokens[Index - 2] + #Tokens[Index - 1] - 1, 'right') - end - Line = Line + 1 - LineOffset = Tokens[Index] + #token - Index = Index + 2 - State.lines[Line] = LineOffset - return true - end - return false -end - -local function getSavePoint() - local index = Index - local line = Line - local lineOffset = LineOffset - local errs = State.errs - local errCount = #errs - return function () - Index = index - Line = line - LineOffset = lineOffset - for i = errCount + 1, #errs do - errs[i] = nil - end - end -end - -local function fastForwardToken(offset) - while true do - local myOffset = Tokens[Index] - if not myOffset - or myOffset >= offset then - break - end - local token = Tokens[Index + 1] - if NLMap[token] then - Line = Line + 1 - LineOffset = Tokens[Index] + #token - State.lines[Line] = LineOffset - end - Index = Index + 2 - end -end - -local function resolveLongString(finishMark) - skipNL() - local miss - local start = Tokens[Index] - local finishOffset = sfind(Lua, finishMark, start, true) - if not finishOffset then - finishOffset = #Lua + 1 - miss = true - end - local stringResult = start and ssub(Lua, start, finishOffset - 1) or '' - local lastLN = stringResult:find '[\r\n][^\r\n]*$' - if lastLN then - local result = stringResult - : gsub('\r\n?', '\n') - stringResult = result - end - if finishMark == ']]' and State.version == 'Lua 5.1' then - local nestOffset = sfind(Lua, '[[', start, true) - if nestOffset and nestOffset < finishOffset then - fastForwardToken(nestOffset) - local nestStartPos = getPosition(nestOffset, 'left') - local nestFinishPos = getPosition(nestOffset + 1, 'right') - pushError { - type = 'NESTING_LONG_MARK', - start = nestStartPos, - finish = nestFinishPos, - } - end - 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 start then - return nil - end - fastForwardToken(finish + 1) - local startPos = getPosition(start, 'left') - local finishMark = sgsub(mark, '%[', ']') - local stringResult, finishPos = resolveLongString(finishMark) - return { - type = 'string', - start = startPos, - finish = finishPos, - [1] = stringResult, - [2] = mark, - } -end - -local function pushCommentHeadError(left) - if State.options.nonstandardSymbol['//'] then - return - end - pushError { - type = 'ERR_COMMENT_PREFIX', - start = left, - finish = left + 2, - fix = { - title = 'FIX_COMMENT_PREFIX', - { - start = left, - finish = left + 2, - text = '--', - }, - } - } -end - -local function pushLongCommentError(left, right) - if State.options.nonstandardSymbol['/**/'] then - return - end - pushError { - type = 'ERR_C_LONG_COMMENT', - start = left, - finish = right, - fix = { - title = 'FIX_C_LONG_COMMENT', - { - start = left, - finish = left + 2, - text = '--[[', - }, - { - start = right - 2, - finish = right, - text = '--]]' - }, - } - } -end - -local function skipComment(isAction) - local token = Tokens[Index + 1] - if token == '--' - or ( - token == '//' - and ( - isAction - or State.options.nonstandardSymbol['//'] - ) - ) then - local start = Tokens[Index] - local left = getPosition(start, 'left') - local chead = false - if token == '//' then - chead = true - pushCommentHeadError(left) - end - Index = Index + 2 - local longComment = start + 2 == Tokens[Index] and parseLongString() - if longComment then - longComment.type = 'comment.long' - longComment.text = longComment[1] - longComment.mark = longComment[2] - longComment[1] = nil - longComment[2] = nil - State.comms[#State.comms+1] = longComment - return true - end - while true do - local nl = Tokens[Index + 1] - if not nl or NLMap[nl] then - break - end - Index = Index + 2 - end - local right = Tokens[Index] and (Tokens[Index] - 1) or #Lua - State.comms[#State.comms+1] = { - type = chead and 'comment.cshort' or 'comment.short', - start = left, - finish = getPosition(right, 'right'), - text = ssub(Lua, start + 2, right), - } - return true - end - if token == '/*' then - local start = Tokens[Index] - local left = getPosition(start, 'left') - Index = Index + 2 - local result, right = resolveLongString '*/' - pushLongCommentError(left, right) - State.comms[#State.comms+1] = { - type = 'comment.long', - start = left, - finish = right, - text = result, - } - return true - end - return false -end - -local function skipSpace(isAction) - repeat until not skipNL() - and not skipComment(isAction) -end - -local function expectAssign(isAction) - local token = Tokens[Index + 1] - if token == '=' then - Index = Index + 2 - return true - end - if token == '==' then - local left = getPosition(Tokens[Index], 'left') - local right = getPosition(Tokens[Index] + #token - 1, 'right') - pushError { - type = 'ERR_ASSIGN_AS_EQ', - start = left, - finish = right, - fix = { - title = 'FIX_ASSIGN_AS_EQ', - { - start = left, - finish = right, - text = '=', - } - } - } - Index = Index + 2 - return true - end - if isAction then - if token == '+=' - or token == '-=' - or token == '*=' - or token == '/=' - or token == '%=' - or token == '^=' - or token == '//=' - or token == '|=' - or token == '&=' - or token == '>>=' - or token == '<<=' then - if not State.options.nonstandardSymbol[token] then - unknownSymbol() - end - Index = Index + 2 - return true - end - end - return false -end - -local function parseLocalAttrs() - local attrs - while true do - skipSpace() - local token = Tokens[Index + 1] - if token ~= '<' then - break - end - if not attrs then - attrs = { - type = 'localattrs', - } - end - local attr = { - type = 'localattr', - parent = attrs, - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index], 'right'), - } - attrs[#attrs+1] = attr - Index = Index + 2 - skipSpace() - local word, wstart, wfinish = peekWord() - if word then - attr[1] = word - attr.finish = wfinish - Index = Index + 2 - if word ~= 'const' - and word ~= 'close' then - pushError { - type = 'UNKNOWN_ATTRIBUTE', - start = wstart, - finish = wfinish, - } - end - else - missName() - end - attr.finish = lastRightPosition() - skipSpace() - if Tokens[Index + 1] == '>' then - attr.finish = getPosition(Tokens[Index], 'right') - Index = Index + 2 - elseif Tokens[Index + 1] == '>=' then - attr.finish = getPosition(Tokens[Index], 'right') - pushError { - type = 'MISS_SPACE_BETWEEN', - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + 1, 'right'), - } - Index = Index + 2 - else - missSymbol '>' - end - if State.version ~= 'Lua 5.4' then - pushError { - type = 'UNSUPPORT_SYMBOL', - start = attr.start, - finish = attr.finish, - version = 'Lua 5.4', - info = { - version = State.version - } - } - end - end - return attrs -end - -local function createLocal(obj, attrs) - obj.type = 'local' - obj.effect = obj.finish - - if attrs then - obj.attrs = attrs - attrs.parent = obj - end - - local chunk = Chunk[#Chunk] - if chunk then - local locals = chunk.locals - if not locals then - locals = {} - chunk.locals = locals - end - locals[#locals+1] = obj - LocalCount = LocalCount + 1 - if not LocalLimited and LocalCount > LocalLimit then - LocalLimited = true - pushError { - type = 'LOCAL_LIMIT', - start = obj.start, - finish = obj.finish, - } - end - end - return obj -end - -local function pushChunk(chunk) - Chunk[#Chunk+1] = chunk -end - -local function resolveLable(label, obj) - if not label.ref then - label.ref = {} - end - label.ref[#label.ref+1] = obj - obj.node = label - - -- 如果有局部变量在 goto 与 label 之间声明, - -- 并在 label 之后使用,则算作语法错误 - - -- 如果 label 在 goto 之前声明,那么不会有中间声明的局部变量 - if obj.start > label.start then - return - end - - local block = guide.getBlock(obj) - local locals = block and block.locals - if not locals then - return - end - - for i = 1, #locals do - local loc = locals[i] - -- 检查局部变量声明位置为 goto 与 label 之间 - if loc.start < obj.start or loc.finish > label.finish then - goto CONTINUE - end - -- 检查局部变量的使用位置在 label 之后 - local refs = loc.ref - if not refs then - goto CONTINUE - end - for j = 1, #refs do - local ref = refs[j] - if ref.finish > label.finish then - pushError { - type = 'JUMP_LOCAL_SCOPE', - start = obj.start, - finish = obj.finish, - info = { - loc = loc[1], - }, - relative = { - { - start = label.start, - finish = label.finish, - }, - { - start = loc.start, - finish = loc.finish, - } - }, - } - return - end - end - ::CONTINUE:: - end -end - -local function resolveGoTo(gotos) - for i = 1, #gotos do - local action = gotos[i] - local label = guide.getLabel(action, action[1]) - if label then - resolveLable(label, action) - else - pushError { - type = 'NO_VISIBLE_LABEL', - start = action.start, - finish = action.finish, - info = { - label = action[1], - } - } - end - end -end - -local function popChunk() - local chunk = Chunk[#Chunk] - if chunk.gotos then - resolveGoTo(chunk.gotos) - chunk.gotos = nil - end - local lastAction = chunk[#chunk] - if lastAction then - chunk.finish = lastAction.finish - end - Chunk[#Chunk] = nil -end - -local function parseNil() - if Tokens[Index + 1] ~= 'nil' then - return nil - end - local offset = Tokens[Index] - Index = Index + 2 - return { - type = 'nil', - start = getPosition(offset, 'left'), - finish = getPosition(offset + 2, 'right'), - } -end - -local function parseBoolean() - local word = Tokens[Index+1] - if word ~= 'true' - and word ~= 'false' then - return nil - end - local start = getPosition(Tokens[Index], 'left') - local finish = getPosition(Tokens[Index] + #word - 1, 'right') - Index = Index + 2 - return { - type = 'boolean', - start = start, - finish = finish, - [1] = word == 'true' and true or false, - } -end - -local function parseStringUnicode() - local offset = Tokens[Index] + 1 - if ssub(Lua, offset, offset) ~= '{' then - local pos = getPosition(offset, 'left') - missSymbol('{', pos) - return nil, offset - end - local leftPos = getPosition(offset, 'left') - local x16 = smatch(Lua, '^%w*', offset + 1) - local rightPos = getPosition(offset + #x16, 'right') - offset = offset + #x16 + 1 - if ssub(Lua, offset, offset) == '}' then - offset = offset + 1 - rightPos = rightPos + 1 - else - missSymbol('}', rightPos) - end - offset = offset + 1 - if #x16 == 0 then - pushError { - type = 'UTF8_SMALL', - start = leftPos, - finish = rightPos, - } - return '', offset - end - if State.version ~= 'Lua 5.3' - and State.version ~= 'Lua 5.4' - and State.version ~= 'LuaJIT' - then - pushError { - type = 'ERR_ESC', - start = leftPos - 2, - finish = rightPos, - version = {'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, - info = { - version = State.version, - } - } - return nil, offset - end - local byte = tonumber(x16, 16) - if not byte then - for i = 1, #x16 do - if not tonumber(ssub(x16, i, i), 16) then - pushError { - type = 'MUST_X16', - start = leftPos + i, - finish = leftPos + i + 1, - } - end - end - return nil, offset - end - if State.version == 'Lua 5.4' then - if byte < 0 or byte > 0x7FFFFFFF then - pushError { - type = 'UTF8_MAX', - start = leftPos, - finish = rightPos, - info = { - min = '00000000', - max = '7FFFFFFF', - } - } - return nil, offset - end - else - if byte < 0 or byte > 0x10FFFF then - pushError { - type = 'UTF8_MAX', - start = leftPos, - finish = rightPos, - version = byte <= 0x7FFFFFFF and 'Lua 5.4' or nil, - info = { - min = '000000', - max = '10FFFF', - } - } - end - end - if byte >= 0 and byte <= 0x10FFFF then - return uchar(byte), offset - end - return '', offset -end - -local stringPool = {} -local function parseShortString() - local mark = Tokens[Index+1] - local startOffset = Tokens[Index] - local startPos = getPosition(startOffset, 'left') - Index = Index + 2 - local stringIndex = 0 - local currentOffset = startOffset + 1 - local escs = {} - while true do - local token = Tokens[Index + 1] - if token == mark then - stringIndex = stringIndex + 1 - stringPool[stringIndex] = ssub(Lua, currentOffset, Tokens[Index] - 1) - Index = Index + 2 - break - end - if NLMap[token] then - stringIndex = stringIndex + 1 - stringPool[stringIndex] = ssub(Lua, currentOffset, Tokens[Index] - 1) - missSymbol(mark) - break - end - if not token then - stringIndex = stringIndex + 1 - stringPool[stringIndex] = ssub(Lua, currentOffset or -1) - missSymbol(mark) - break - end - if token == '\\' then - stringIndex = stringIndex + 1 - stringPool[stringIndex] = ssub(Lua, currentOffset, Tokens[Index] - 1) - currentOffset = Tokens[Index] - Index = Index + 2 - if not Tokens[Index] then - goto CONTINUE - end - local escLeft = getPosition(currentOffset, 'left') - -- has space? - if Tokens[Index] - currentOffset > 1 then - local right = getPosition(currentOffset + 1, 'right') - pushError { - type = 'ERR_ESC', - start = escLeft, - finish = right, - } - escs[#escs+1] = escLeft - escs[#escs+1] = right - escs[#escs+1] = 'err' - goto CONTINUE - end - local nextToken = ssub(Tokens[Index + 1], 1, 1) - if EscMap[nextToken] then - stringIndex = stringIndex + 1 - stringPool[stringIndex] = EscMap[nextToken] - currentOffset = Tokens[Index] + #nextToken - Index = Index + 2 - escs[#escs+1] = escLeft - escs[#escs+1] = escLeft + 2 - escs[#escs+1] = 'normal' - goto CONTINUE - end - if nextToken == mark then - stringIndex = stringIndex + 1 - stringPool[stringIndex] = mark - currentOffset = Tokens[Index] + #nextToken - Index = Index + 2 - escs[#escs+1] = escLeft - escs[#escs+1] = escLeft + 2 - escs[#escs+1] = 'normal' - goto CONTINUE - end - if nextToken == 'z' then - Index = Index + 2 - repeat until not skipNL() - currentOffset = Tokens[Index] - escs[#escs+1] = escLeft - escs[#escs+1] = escLeft + 2 - escs[#escs+1] = 'normal' - goto CONTINUE - end - if CharMapNumber[nextToken] then - local numbers = smatch(Tokens[Index + 1], '^%d+') - if #numbers > 3 then - numbers = ssub(numbers, 1, 3) - end - currentOffset = Tokens[Index] + #numbers - fastForwardToken(currentOffset) - local right = getPosition(currentOffset - 1, 'right') - local byte = tointeger(numbers) - if byte and byte <= 255 then - stringIndex = stringIndex + 1 - stringPool[stringIndex] = schar(byte) - else - pushError { - type = 'ERR_ESC', - start = escLeft, - finish = right, - } - end - escs[#escs+1] = escLeft - escs[#escs+1] = right - escs[#escs+1] = 'byte' - goto CONTINUE - end - if nextToken == 'x' then - local left = getPosition(Tokens[Index] - 1, 'left') - local x16 = ssub(Tokens[Index + 1], 2, 3) - local byte = tonumber(x16, 16) - if byte then - currentOffset = Tokens[Index] + 3 - stringIndex = stringIndex + 1 - stringPool[stringIndex] = schar(byte) - else - currentOffset = Tokens[Index] + 1 - pushError { - type = 'MISS_ESC_X', - start = getPosition(currentOffset, 'left'), - finish = getPosition(currentOffset + 1, 'right'), - } - end - local right = getPosition(currentOffset + 1, 'right') - escs[#escs+1] = escLeft - escs[#escs+1] = right - escs[#escs+1] = 'byte' - if State.version == 'Lua 5.1' then - pushError { - type = 'ERR_ESC', - start = left, - finish = left + 4, - version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, - info = { - version = State.version, - } - } - end - Index = Index + 2 - goto CONTINUE - end - if nextToken == 'u' then - local str, newOffset = parseStringUnicode() - if str then - stringIndex = stringIndex + 1 - stringPool[stringIndex] = str - end - currentOffset = newOffset - fastForwardToken(currentOffset - 1) - local right = getPosition(currentOffset + 1, 'right') - escs[#escs+1] = escLeft - escs[#escs+1] = right - escs[#escs+1] = 'unicode' - goto CONTINUE - end - if NLMap[nextToken] then - stringIndex = stringIndex + 1 - stringPool[stringIndex] = '\n' - currentOffset = Tokens[Index] + #nextToken - skipNL() - local right = getPosition(currentOffset + 1, 'right') - escs[#escs+1] = escLeft - escs[#escs+1] = escLeft + 1 - escs[#escs+1] = 'normal' - goto CONTINUE - end - local right = getPosition(currentOffset + 1, 'right') - pushError { - type = 'ERR_ESC', - start = escLeft, - finish = right, - } - escs[#escs+1] = escLeft - escs[#escs+1] = right - escs[#escs+1] = 'err' - end - Index = Index + 2 - ::CONTINUE:: - end - local stringResult = tconcat(stringPool, '', 1, stringIndex) - local str = { - type = 'string', - start = startPos, - finish = lastRightPosition(), - escs = #escs > 0 and escs or nil, - [1] = stringResult, - [2] = mark, - } - if mark == '`' then - if not State.options.nonstandardSymbol[mark] then - pushError { - type = 'ERR_NONSTANDARD_SYMBOL', - start = startPos, - finish = str.finish, - info = { - symbol = '"', - }, - fix = { - title = 'FIX_NONSTANDARD_SYMBOL', - symbol = '"', - { - start = startPos, - finish = startPos + 1, - text = '"', - }, - { - start = str.finish - 1, - finish = str.finish, - text = '"', - }, - } - } - end - end - return str -end - -local function parseString() - local c = Tokens[Index + 1] - if CharMapStrSH[c] then - return parseShortString() - end - if CharMapStrLH[c] then - return parseLongString() - end - return nil -end - -local function parseNumber10(start) - local integer = true - local integerPart = smatch(Lua, '^%d*', start) - local offset = start + #integerPart - -- float part - if ssub(Lua, offset, offset) == '.' then - local floatPart = smatch(Lua, '^%d*', offset + 1) - integer = false - offset = offset + #floatPart + 1 - end - -- exp part - local echar = ssub(Lua, offset, offset) - if CharMapE10[echar] then - integer = false - offset = offset + 1 - local nextChar = ssub(Lua, offset, offset) - if CharMapSign[nextChar] then - offset = offset + 1 - end - local exp = smatch(Lua, '^%d*', offset) - offset = offset + #exp - if #exp == 0 then - pushError { - type = 'MISS_EXPONENT', - start = getPosition(offset - 1, 'right'), - finish = getPosition(offset - 1, 'right'), - } - end - end - return tonumber(ssub(Lua, start, offset - 1)), offset, integer -end - -local function parseNumber16(start) - local integerPart = smatch(Lua, '^[%da-fA-F]*', start) - local offset = start + #integerPart - local integer = true - -- float part - if ssub(Lua, offset, offset) == '.' then - local floatPart = smatch(Lua, '^[%da-fA-F]*', offset + 1) - integer = false - offset = offset + #floatPart + 1 - if #integerPart == 0 and #floatPart == 0 then - pushError { - type = 'MUST_X16', - start = getPosition(offset - 1, 'right'), - finish = getPosition(offset - 1, 'right'), - } - end - else - if #integerPart == 0 then - pushError { - type = 'MUST_X16', - start = getPosition(offset - 1, 'right'), - finish = getPosition(offset - 1, 'right'), - } - return 0, offset - end - end - -- exp part - local echar = ssub(Lua, offset, offset) - if CharMapE16[echar] then - integer = false - offset = offset + 1 - local nextChar = ssub(Lua, offset, offset) - if CharMapSign[nextChar] then - offset = offset + 1 - end - local exp = smatch(Lua, '^%d*', offset) - offset = offset + #exp - end - local n = tonumber(ssub(Lua, start - 2, offset - 1)) - return n, offset, integer -end - -local function parseNumber2(start) - local bins = smatch(Lua, '^[01]*', start) - local offset = start + #bins - if State.version ~= 'LuaJIT' then - pushError { - type = 'UNSUPPORT_SYMBOL', - start = getPosition(start - 2, 'left'), - finish = getPosition(offset - 1, 'right'), - version = 'LuaJIT', - info = { - version = 'Lua 5.4', - } - } - end - return tonumber(bins, 2), offset -end - -local function dropNumberTail(offset, integer) - local _, finish, word = sfind(Lua, '^([%.%w_\x80-\xff]+)', offset) - if not finish then - return offset - end - if integer then - if supper(ssub(word, 1, 2)) == 'LL' then - if State.version ~= 'LuaJIT' then - pushError { - type = 'UNSUPPORT_SYMBOL', - start = getPosition(offset, 'left'), - finish = getPosition(offset + 1, 'right'), - version = 'LuaJIT', - info = { - version = State.version, - } - } - end - offset = offset + 2 - word = ssub(word, offset) - elseif supper(ssub(word, 1, 3)) == 'ULL' then - if State.version ~= 'LuaJIT' then - pushError { - type = 'UNSUPPORT_SYMBOL', - start = getPosition(offset, 'left'), - finish = getPosition(offset + 2, 'right'), - version = 'LuaJIT', - info = { - version = State.version, - } - } - end - offset = offset + 3 - word = ssub(word, offset) - end - end - if supper(ssub(word, 1, 1)) == 'I' then - if State.version ~= 'LuaJIT' then - pushError { - type = 'UNSUPPORT_SYMBOL', - start = getPosition(offset, 'left'), - finish = getPosition(offset, 'right'), - version = 'LuaJIT', - info = { - version = State.version, - } - } - end - offset = offset + 1 - word = ssub(word, offset) - end - if #word > 0 then - pushError { - type = 'MALFORMED_NUMBER', - start = getPosition(offset, 'left'), - finish = getPosition(finish, 'right'), - } - end - return finish + 1 -end - -local function parseNumber() - local offset = Tokens[Index] - if not offset then - return nil - end - local startPos = getPosition(offset, 'left') - local neg - if ssub(Lua, offset, offset) == '-' then - neg = true - offset = offset + 1 - end - local number, integer - local firstChar = ssub(Lua, offset, offset) - if firstChar == '.' then - number, offset = parseNumber10(offset) - integer = false - elseif firstChar == '0' then - local nextChar = ssub(Lua, offset + 1, offset + 1) - if CharMapN16[nextChar] then - number, offset, integer = parseNumber16(offset + 2) - elseif CharMapN2[nextChar] then - number, offset = parseNumber2(offset + 2) - integer = true - else - number, offset, integer = parseNumber10(offset) - end - elseif CharMapNumber[firstChar] then - number, offset, integer = parseNumber10(offset) - else - return nil - end - if not number then - number = 0 - end - if neg then - number = - number - end - local result = { - type = integer and 'integer' or 'number', - start = startPos, - finish = getPosition(offset - 1, 'right'), - [1] = number, - } - offset = dropNumberTail(offset, integer) - fastForwardToken(offset) - return result -end - -local function isKeyWord(word, nextToken) - if KeyWord[word] then - return true - end - if word == 'goto' then - if State.version == 'Lua 5.1' then - return false - end - if State.version == 'LuaJIT' then - if not nextToken then - return false - end - if CharMapWord[ssub(nextToken, 1, 1)] then - return true - end - return false - end - return true - 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, Tokens[Index + 1]) then - pushError { - type = 'KEYWORD', - start = startPos, - finish = finishPos, - } - end - return { - type = 'name', - start = startPos, - finish = finishPos, - [1] = word, - } -end - -local function parseNameOrList(parent) - local first = parseName() - if not first then - return nil - end - skipSpace() - local list - while true do - if Tokens[Index + 1] ~= ',' then - break - end - Index = Index + 2 - skipSpace() - local name = parseName(true) - if not name then - missName() - break - end - if not list then - list = { - type = 'list', - start = first.start, - finish = first.finish, - parent = parent, - [1] = first - } - end - list[#list+1] = name - list.finish = name.finish - end - return list or first -end - -local function parseExpList(mini) - local list - local wantSep = false - while true do - skipSpace() - local token = Tokens[Index + 1] - if not token then - break - end - if ListFinishMap[token] then - break - end - if token == ',' then - local sepPos = getPosition(Tokens[Index], 'right') - if not wantSep then - pushError { - type = 'UNEXPECT_SYMBOL', - start = getPosition(Tokens[Index], 'left'), - finish = sepPos, - info = { - symbol = ',', - } - } - end - wantSep = false - Index = Index + 2 - goto CONTINUE - else - if mini then - if wantSep then - break - end - local nextToken = peekWord() - if isKeyWord(nextToken, Tokens[Index + 2]) - and nextToken ~= 'function' - and nextToken ~= 'true' - and nextToken ~= 'false' - and nextToken ~= 'nil' - and nextToken ~= 'not' then - break - end - end - local exp = parseExp() - if not exp then - break - end - if wantSep then - missSymbol(',', list[#list].finish, exp.start) - end - wantSep = true - if not list then - list = { - type = 'list', - start = exp.start, - } - end - list[#list+1] = exp - list.finish = exp.finish - exp.parent = list - end - ::CONTINUE:: - end - if not list then - return nil - end - if not wantSep then - missExp() - end - return list -end - -local function parseIndex() - local start = getPosition(Tokens[Index], 'left') - Index = Index + 2 - skipSpace() - local exp = parseExp() - local index = { - type = 'index', - start = start, - finish = exp and exp.finish or (start + 1), - index = exp - } - if exp then - exp.parent = index - else - missExp() - end - skipSpace() - if Tokens[Index + 1] == ']' then - index.finish = getPosition(Tokens[Index], 'right') - Index = Index + 2 - else - missSymbol ']' - end - return index -end - -local function parseTable() - local tbl = { - type = 'table', - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index], 'right'), - } - Index = Index + 2 - local index = 0 - local tindex = 0 - local wantSep = false - while true do - skipSpace(true) - local token = Tokens[Index + 1] - if token == '}' then - Index = Index + 2 - break - end - if CharMapTSep[token] then - if not wantSep then - missExp() - end - wantSep = false - Index = Index + 2 - goto CONTINUE - end - local lastRight = lastRightPosition() - - if peekWord() then - local savePoint = getSavePoint() - local name = parseName() - if name then - skipSpace() - if Tokens[Index + 1] == '=' then - Index = Index + 2 - if wantSep then - pushError { - type = 'MISS_SEP_IN_TABLE', - start = lastRight, - finish = getPosition(Tokens[Index], 'left'), - } - end - wantSep = true - skipSpace() - local fvalue = parseExp() - local tfield = { - type = 'tablefield', - start = name.start, - finish = name.finish, - range = fvalue and fvalue.finish, - node = tbl, - parent = tbl, - field = name, - value = fvalue, - } - name.type = 'field' - name.parent = tfield - if fvalue then - fvalue.parent = tfield - else - missExp() - end - index = index + 1 - tbl[index] = tfield - goto CONTINUE - end - end - savePoint() - end - - local exp = parseExp(true) - if exp then - if wantSep then - pushError { - type = 'MISS_SEP_IN_TABLE', - start = lastRight, - finish = exp.start, - } - end - wantSep = true - if exp.type == 'varargs' then - index = index + 1 - tbl[index] = exp - exp.parent = tbl - goto CONTINUE - end - index = index + 1 - tindex = tindex + 1 - local texp = { - type = 'tableexp', - start = exp.start, - finish = exp.finish, - tindex = tindex, - parent = tbl, - value = exp, - } - exp.parent = texp - tbl[index] = texp - goto CONTINUE - end - - if token == '[' then - if wantSep then - pushError { - type = 'MISS_SEP_IN_TABLE', - start = lastRight, - finish = getPosition(Tokens[Index], 'left'), - } - end - wantSep = true - local tindex = parseIndex() - skipSpace() - tindex.type = 'tableindex' - tindex.node = tbl - tindex.parent = tbl - index = index + 1 - tbl[index] = tindex - if expectAssign() then - skipSpace() - local ivalue = parseExp() - if ivalue then - ivalue.parent = tindex - tindex.range = ivalue.finish - tindex.value = ivalue - else - missExp() - end - else - missSymbol '=' - end - goto CONTINUE - end - - missSymbol '}' - break - ::CONTINUE:: - end - tbl.finish = lastRightPosition() - return tbl -end - -local function addDummySelf(node, call) - if node.type ~= 'getmethod' then - return - end - -- dummy param `self` - if not call.args then - call.args = { - type = 'callargs', - start = call.start, - finish = call.finish, - parent = call, - } - end - local self = { - type = 'self', - start = node.colon.start, - finish = node.colon.finish, - parent = call.args, - [1] = 'self', - } - tinsert(call.args, 1, self) -end - -local function checkAmbiguityCall(call, parenPos) - if State.version ~= 'Lua 5.1' then - return - end - local node = call.node - local nodeRow = guide.rowColOf(node.finish) - local callRow = guide.rowColOf(parenPos) - if nodeRow == callRow then - return - end - pushError { - type = 'AMBIGUOUS_SYNTAX', - start = parenPos, - finish = call.finish, - } -end - -local function bindSpecial(source, name) - if Specials[name] then - addSpecial(name, source) - else - local ospeicals = State.options.special - if ospeicals and ospeicals[name] then - addSpecial(ospeicals[name], source) - end - end -end - -local function parseSimple(node, funcName) - local currentName - if node.type == 'getglobal' - or node.type == 'getlocal' then - currentName = node[1] - end - local lastMethod - while true do - if lastMethod and node.node == lastMethod then - if node.type ~= 'call' then - missSymbol('(', node.node.finish, node.node.finish) - end - lastMethod = nil - end - skipSpace() - local token = Tokens[Index + 1] - if token == '.' then - local dot = { - type = token, - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index], 'right'), - } - Index = Index + 2 - skipSpace() - local field = parseName(true) - local getfield = { - type = 'getfield', - start = node.start, - finish = lastRightPosition(), - node = node, - dot = dot, - field = field - } - if field then - field.parent = getfield - field.type = 'field' - if currentName then - if node.type == 'getlocal' - or node.type == 'getglobal' - or node.type == 'getfield' then - currentName = currentName .. '.' .. field[1] - bindSpecial(getfield, currentName) - else - currentName = nil - end - end - else - pushError { - type = 'MISS_FIELD', - start = lastRightPosition(), - finish = lastRightPosition(), - } - end - node.parent = getfield - node.next = getfield - node = getfield - elseif token == ':' then - local colon = { - type = token, - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index], 'right'), - } - Index = Index + 2 - skipSpace() - local method = parseName(true) - local getmethod = { - type = 'getmethod', - start = node.start, - finish = lastRightPosition(), - node = node, - colon = colon, - method = method - } - if method then - method.parent = getmethod - method.type = 'method' - else - pushError { - type = 'MISS_METHOD', - start = lastRightPosition(), - finish = lastRightPosition(), - } - end - node.parent = getmethod - node.next = getmethod - node = getmethod - if lastMethod then - missSymbol('(', node.node.finish, node.node.finish) - end - lastMethod = getmethod - elseif token == '(' then - if funcName then - break - end - local startPos = getPosition(Tokens[Index], 'left') - local call = { - type = 'call', - start = node.start, - node = node, - } - Index = Index + 2 - local args = parseExpList() - if Tokens[Index + 1] == ')' then - call.finish = getPosition(Tokens[Index], 'right') - Index = Index + 2 - else - call.finish = lastRightPosition() - missSymbol ')' - end - if args then - args.type = 'callargs' - args.start = startPos - args.finish = call.finish - args.parent = call - call.args = args - end - addDummySelf(node, call) - checkAmbiguityCall(call, startPos) - node.parent = call - node = call - elseif token == '{' then - if funcName then - break - end - local tbl = parseTable() - local call = { - type = 'call', - start = node.start, - finish = tbl.finish, - node = node, - } - local args = { - type = 'callargs', - start = tbl.start, - finish = tbl.finish, - parent = call, - [1] = tbl, - } - call.args = args - addDummySelf(node, call) - tbl.parent = args - node.parent = call - node = call - elseif CharMapStrSH[token] then - if funcName then - break - end - local str = parseShortString() - local call = { - type = 'call', - start = node.start, - finish = str.finish, - node = node, - } - local args = { - type = 'callargs', - start = str.start, - finish = str.finish, - parent = call, - [1] = str, - } - call.args = args - addDummySelf(node, call) - str.parent = args - node.parent = call - node = call - elseif CharMapStrLH[token] then - local str = parseLongString() - if str then - if funcName then - break - end - local call = { - type = 'call', - start = node.start, - finish = str.finish, - node = node, - } - local args = { - type = 'callargs', - start = str.start, - finish = str.finish, - parent = call, - [1] = str, - } - call.args = args - addDummySelf(node, call) - str.parent = args - node.parent = call - node = call - else - local index = parseIndex() - local bstart = index.start - index.type = 'getindex' - index.start = node.start - index.node = node - node.next = index - node.parent = index - node = index - if funcName then - pushError { - type = 'INDEX_IN_FUNC_NAME', - start = bstart, - finish = index.finish, - } - end - end - else - break - end - end - if node.type == 'call' - and node.node == lastMethod then - lastMethod = nil - end - if node.type == 'call' then - if node.node.special == 'error' - or node.node.special == 'os.exit' then - node.hasExit = true - end - end - if node == lastMethod then - if funcName then - lastMethod = nil - end - end - if lastMethod then - missSymbol('(', lastMethod.finish) - end - return node -end - -local function parseVarargs() - local varargs = { - type = 'varargs', - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + 2, 'right'), - } - Index = Index + 2 - for i = #Chunk, 1, -1 do - local chunk = Chunk[i] - if chunk.vararg then - if not chunk.vararg.ref then - chunk.vararg.ref = {} - end - chunk.vararg.ref[#chunk.vararg.ref+1] = varargs - varargs.node = chunk.vararg - break - end - if chunk.type == 'main' then - break - end - if chunk.type == 'function' then - pushError { - type = 'UNEXPECT_DOTS', - start = varargs.start, - finish = varargs.finish, - } - break - end - end - return varargs -end - -local function parseParen() - local pl = Tokens[Index] - local paren = { - type = 'paren', - start = getPosition(pl, 'left'), - finish = getPosition(pl, 'right') - } - Index = Index + 2 - skipSpace() - local exp = parseExp() - if exp then - paren.exp = exp - paren.finish = exp.finish - exp.parent = paren - else - missExp() - end - skipSpace() - if Tokens[Index + 1] == ')' then - paren.finish = getPosition(Tokens[Index], 'right') - Index = Index + 2 - else - missSymbol ')' - end - return paren -end - -local function getLocal(name, pos) - for i = #Chunk, 1, -1 do - local chunk = Chunk[i] - local locals = chunk.locals - if locals then - local res - for n = 1, #locals do - local loc = locals[n] - if loc.effect > pos then - break - end - if loc[1] == name then - if not res or res.effect < loc.effect then - res = loc - end - end - end - if res then - return res - end - end - end -end - -local function resolveName(node) - if not node then - return nil - end - local loc = getLocal(node[1], node.start) - if loc then - node.type = 'getlocal' - node.node = loc - if not loc.ref then - loc.ref = {} - end - loc.ref[#loc.ref+1] = node - if loc.special then - addSpecial(loc.special, node) - end - else - node.type = 'getglobal' - local env = getLocal(State.ENVMode, node.start) - if env then - node.node = env - if not env.ref then - env.ref = {} - end - env.ref[#env.ref+1] = node - end - end - local name = node[1] - bindSpecial(node, name) - return node -end - -local function isChunkFinishToken(token) - local currentChunk = Chunk[#Chunk] - if not currentChunk then - return false - end - local tp = currentChunk.type - if tp == 'main' then - return false - end - if tp == 'for' - or tp == 'in' - or tp == 'loop' - or tp == 'function' then - return token == 'end' - end - if tp == 'if' - or tp == 'ifblock' - or tp == 'elseifblock' - or tp == 'elseblock' then - return token == 'then' - or token == 'end' - or token == 'else' - or token == 'elseif' - end - if tp == 'repeat' then - return token == 'until' - end - return true -end - -local function parseActions() - local rtn, last - while true do - skipSpace(true) - local token = Tokens[Index + 1] - if token == ';' then - Index = Index + 2 - goto CONTINUE - end - if ChunkFinishMap[token] - and isChunkFinishToken(token) then - break - end - local action, failed = parseAction() - if failed then - if not skipUnknownSymbol() then - break - end - end - if action then - if not rtn and action.type == 'return' then - rtn = action - end - last = action - end - ::CONTINUE:: - end - if rtn and rtn ~= last then - pushError { - type = 'ACTION_AFTER_RETURN', - start = rtn.start, - finish = rtn.finish, - } - end -end - -local function parseParams(params) - local lastSep - local hasDots - while true do - skipSpace() - local token = Tokens[Index + 1] - if not token or token == ')' then - if lastSep then - missName() - end - break - end - if token == ',' then - if lastSep or lastSep == nil then - missName() - else - lastSep = true - end - Index = Index + 2 - goto CONTINUE - end - if token == '...' then - if lastSep == false then - missSymbol ',' - end - lastSep = false - if not params then - params = {} - end - local vararg = { - type = '...', - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + 2, 'right'), - parent = params, - [1] = '...', - } - local chunk = Chunk[#Chunk] - chunk.vararg = vararg - params[#params+1] = vararg - if hasDots then - pushError { - type = 'ARGS_AFTER_DOTS', - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + 2, 'right'), - } - end - hasDots = true - Index = Index + 2 - goto CONTINUE - end - if CharMapWord[ssub(token, 1, 1)] then - if lastSep == false then - missSymbol ',' - end - lastSep = false - if not params then - params = {} - end - params[#params+1] = createLocal { - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + #token - 1, 'right'), - parent = params, - [1] = token, - } - if hasDots then - pushError { - type = 'ARGS_AFTER_DOTS', - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + #token - 1, 'right'), - } - end - if isKeyWord(token, Tokens[Index + 3]) 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, - bstart = funcRight, - keyword = { - [1] = funcLeft, - [2] = funcRight, - }, - } - Index = Index + 2 - 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 - func.bstart = 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 - local LastLocalCount = LocalCount - LocalCount = 0 - pushChunk(func) - local params - if func.name and func.name.type == 'getmethod' then - if func.name.type == 'getmethod' then - params = {} - params[1] = createLocal { - start = funcRight, - finish = funcRight, - parent = params, - [1] = 'self', - } - params[1].type = 'self' - end - end - if hasLeftParen then - params = params or {} - local parenLeft = getPosition(Tokens[Index], 'left') - Index = Index + 2 - params = parseParams(params) - params.type = 'funcargs' - params.start = parenLeft - params.finish = lastRightPosition() - params.parent = func - func.args = params - skipSpace(true) - if Tokens[Index + 1] == ')' then - local parenRight = getPosition(Tokens[Index], 'right') - func.finish = parenRight - func.bstart = parenRight - if params then - params.finish = parenRight - end - Index = Index + 2 - skipSpace(true) - else - func.finish = lastRightPosition() - func.bstart = func.finish - if params then - params.finish = func.finish - end - missSymbol ')' - end - else - missSymbol '(' - end - parseActions() - popChunk() - if Tokens[Index + 1] == 'end' then - local endLeft = getPosition(Tokens[Index], 'left') - local endRight = getPosition(Tokens[Index] + 2, 'right') - func.keyword[3] = endLeft - func.keyword[4] = endRight - func.finish = endRight - Index = Index + 2 - else - func.finish = lastRightPosition() - missEnd(funcLeft, funcRight) - end - LocalCount = LastLocalCount - return func -end - -local function checkNeedParen(source) - local token = Tokens[Index + 1] - if token ~= '.' - and token ~= ':' then - return source - end - local exp = parseSimple(source, false) - if exp == source then - return exp - end - pushError { - type = 'NEED_PAREN', - start = source.start, - finish = source.finish, - fix = { - title = 'FIX_ADD_PAREN', - { - start = source.start, - finish = source.start, - text = '(', - }, - { - start = source.finish, - finish = source.finish, - text = ')', - } - } - } - return exp -end - -local function parseExpUnit() - local token = Tokens[Index + 1] - if token == '(' then - local paren = parseParen() - return parseSimple(paren, false) - end - - if token == '...' then - local varargs = parseVarargs() - return varargs - end - - if token == '{' then - local table = parseTable() - if not table then - return nil - end - local exp = checkNeedParen(table) - return exp - end - - if CharMapStrSH[token] then - local string = parseShortString() - if not string then - return nil - end - local exp = checkNeedParen(string) - return exp - end - - if CharMapStrLH[token] then - local string = parseLongString() - if not string then - return nil - end - local exp = checkNeedParen(string) - return exp - end - - local number = parseNumber() - if number then - return number - end - - if ChunkFinishMap[token] then - return nil - end - - if token == 'nil' then - return parseNil() - end - - if token == 'true' - or token == 'false' then - return parseBoolean() - end - - if token == 'function' then - return parseFunction() - end - - local node = parseName() - if node then - return parseSimple(resolveName(node), false) - end - - return nil -end - -local function parseUnaryOP() - local token = Tokens[Index + 1] - local symbol = UnarySymbol[token] and token or UnaryAlias[token] - if not symbol then - return nil - end - local myLevel = UnarySymbol[symbol] - local op = { - type = symbol, - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + #symbol - 1, 'right'), - } - Index = Index + 2 - return op, myLevel -end - ----@param level integer # op level must greater than this level -local function parseBinaryOP(asAction, level) - local token = Tokens[Index + 1] - local symbol = (BinarySymbol[token] and token) - or BinaryAlias[token] - or (not asAction and BinaryActionAlias[token]) - if not symbol then - return nil - end - if symbol == '//' and State.options.nonstandardSymbol['//'] then - return nil - end - local myLevel = BinarySymbol[symbol] - if level and myLevel < level then - return nil - end - local op = { - type = symbol, - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + #token - 1, 'right'), - } - if not asAction then - if token == '=' then - pushError { - type = 'ERR_EQ_AS_ASSIGN', - start = op.start, - finish = op.finish, - fix = { - title = 'FIX_EQ_AS_ASSIGN', - { - start = op.start, - finish = op.finish, - text = '==', - } - } - } - end - end - if BinaryAlias[token] then - if not State.options.nonstandardSymbol[token] then - pushError { - type = 'ERR_NONSTANDARD_SYMBOL', - start = op.start, - finish = op.finish, - info = { - symbol = symbol, - }, - fix = { - title = 'FIX_NONSTANDARD_SYMBOL', - symbol = symbol, - { - start = op.start, - finish = op.finish, - text = symbol, - }, - } - } - end - end - if token == '//' - or token == '<<' - or token == '>>' then - if State.version ~= 'Lua 5.3' - and State.version ~= 'Lua 5.4' then - pushError { - type = 'UNSUPPORT_SYMBOL', - version = {'Lua 5.3', 'Lua 5.4'}, - start = op.start, - finish = op.finish, - info = { - version = State.version, - } - } - end - end - Index = Index + 2 - return op, myLevel -end - -function parseExp(asAction, level) - local exp - local uop, uopLevel = parseUnaryOP() - if uop then - skipSpace() - local child = parseExp(asAction, uopLevel) - -- 预计算负数 - if uop.type == '-' - and child - and (child.type == 'number' or child.type == 'integer') then - child.start = uop.start - child[1] = - child[1] - exp = child - else - exp = { - type = 'unary', - op = uop, - start = uop.start, - finish = child and child.finish or uop.finish, - [1] = child, - } - if child then - child.parent = exp - else - missExp() - end - end - else - exp = parseExpUnit() - if not exp then - return nil - end - end - - while true do - skipSpace() - local bop, bopLevel = parseBinaryOP(asAction, level) - if not bop then - break - end - - ::AGAIN:: - skipSpace() - local isForward = SymbolForward[bopLevel] - local child = parseExp(asAction, isForward and (bopLevel + 0.5) or bopLevel) - if not child then - if skipUnknownSymbol() then - goto AGAIN - else - missExp() - end - end - local bin = { - type = 'binary', - start = exp.start, - finish = child and child.finish or bop.finish, - op = bop, - [1] = exp, - [2] = child - } - exp.parent = bin - if child then - child.parent = bin - end - exp = bin - end - - return exp -end - -local function skipSeps() - while true do - skipSpace() - if Tokens[Index + 1] == ',' then - missExp() - Index = Index + 2 - else - break - end - end -end - ----@return parser.object? first ----@return parser.object? second ----@return parser.object[]? rest -local function parseSetValues() - skipSpace() - local first = parseExp() - if not first then - return nil - end - skipSpace() - if Tokens[Index + 1] ~= ',' then - return first - end - Index = Index + 2 - skipSeps() - local second = parseExp() - if not second then - missExp() - return first - end - skipSpace() - if Tokens[Index + 1] ~= ',' then - return first, second - end - Index = Index + 2 - skipSeps() - local third = parseExp() - if not third then - missExp() - return first, second - end - - local rest = { third } - while true do - skipSpace() - if Tokens[Index + 1] ~= ',' then - return first, second, rest - end - Index = Index + 2 - skipSeps() - local exp = parseExp() - if not exp then - missExp() - return first, second, rest - end - rest[#rest+1] = exp - end -end - -local function pushActionIntoCurrentChunk(action) - local chunk = Chunk[#Chunk] - if chunk then - chunk[#chunk+1] = action - action.parent = chunk - end -end - ----@return parser.object? second ----@return parser.object[]? rest -local function parseVarTails(parser, isLocal) - if Tokens[Index + 1] ~= ',' then - return nil - end - Index = Index + 2 - skipSpace() - local second = parser(true) - if not second then - missName() - return nil - end - if isLocal then - createLocal(second, parseLocalAttrs()) - end - skipSpace() - if Tokens[Index + 1] ~= ',' then - return second - end - Index = Index + 2 - skipSeps() - local third = parser(true) - if not third then - missName() - return second - end - if isLocal then - createLocal(third, parseLocalAttrs()) - end - local rest = { third } - while true do - skipSpace() - if Tokens[Index + 1] ~= ',' then - return second, rest - end - Index = Index + 2 - skipSeps() - local name = parser(true) - if not name then - missName() - return second, rest - end - if isLocal then - createLocal(name, parseLocalAttrs()) - end - rest[#rest+1] = name - end -end - -local function bindValue(n, v, index, lastValue, isLocal, isSet) - if isLocal then - if v and v.special then - addSpecial(v.special, n) - end - elseif isSet then - n.type = GetToSetMap[n.type] or n.type - if n.type == 'setlocal' then - local loc = n.node - if loc.attrs then - pushError { - type = 'SET_CONST', - start = n.start, - finish = n.finish, - } - end - end - end - if not v and lastValue then - if lastValue.type == 'call' - or lastValue.type == 'varargs' then - v = lastValue - if not v.extParent then - v.extParent = {} - end - end - end - if v then - if v.type == 'call' - or v.type == 'varargs' then - local select = { - type = 'select', - sindex = index, - start = v.start, - finish = v.finish, - vararg = v - } - if v.parent then - v.extParent[#v.extParent+1] = select - else - v.parent = select - end - v = select - end - n.value = v - n.range = v.finish - v.parent = n - end -end - -local function parseMultiVars(n1, parser, isLocal) - local n2, nrest = parseVarTails(parser, isLocal) - skipSpace() - local v1, v2, vrest - local isSet - local max = 1 - if expectAssign(not isLocal) then - v1, v2, vrest = parseSetValues() - isSet = true - if not v1 then - missExp() - end - end - local index = 1 - bindValue(n1, v1, index, nil, isLocal, isSet) - local lastValue = v1 - local lastVar = n1 - if n2 then - max = 2 - if not v2 then - index = 2 - end - bindValue(n2, v2, index, lastValue, isLocal, isSet) - lastValue = v2 or lastValue - lastVar = n2 - pushActionIntoCurrentChunk(n2) - end - if nrest then - for i = 1, #nrest do - local n = nrest[i] - local v = vrest and vrest[i] - max = i + 2 - if not v then - index = index + 1 - end - bindValue(n, v, index, lastValue, isLocal, isSet) - lastValue = v or lastValue - lastVar = n - pushActionIntoCurrentChunk(n) - end - end - - if isLocal then - local effect = lastValue and lastValue.finish or lastVar.finish - n1.effect = effect - if n2 then - n2.effect = effect - end - if nrest then - for i = 1, #nrest do - nrest[i].effect = effect - end - end - end - - if v2 and not n2 then - v2.redundant = { - max = max, - passed = 2, - } - pushActionIntoCurrentChunk(v2) - end - if vrest then - for i = 1, #vrest do - local v = vrest[i] - if not nrest or not nrest[i] then - v.redundant = { - max = max, - passed = i + 2, - } - pushActionIntoCurrentChunk(v) - end - end - end - - return n1, isSet -end - -local function compileExpAsAction(exp) - pushActionIntoCurrentChunk(exp) - if GetToSetMap[exp.type] then - skipSpace() - local isLocal - if exp.type == 'getlocal' and exp[1] == State.ENVMode then - exp.special = nil - -- TODO: need + 1 at the end - LocalCount = LocalCount - 1 - local loc = createLocal(exp, parseLocalAttrs()) - loc.locPos = exp.start - loc.effect = maxinteger - isLocal = true - skipSpace() - end - local action, isSet = parseMultiVars(exp, parseExp, isLocal) - if isSet - or action.type == 'getmethod' then - return action - end - end - - if exp.type == 'call' then - if exp.hasExit then - for i = #Chunk, 1, -1 do - local block = Chunk[i] - if block.type == 'ifblock' - or block.type == 'elseifblock' - or block.type == 'elseblock' - or block.type == 'function' then - block.hasExit = true - break - end - end - end - return exp - end - - if exp.type == 'binary' then - if GetToSetMap[exp[1].type] then - local op = exp.op - if op.type == '==' then - pushError { - type = 'ERR_ASSIGN_AS_EQ', - start = op.start, - finish = op.finish, - fix = { - title = 'FIX_ASSIGN_AS_EQ', - { - start = op.start, - finish = op.finish, - text = '=', - } - } - } - return - end - end - end - - pushError { - type = 'EXP_IN_ACTION', - start = exp.start, - finish = exp.finish, - } - - return exp -end - -local function parseLocal() - local locPos = getPosition(Tokens[Index], 'left') - Index = Index + 2 - skipSpace() - local word = peekWord() - if not word then - missName() - return nil - end - - if word == 'function' then - local func = parseFunction(true, true) - local name = func.name - if name then - func.name = nil - name.value = func - name.vstart = func.start - name.range = func.finish - name.locPos = locPos - func.parent = name - pushActionIntoCurrentChunk(name) - return name - else - missName(func.keyword[2]) - pushActionIntoCurrentChunk(func) - return func - end - end - - local name = parseName(true) - if not name then - missName() - return nil - end - local loc = createLocal(name, parseLocalAttrs()) - loc.locPos = locPos - loc.effect = maxinteger - pushActionIntoCurrentChunk(loc) - skipSpace() - parseMultiVars(loc, parseName, true) - - return loc -end - -local function parseDo() - local doLeft = getPosition(Tokens[Index], 'left') - local doRight = getPosition(Tokens[Index] + 1, 'right') - local obj = { - type = 'do', - start = doLeft, - finish = doRight, - bstart = doRight, - keyword = { - [1] = doLeft, - [2] = doRight, - }, - } - Index = Index + 2 - pushActionIntoCurrentChunk(obj) - pushChunk(obj) - parseActions() - popChunk() - if Tokens[Index + 1] == 'end' then - obj.finish = getPosition(Tokens[Index] + 2, 'right') - obj.keyword[3] = getPosition(Tokens[Index], 'left') - obj.keyword[4] = getPosition(Tokens[Index] + 2, 'right') - Index = Index + 2 - else - missEnd(doLeft, doRight) - end - if obj.locals then - LocalCount = LocalCount - #obj.locals - end - - return obj -end - -local function parseReturn() - local returnLeft = getPosition(Tokens[Index], 'left') - local returnRight = getPosition(Tokens[Index] + 5, 'right') - Index = Index + 2 - skipSpace() - local rtn = parseExpList(true) - if rtn then - rtn.type = 'return' - rtn.start = returnLeft - else - rtn = { - type = 'return', - start = returnLeft, - finish = returnRight, - } - end - pushActionIntoCurrentChunk(rtn) - for i = #Chunk, 1, -1 do - local block = Chunk[i] - if block.type == 'function' - or block.type == 'main' then - if not block.returns then - block.returns = {} - end - block.returns[#block.returns+1] = rtn - break - end - end - for i = #Chunk, 1, -1 do - local block = Chunk[i] - if block.type == 'ifblock' - or block.type == 'elseifblock' - or block.type == 'elseblock' - or block.type == 'function' then - block.hasReturn = true - break - end - end - - return rtn -end - -local function parseLabel() - local left = getPosition(Tokens[Index], 'left') - Index = Index + 2 - skipSpace() - local label = parseName() - skipSpace() - - if not label then - missName() - end - - if Tokens[Index + 1] == '::' then - Index = Index + 2 - else - if label then - missSymbol '::' - end - end - - if not label then - return nil - end - - label.type = 'label' - pushActionIntoCurrentChunk(label) - - local block = guide.getBlock(label) - if block then - if not block.labels then - block.labels = {} - end - local name = label[1] - local olabel = guide.getLabel(block, name) - if olabel then - if State.version == 'Lua 5.4' - or block == guide.getBlock(olabel) then - pushError { - type = 'REDEFINED_LABEL', - start = label.start, - finish = label.finish, - relative = { - { - olabel.start, - olabel.finish, - } - } - } - end - end - block.labels[name] = label - end - - if State.version == 'Lua 5.1' then - pushError { - type = 'UNSUPPORT_SYMBOL', - start = left, - finish = lastRightPosition(), - version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, - info = { - version = State.version, - } - } - return - end - return label -end - -local function parseGoTo() - local start = getPosition(Tokens[Index], 'left') - Index = Index + 2 - skipSpace() - - local action = parseName() - if not action then - missName() - return nil - end - - action.type = 'goto' - action.keyStart = start - - for i = #Chunk, 1, -1 do - local chunk = Chunk[i] - if chunk.type == 'function' - or chunk.type == 'main' then - if not chunk.gotos then - chunk.gotos = {} - end - chunk.gotos[#chunk.gotos+1] = action - break - end - end - for i = #Chunk, 1, -1 do - local chunk = Chunk[i] - if chunk.type == 'ifblock' - or chunk.type == 'elseifblock' - or chunk.type == 'elseblock' then - chunk.hasGoTo = true - break - end - end - - pushActionIntoCurrentChunk(action) - return action -end - -local function parseIfBlock(parent) - local ifLeft = getPosition(Tokens[Index], 'left') - local ifRight = getPosition(Tokens[Index] + 1, 'right') - Index = Index + 2 - local ifblock = { - type = 'ifblock', - parent = parent, - start = ifLeft, - finish = ifRight, - bstart = ifRight, - keyword = { - [1] = ifLeft, - [2] = ifRight, - } - } - skipSpace() - local filter = parseExp() - if filter then - ifblock.filter = filter - ifblock.finish = filter.finish - ifblock.bstart = ifblock.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.bstart = ifblock.finish - ifblock.keyword[3] = getPosition(Tokens[Index], 'left') - ifblock.keyword[4] = ifblock.finish - if thenToken == 'do' then - pushError { - type = 'ERR_THEN_AS_DO', - start = ifblock.keyword[3], - finish = ifblock.keyword[4], - fix = { - title = 'FIX_THEN_AS_DO', - { - start = ifblock.keyword[3], - finish = ifblock.keyword[4], - text = 'then', - } - } - } - end - Index = Index + 2 - else - missSymbol 'then' - end - pushChunk(ifblock) - parseActions() - popChunk() - ifblock.finish = getPosition(Tokens[Index], 'left') - if ifblock.locals then - LocalCount = LocalCount - #ifblock.locals - end - return ifblock -end - -local function parseElseIfBlock(parent) - local ifLeft = getPosition(Tokens[Index], 'left') - local ifRight = getPosition(Tokens[Index] + 5, 'right') - local elseifblock = { - type = 'elseifblock', - parent = parent, - start = ifLeft, - finish = ifRight, - bstart = ifRight, - keyword = { - [1] = ifLeft, - [2] = ifRight, - } - } - Index = Index + 2 - skipSpace() - local filter = parseExp() - if filter then - elseifblock.filter = filter - elseifblock.finish = filter.finish - elseifblock.bstart = elseifblock.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.bstart = elseifblock.finish - elseifblock.keyword[3] = getPosition(Tokens[Index], 'left') - elseifblock.keyword[4] = elseifblock.finish - if thenToken == 'do' then - pushError { - type = 'ERR_THEN_AS_DO', - start = elseifblock.keyword[3], - finish = elseifblock.keyword[4], - fix = { - title = 'FIX_THEN_AS_DO', - { - start = elseifblock.keyword[3], - finish = elseifblock.keyword[4], - text = 'then', - } - } - } - end - Index = Index + 2 - else - missSymbol 'then' - end - pushChunk(elseifblock) - parseActions() - popChunk() - elseifblock.finish = getPosition(Tokens[Index], 'left') - if elseifblock.locals then - LocalCount = LocalCount - #elseifblock.locals - end - return elseifblock -end - -local function parseElseBlock(parent) - local ifLeft = getPosition(Tokens[Index], 'left') - local ifRight = getPosition(Tokens[Index] + 3, 'right') - local elseblock = { - type = 'elseblock', - parent = parent, - start = ifLeft, - finish = ifRight, - bstart = ifRight, - keyword = { - [1] = ifLeft, - [2] = ifRight, - } - } - Index = Index + 2 - skipSpace() - pushChunk(elseblock) - parseActions() - popChunk() - elseblock.finish = getPosition(Tokens[Index], 'left') - if elseblock.locals then - LocalCount = LocalCount - #elseblock.locals - end - return elseblock -end - -local function parseIf() - local token = Tokens[Index + 1] - local left = getPosition(Tokens[Index], 'left') - local action = { - type = 'if', - start = left, - finish = getPosition(Tokens[Index] + #token - 1, 'right'), - } - pushActionIntoCurrentChunk(action) - if token ~= 'if' then - missSymbol('if', left, left) - end - local hasElse - while true do - local word = Tokens[Index + 1] - local child - if word == 'if' then - child = parseIfBlock(action) - elseif word == 'elseif' then - child = parseElseIfBlock(action) - elseif word == 'else' then - child = parseElseBlock(action) - end - if not child then - break - end - if hasElse then - pushError { - type = 'BLOCK_AFTER_ELSE', - start = child.start, - finish = child.finish, - } - end - if word == 'else' then - hasElse = true - end - action[#action+1] = child - action.finish = child.finish - skipSpace() - end - - if Tokens[Index + 1] == 'end' then - action.finish = getPosition(Tokens[Index] + 2, 'right') - Index = Index + 2 - else - missEnd(action[1].keyword[1], action[1].keyword[2]) - end - - return action -end - -local function parseFor() - local action = { - type = 'for', - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + 2, 'right'), - keyword = {}, - } - action.bstart = action.finish - 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() - local forStateVars - -- 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 - -- for x in ... uses 4 variables - forStateVars = 3 - LocalCount = LocalCount + forStateVars - if name then - local loc = createLocal(name) - loc.parent = action - action.finish = name.finish - action.bstart = action.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 - action.bstart = action.finish - end - local max = expList[2] - if max then - max.parent = expList - action.max = max - action.finish = max.finish - action.bstart = action.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 - action.bstart = action.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.bstart = action.finish - 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 - action.bstart = action.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 State.version == 'Lua 5.4' then - forStateVars = 4 - else - forStateVars = 3 - end - LocalCount = LocalCount + forStateVars - - 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.bstart = action.finish - 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 - if forStateVars then - LocalCount = LocalCount - forStateVars - end - - return action -end - -local function parseWhile() - local action = { - type = 'while', - start = getPosition(Tokens[Index], 'left'), - finish = getPosition(Tokens[Index] + 4, 'right'), - keyword = {}, - } - action.bstart = action.finish - 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.bstart = action.finish - 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.bstart = action.finish - 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', Tokens[Index + 3]) 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 - LocalLimited = false - Chunk = {} - Tokens = tokens(lua) - Index = 1 - ---@class parser.state - ---@field uri uri - ---@field lines integer[] - local state = { - version = version, - lua = lua, - ast = {}, - errs = {}, - comms = {}, - lines = { - [0] = 1, - size = #lua, - }, - options = options or {}, - } - if not state.options.nonstandardSymbol then - state.options.nonstandardSymbol = {} - end - State = state - if version == 'Lua 5.1' or version == 'LuaJIT' then - state.ENVMode = '@fenv' - else - state.ENVMode = '_ENV' - end - - pushError = function (err) - local errs = state.errs - if err.finish < err.start then - err.finish = err.start - end - local last = errs[#errs] - if last then - if last.start <= err.start and last.finish >= err.finish then - return - end - end - err.level = err.level or 'Error' - errs[#errs+1] = err - return err - end -end - -return function (lua, mode, version, options) - Mode = mode - initState(lua, version, options) - skipSpace() - if mode == 'Lua' then - State.ast = parseLua() - elseif mode == 'Nil' then - State.ast = parseNil() - elseif mode == 'Boolean' then - State.ast = parseBoolean() - elseif mode == 'String' then - State.ast = parseString() - elseif mode == 'Number' then - State.ast = parseNumber() - elseif mode == 'Name' then - State.ast = parseName() - elseif mode == 'Exp' then - State.ast = parseExp() - elseif mode == 'Action' then - State.ast = parseAction() - end - - if State.ast then - State.ast.state = State - end - - while true do - if Index <= #Tokens then - unknownSymbol() - Index = Index + 2 - else - break - end - end - - return State -end diff --git a/src/parser.old/guide.lua b/src/parser.old/guide.lua deleted file mode 100644 index e7eb375..0000000 --- a/src/parser.old/guide.lua +++ /dev/null @@ -1,1292 +0,0 @@ -local error = error -local type = type - ----@class parser.object ----@field bindDocs parser.object[] ----@field bindGroup parser.object[] ----@field bindSource parser.object ----@field value parser.object ----@field parent parser.object ----@field type string ----@field special string ----@field tag string ----@field args { [integer]: parser.object, start: integer, finish: integer } ----@field locals parser.object[] ----@field returns? parser.object[] ----@field breaks? parser.object[] ----@field exps parser.object[] ----@field keys parser.object ----@field uri uri ----@field start integer ----@field finish integer ----@field range integer ----@field effect integer ----@field bstart integer ----@field attrs string[] ----@field specials parser.object[] ----@field labels parser.object[] ----@field node parser.object ----@field field parser.object ----@field method parser.object ----@field index parser.object ----@field extends parser.object[]|parser.object ----@field types parser.object[] ----@field fields parser.object[] ----@field tkey parser.object ----@field tvalue parser.object ----@field tindex integer ----@field op parser.object ----@field next parser.object ----@field docParam parser.object ----@field sindex integer ----@field name parser.object ----@field call parser.object ----@field closure parser.object ----@field proto parser.object ----@field exp parser.object ----@field alias parser.object ----@field class parser.object ----@field enum parser.object ----@field vararg parser.object ----@field param parser.object ----@field overload parser.object ----@field docParamMap table ----@field upvalues table ----@field ref parser.object[] ----@field returnIndex integer ----@field assignIndex integer ----@field docIndex integer ----@field docs parser.object[] ----@field state table ----@field comment table ----@field optional boolean ----@field max parser.object ----@field init parser.object ----@field step parser.object ----@field redundant { max: integer, passed: integer } ----@field filter parser.object ----@field loc parser.object ----@field keyword integer[] ----@field casts parser.object[] ----@field mode? '+' | '-' ----@field hasGoTo? true ----@field hasReturn? true ----@field hasBreak? true ----@field hasExit? true ----@field [integer] parser.object|any ----@field package _root parser.object - ----@class guide ----@field debugMode boolean -local m = {} - -m.ANY = {""} - -m.notNamePattern = '[^%w_\x80-\xff]' -m.namePattern = '[%a_\x80-\xff][%w_\x80-\xff]*' -m.namePatternFull = '^' .. m.namePattern .. '$' - -local blockTypes = { - ['while'] = true, - ['in'] = true, - ['loop'] = true, - ['repeat'] = true, - ['do'] = true, - ['function'] = true, - ['if'] = true, - ['ifblock'] = true, - ['elseblock'] = true, - ['elseifblock'] = true, - ['main'] = true, -} - -local topBlockTypes = { - ['while'] = true, - ['function'] = true, - ['if'] = true, - ['ifblock'] = true, - ['elseblock'] = true, - ['elseifblock'] = true, - ['main'] = true, -} - -local breakBlockTypes = { - ['while'] = true, - ['in'] = true, - ['loop'] = true, - ['repeat'] = true, - ['for'] = true, -} - -local childMap = { - ['main'] = {'#', 'docs'}, - ['repeat'] = {'#', 'filter'}, - ['while'] = {'filter', '#'}, - ['in'] = {'keys', 'exps', '#'}, - ['loop'] = {'loc', 'init', 'max', 'step', '#'}, - ['do'] = {'#'}, - ['if'] = {'#'}, - ['ifblock'] = {'filter', '#'}, - ['elseifblock'] = {'filter', '#'}, - ['elseblock'] = {'#'}, - ['setfield'] = {'node', 'field', 'value'}, - ['getfield'] = {'node', 'field'}, - ['setmethod'] = {'node', 'method', 'value'}, - ['getmethod'] = {'node', 'method'}, - ['setindex'] = {'node', 'index', 'value'}, - ['getindex'] = {'node', 'index'}, - ['tableindex'] = {'index', 'value'}, - ['tablefield'] = {'field', 'value'}, - ['tableexp'] = {'value'}, - ['setglobal'] = {'value'}, - ['local'] = {'attrs', 'value'}, - ['setlocal'] = {'value'}, - ['return'] = {'#'}, - ['select'] = {'vararg'}, - ['table'] = {'#'}, - ['function'] = {'args', '#'}, - ['funcargs'] = {'#'}, - ['paren'] = {'exp'}, - ['call'] = {'node', 'args'}, - ['callargs'] = {'#'}, - ['list'] = {'#'}, - ['binary'] = {1, 2}, - ['unary'] = {1}, - - ['doc'] = {'#'}, - ['doc.class'] = {'class', '#extends', '#signs', 'comment'}, - ['doc.type'] = {'#types', 'name', 'comment'}, - ['doc.alias'] = {'alias', 'extends', 'comment'}, - ['doc.enum'] = {'enum', 'extends', 'comment'}, - ['doc.param'] = {'param', 'extends', 'comment'}, - ['doc.return'] = {'#returns', 'comment'}, - ['doc.field'] = {'field', 'extends', 'comment'}, - ['doc.generic'] = {'#generics', 'comment'}, - ['doc.generic.object'] = {'generic', 'extends', 'comment'}, - ['doc.vararg'] = {'vararg', 'comment'}, - ['doc.type.array'] = {'node'}, - ['doc.type.function'] = {'#args', '#returns', 'comment'}, - ['doc.type.table'] = {'#fields', 'comment'}, - ['doc.type.literal'] = {'node'}, - ['doc.type.arg'] = {'name', 'extends'}, - ['doc.type.field'] = {'name', 'extends'}, - ['doc.type.sign'] = {'node', '#signs'}, - ['doc.overload'] = {'overload', 'comment'}, - ['doc.see'] = {'name', 'comment'}, - ['doc.version'] = {'#versions'}, - ['doc.diagnostic'] = {'#names'}, - ['doc.as'] = {'as'}, - ['doc.cast'] = {'name', '#casts'}, - ['doc.cast.block'] = {'extends'}, - ['doc.operator'] = {'op', 'exp', 'extends'}, - ['doc.meta'] = {'name'}, -} - ----@type table -local compiledChildMap = setmetatable({}, {__index = function (self, name) - local defs = childMap[name] - if not defs then - self[name] = false - return false - end - local text = {} - text[#text+1] = 'local obj, list = ...' - for _, def in ipairs(defs) do - if def == '#' then - text[#text+1] = [[ -for i = 1, #obj do - list[#list+1] = obj[i] -end -]] - elseif type(def) == 'string' and def:sub(1, 1) == '#' then - local key = def:sub(2) - text[#text+1] = ([[ -local childs = obj.%s -if childs then - for i = 1, #childs do - list[#list+1] = childs[i] - end -end -]]):format(key) - elseif type(def) == 'string' then - text[#text+1] = ('list[#list+1] = obj.%s'):format(def) - else - text[#text+1] = ('list[#list+1] = obj[%q]'):format(def) - end - end - local buf = table.concat(text, '\n') - local f = load(buf, buf, 't') - self[name] = f - return f -end}) - -local eachChildMap = setmetatable({}, {__index = function (self, name) - local defs = childMap[name] - if not defs then - self[name] = false - return false - end - local text = {} - text[#text+1] = 'local obj, callback = ...' - for _, def in ipairs(defs) do - if def == '#' then - text[#text+1] = [[ -for i = 1, #obj do - callback(obj[i]) -end -]] - elseif type(def) == 'string' and def:sub(1, 1) == '#' then - local key = def:sub(2) - text[#text+1] = ([[ -local childs = obj.%s -if childs then - for i = 1, #childs do - callback(childs[i]) - end -end -]]):format(key) - elseif type(def) == 'string' then - text[#text+1] = ('callback(obj.%s)'):format(def) - else - text[#text+1] = ('callback(obj[%q])'):format(def) - end - end - local buf = table.concat(text, '\n') - local f = load(buf, buf, 't') - self[name] = f - return f -end}) - -m.actionMap = { - ['main'] = {'#'}, - ['repeat'] = {'#'}, - ['while'] = {'#'}, - ['in'] = {'#'}, - ['loop'] = {'#'}, - ['if'] = {'#'}, - ['ifblock'] = {'#'}, - ['elseifblock'] = {'#'}, - ['elseblock'] = {'#'}, - ['do'] = {'#'}, - ['function'] = {'#'}, - ['funcargs'] = {'#'}, -} - ---- 是否是字面量 ----@param obj table ----@return boolean -function m.isLiteral(obj) - local tp = obj.type - return tp == 'nil' - or tp == 'boolean' - or tp == 'string' - or tp == 'number' - or tp == 'integer' - or tp == 'table' - or tp == 'function' - or tp == 'doc.type.function' - or tp == 'doc.type.table' - or tp == 'doc.type.string' - or tp == 'doc.type.integer' - or tp == 'doc.type.boolean' - or tp == 'doc.type.code' - or tp == 'doc.type.array' -end - ---- 获取字面量 ----@param obj table ----@return any -function m.getLiteral(obj) - if m.isLiteral(obj) then - return obj[1] - end - return nil -end - ---- 寻找父函数 ----@param obj parser.object ----@return parser.object? -function m.getParentFunction(obj) - for _ = 1, 10000 do - obj = obj.parent - if not obj then - break - end - local tp = obj.type - if tp == 'function' or tp == 'main' then - return obj - end - end - return nil -end - ---- 寻找所在区块 ----@param obj parser.object ----@return parser.object? -function m.getBlock(obj) - for _ = 1, 10000 do - if not obj then - return nil - end - local tp = obj.type - if blockTypes[tp] then - return obj - end - if obj == obj.parent then - error('obj == obj.parent?' .. obj.type) - end - obj = obj.parent - end - -- make stack - local stack = {} - for _ = 1, 10 do - stack[#stack+1] = ('%s:%s'):format(obj.type, obj.finish) - obj = obj.parent - if not obj then - break - end - end - error('guide.getBlock overstack:' .. table.concat(stack, ' -> ')) -end - ---- 寻找所在父区块 ----@param obj parser.object ----@return parser.object? -function m.getParentBlock(obj) - for _ = 1, 10000 do - obj = obj.parent - if not obj then - return nil - end - local tp = obj.type - if blockTypes[tp] then - return obj - end - end - error('guide.getParentBlock overstack') -end - ---- 寻找所在可break的父区块 ----@param obj parser.object ----@return parser.object? -function m.getBreakBlock(obj) - for _ = 1, 10000 do - obj = obj.parent - if not obj then - return nil - end - local tp = obj.type - if breakBlockTypes[tp] then - return obj - end - if tp == 'function' then - return nil - end - end - error('guide.getBreakBlock overstack') -end - ---- 寻找doc的主体 ----@param obj parser.object ----@return parser.object -function m.getDocState(obj) - for _ = 1, 10000 do - local parent = obj.parent - if not parent then - return obj - end - if parent.type == 'doc' then - return obj - end - obj = parent - end - error('guide.getDocState overstack') -end - ---- 寻找所在父类型 ----@param obj parser.object ----@return parser.object? -function m.getParentType(obj, want) - for _ = 1, 10000 do - obj = obj.parent - if not obj then - return nil - end - if want == obj.type then - return obj - end - end - error('guide.getParentType overstack') -end - ---- 寻找根区块 ----@param obj parser.object ----@return parser.object -function m.getRoot(obj) - local source = obj - if source._root then - return source._root - end - for _ = 1, 10000 do - if obj.type == 'main' then - source._root = obj - return obj - end - if obj._root then - source._root = obj._root - return source._root - end - local parent = obj.parent - if not parent then - error('Can not find out root:' .. tostring(obj.type)) - end - obj = parent - end - error('guide.getRoot overstack') -end - ----@param obj parser.object | { uri: uri } ----@return uri -function m.getUri(obj) - if obj.uri then - return obj.uri - end - local root = m.getRoot(obj) - if root then - return root.uri or '' - end - return '' -end - ----@return parser.object? -function m.getENV(source, start) - if not start then - start = 1 - end - return m.getLocal(source, '_ENV', start) - or m.getLocal(source, '@fenv', start) -end - ---- 获取指定区块中可见的局部变量 ----@param source parser.object ----@param name string # 变量名 ----@param pos integer # 可见位置 ----@return parser.object? -function m.getLocal(source, name, pos) - local block = source - -- find nearest source - for _ = 1, 10000 do - if not block then - return nil - end - if block.type == 'main' then - break - end - if block.start <= pos - and block.finish >= pos - and blockTypes[block.type] then - break - end - block = block.parent - end - - m.eachSourceContain(block, pos, function (src) - if blockTypes[src.type] - and (src.finish - src.start) < (block.finish - src.start) then - block = src - end - end) - - for _ = 1, 10000 do - if not block then - break - end - local res - if block.locals then - for _, loc in ipairs(block.locals) do - if loc[1] == name - and loc.effect <= pos then - if not res or res.effect < loc.effect then - res = loc - end - end - end - end - if res then - return res - end - block = block.parent - end - return nil -end - ---- 获取指定区块中所有的可见局部变量名称 -function m.getVisibleLocals(block, pos) - local result = {} - m.eachSourceContain(m.getRoot(block), pos, function (source) - local locals = source.locals - if locals then - for i = 1, #locals do - local loc = locals[i] - local name = loc[1] - if loc.effect <= pos then - result[name] = loc - end - end - end - end) - return result -end - ---- 获取指定区块中可见的标签 ----@param block parser.object ----@param name string -function m.getLabel(block, name) - local current = m.getBlock(block) - for _ = 1, 10000 do - if not current then - return nil - end - local labels = current.labels - if labels then - local label = labels[name] - if label then - return label - end - end - if current.type == 'function' then - return nil - end - current = m.getParentBlock(current) - end - error('guide.getLocal overstack') -end - -function m.getStartFinish(source) - local start = source.start - local finish = source.finish - if not start then - local first = source[1] - if not first then - return nil, nil - end - local last = source[#source] - start = first.start - finish = last.finish - end - return start, finish -end - -function m.getRange(source) - local start = source.vstart or source.start - local finish = source.range or source.finish - if not start then - local first = source[1] - if not first then - return nil, nil - end - local last = source[#source] - start = first.vstart or first.start - finish = last.range or last.finish - end - return start, finish -end - ---- 判断source是否包含position -function m.isContain(source, position) - local start, finish = m.getStartFinish(source) - if not start then - return false - end - return start <= position and finish >= position -end - ---- 判断position在source的影响范围内 ---- ---- 主要针对赋值等语句时,key包含value -function m.isInRange(source, position) - local start, finish = m.getRange(source) - if not start then - return false - end - return start <= position and finish >= position -end - -function m.isBetween(source, tStart, tFinish) - local start, finish = m.getStartFinish(source) - if not start then - return false - end - return start <= tFinish and finish >= tStart -end - -function m.isBetweenRange(source, tStart, tFinish) - local start, finish = m.getRange(source) - if not start then - return false - end - return start <= tFinish and finish >= tStart -end - ---- 添加child -local function addChilds(list, obj) - local tp = obj.type - if not tp then - return - end - local f = compiledChildMap[tp] - if not f then - return - end - f(obj, list) -end - ---- 遍历所有包含position的source ----@param ast parser.object ----@param position integer ----@param callback fun(src: parser.object): any -function m.eachSourceContain(ast, position, callback) - local list = { ast } - local mark = {} - while true do - local len = #list - if len == 0 then - return - end - local obj = list[len] - list[len] = nil - if not mark[obj] then - mark[obj] = true - if m.isInRange(obj, position) then - if m.isContain(obj, position) then - local res = callback(obj) - if res ~= nil then - return res - end - end - addChilds(list, obj) - end - end - end -end - ---- 遍历所有在某个范围内的source -function m.eachSourceBetween(ast, start, finish, callback) - local list = { ast } - local mark = {} - while true do - local len = #list - if len == 0 then - return - end - local obj = list[len] - list[len] = nil - if not mark[obj] then - mark[obj] = true - if m.isBetweenRange(obj, start, finish) then - if m.isBetween(obj, start, finish) then - local res = callback(obj) - if res ~= nil then - return res - end - end - addChilds(list, obj) - end - end - end -end - -local function getSourceTypeCache(ast) - local cache = ast._typeCache - if not cache then - cache = {} - ast._typeCache = cache - m.eachSource(ast, function (source) - local tp = source.type - if not tp then - return - end - local myCache = cache[tp] - if not myCache then - myCache = {} - cache[tp] = myCache - end - myCache[#myCache+1] = source - end) - end - return cache -end - ---- 遍历所有指定类型的source ----@param ast parser.object ----@param type string ----@param callback fun(src: parser.object) ----@return any -function m.eachSourceType(ast, type, callback) - local cache = getSourceTypeCache(ast) - local myCache = cache[type] - if not myCache then - return - end - for i = 1, #myCache do - local res = callback(myCache[i]) - if res ~= nil then - return res - end - end -end - ----@param ast parser.object ----@param tps string[] ----@param callback fun(src: parser.object) -function m.eachSourceTypes(ast, tps, callback) - local cache = getSourceTypeCache(ast) - for x = 1, #tps do - local tpCache = cache[tps[x]] - if tpCache then - for i = 1, #tpCache do - callback(tpCache[i]) - end - end - end -end - ---- 遍历所有的source ----@param ast parser.object ----@param callback fun(src: parser.object): boolean? -function m.eachSource(ast, callback) - local cache = ast._eachCache - if not cache then - cache = { ast } - ast._eachCache = cache - local mark = {} - local index = 1 - while true do - local obj = cache[index] - if not obj then - break - end - index = index + 1 - if not mark[obj] then - mark[obj] = true - addChilds(cache, obj) - end - end - end - for i = 1, #cache do - local res = callback(cache[i]) - if res == false then - return - end - end -end - ----@param source parser.object ----@param callback fun(src: parser.object) -function m.eachChild(source, callback) - local f = eachChildMap[source.type] - if not f then - return - end - f(source, callback) -end - ---- 获取指定的 special ----@param ast parser.object ----@param name string ----@param callback fun(src: parser.object) -function m.eachSpecialOf(ast, name, callback) - local root = m.getRoot(ast) - local state = root.state - if not state.specials then - return - end - local specials = state.specials[name] - if not specials then - return - end - for i = 1, #specials do - callback(specials[i]) - end -end - ---- 将 position 拆分成行号与列号 ---- ---- 第一行是0 ----@param position integer ----@return integer row ----@return integer col -function m.rowColOf(position) - return position // 10000, position % 10000 -end - ---- 将行列合并为 position ---- ---- 第一行是0 ----@param row integer ----@param col integer ----@return integer -function m.positionOf(row, col) - return row * 10000 + math.min(col, 10000 - 1) -end - -function m.positionToOffsetByLines(lines, position) - local row, col = m.rowColOf(position) - if row < 0 then - return 0 - end - if row > #lines then - return lines.size - end - local offset = lines[row] + col - 1 - if lines[row + 1] and offset >= lines[row + 1] then - return lines[row + 1] - 1 - elseif offset > lines.size then - return lines.size - end - return offset -end - ---- 返回全文光标位置 ----@param state any ----@param position integer -function m.positionToOffset(state, position) - return m.positionToOffsetByLines(state.lines, position) -end - ----@param lines integer[] ----@param offset integer -function m.offsetToPositionByLines(lines, offset) - local left = 0 - local right = #lines - local row = 0 - while true do - row = (left + right) // 2 - if row == left then - if right ~= left then - if lines[right] - 1 <= offset then - row = right - end - end - break - end - local start = lines[row] - 1 - if start > offset then - right = row - else - left = row - end - end - local col = offset - lines[row] + 1 - return m.positionOf(row, col) -end - -function m.offsetToPosition(state, offset) - return m.offsetToPositionByLines(state.lines, offset) -end - -function m.getLineRange(state, row) - if not state.lines[row] then - return 0 - end - local nextLineStart = state.lines[row + 1] or #state.lua - for i = nextLineStart - 1, state.lines[row], -1 do - local w = state.lua:sub(i, i) - if w ~= '\r' and w ~= '\n' then - return i - state.lines[row] + 1 - end - end - return 0 -end - -local assignTypeMap = { - ['setglobal'] = true, - ['local'] = true, - ['self'] = true, - ['setlocal'] = true, - ['setfield'] = true, - ['setmethod'] = true, - ['setindex'] = true, - ['tablefield'] = true, - ['tableindex'] = true, - ['label'] = true, - ['doc.class'] = true, - ['doc.alias'] = true, - ['doc.enum'] = true, - ['doc.field'] = true, - ['doc.class.name'] = true, - ['doc.alias.name'] = true, - ['doc.enum.name'] = true, - ['doc.field.name'] = true, - ['doc.type.field'] = true, - ['doc.type.array'] = true, -} -function m.isAssign(source) - local tp = source.type - if assignTypeMap[tp] then - return true - end - if tp == 'call' then - local special = m.getSpecial(source.node) - if special == 'rawset' then - return true - end - end - return false -end - -local getTypeMap = { - ['getglobal'] = true, - ['getlocal'] = true, - ['getfield'] = true, - ['getmethod'] = true, - ['getindex'] = true, -} -function m.isGet(source) - local tp = source.type - if getTypeMap[tp] then - return true - end - if tp == 'call' then - local special = m.getSpecial(source.node) - if special == 'rawget' then - return true - end - end - return false -end - -function m.getSpecial(source) - if not source then - return nil - end - return source.special -end - -function m.getKeyNameOfLiteral(obj) - if not obj then - return nil - end - local tp = obj.type - if tp == 'field' - or tp == 'method' then - return obj[1] - elseif tp == 'string' - or tp == 'number' - or tp == 'integer' - or tp == 'boolean' - or tp == 'doc.type.integer' - or tp == 'doc.type.string' - or tp == 'doc.type.boolean' then - return obj[1] - end -end - ----@return string? -function m.getKeyName(obj) - if not obj then - return nil - end - local tp = obj.type - if tp == 'getglobal' - or tp == 'setglobal' then - return obj[1] - elseif tp == 'local' - or tp == 'self' - or tp == 'getlocal' - or tp == 'setlocal' then - return obj[1] - elseif tp == 'getfield' - or tp == 'setfield' - or tp == 'tablefield' then - if obj.field then - return obj.field[1] - end - elseif tp == 'getmethod' - or tp == 'setmethod' then - if obj.method then - return obj.method[1] - end - elseif tp == 'getindex' - or tp == 'setindex' - or tp == 'tableindex' then - return m.getKeyNameOfLiteral(obj.index) - elseif tp == 'tableexp' then - return obj.tindex - elseif tp == 'field' - or tp == 'method' then - return obj[1] - elseif tp == 'doc.class' then - return obj.class[1] - elseif tp == 'doc.alias' then - return obj.alias[1] - elseif tp == 'doc.enum' then - return obj.enum[1] - elseif tp == 'doc.field' then - return obj.field[1] - elseif tp == 'doc.field.name' - or tp == 'doc.type.name' - or tp == 'doc.class.name' - or tp == 'doc.alias.name' - or tp == 'doc.enum.name' - or tp == 'doc.extends.name' then - return obj[1] - elseif tp == 'doc.type.field' then - return m.getKeyName(obj.name) - end - return m.getKeyNameOfLiteral(obj) -end - -function m.getKeyTypeOfLiteral(obj) - if not obj then - return nil - end - local tp = obj.type - if tp == 'field' - or tp == 'method' then - return 'string' - elseif tp == 'string' then - return 'string' - elseif tp == 'number' then - return 'number' - elseif tp == 'integer' then - return 'integer' - elseif tp == 'boolean' then - return 'boolean' - end -end - -function m.getKeyType(obj) - if not obj then - return nil - end - local tp = obj.type - if tp == 'getglobal' - or tp == 'setglobal' then - return 'string' - elseif tp == 'local' - or tp == 'self' - or tp == 'getlocal' - or tp == 'setlocal' then - return 'local' - elseif tp == 'getfield' - or tp == 'setfield' - or tp == 'tablefield' then - return 'string' - elseif tp == 'getmethod' - or tp == 'setmethod' then - return 'string' - elseif tp == 'getindex' - or tp == 'setindex' - or tp == 'tableindex' then - return m.getKeyTypeOfLiteral(obj.index) - elseif tp == 'tableexp' then - return 'integer' - elseif tp == 'field' - or tp == 'method' then - return 'string' - elseif tp == 'doc.class' then - return 'string' - elseif tp == 'doc.alias' then - return 'string' - elseif tp == 'doc.enum' then - return 'string' - elseif tp == 'doc.field' then - return type(obj.field[1]) - elseif tp == 'doc.type.field' then - return type(obj.name[1]) - end - if tp == 'doc.field.name' then - return type(obj[1]) - end - return m.getKeyTypeOfLiteral(obj) -end - ----是否是全局变量(包括 _G.XXX 形式) ----@param source parser.object ----@return boolean -function m.isGlobal(source) - if source._isGlobal ~= nil then - return source._isGlobal - end - if source.tag == '_ENV' then - source._isGlobal = true - return false - end - if source.special == '_G' then - source._isGlobal = true - return true - end - if source.type == 'setglobal' - or source.type == 'getglobal' then - if source.node and source.node.tag == '_ENV' then - source._isGlobal = true - return true - end - end - if source.type == 'setfield' - or source.type == 'getfield' - or source.type == 'setindex' - or source.type == 'getindex' then - local current = source - while current do - local node = current.node - if not node then - break - end - if node.special == '_G' then - source._isGlobal = true - return true - end - if m.getKeyName(node) ~= '_G' then - break - end - current = node - end - end - if source.type == 'call' then - local node = source.node - if node.special == 'rawget' - or node.special == 'rawset' then - if source.args and source.args[1] then - local isGlobal = source.args[1].special == '_G' - source._isGlobal = isGlobal - return isGlobal - end - end - end - source._isGlobal = false - return false -end - -function m.isInString(ast, position) - return m.eachSourceContain(ast, position, function (source) - if source.type == 'string' - and source.start < position then - return true - end - end) -end - -function m.isInComment(ast, offset) - for _, com in ipairs(ast.state.comms) do - if offset >= com.start and offset <= com.finish then - return true - end - end - return false -end - -function m.isOOP(source) - if source.type == 'setmethod' - or source.type == 'getmethod' then - return true - end - if source.type == 'method' - or source.type == 'field' - or source.type == 'function' then - return m.isOOP(source.parent) - end - return false -end - -local basicTypeMap = { - ['unknown'] = true, - ['any'] = true, - ['true'] = true, - ['false'] = true, - ['nil'] = true, - ['boolean'] = true, - ['integer'] = true, - ['number'] = true, - ['string'] = true, - ['table'] = true, - ['function'] = true, - ['thread'] = true, - ['userdata'] = true, -} - ----@param str string ----@return boolean -function m.isBasicType(str) - return basicTypeMap[str] == true -end - ----@param source parser.object ----@return boolean -function m.isBlockType(source) - return blockTypes[source.type] == true -end - ----@param source parser.object ----@return parser.object? -function m.getSelfNode(source) - if source.type == 'getlocal' - or source.type == 'setlocal' then - source = source.node - end - if source.type ~= 'self' then - return nil - end - local args = source.parent - if args.type == 'callargs' then - local call = args.parent - if call.type ~= 'call' then - return nil - end - local getmethod = call.node - if getmethod.type ~= 'getmethod' then - return nil - end - return getmethod.node - end - if args.type == 'funcargs' then - return m.getFunctionSelfNode(args.parent) - end - return nil -end - ----@param func parser.object ----@return parser.object? -function m.getFunctionSelfNode(func) - if func.type ~= 'function' then - return nil - end - local parent = func.parent - if parent.type == 'setmethod' - or parent.type == 'setfield' then - return parent.node - end - return nil -end - ----@param source parser.object ----@return parser.object? -function m.getTopBlock(source) - for _ = 1, 1000 do - local block = source.parent - if not block then - return nil - end - if topBlockTypes[block.type] then - return block - end - source = block - end - return nil -end - ----@param source parser.object ----@return boolean -function m.isParam(source) - if source.type ~= 'local' - and source.type ~= 'self' then - return false - end - if source.parent.type ~= 'funcargs' then - return false - end - return true -end - -return m diff --git a/src/parser.old/init.lua b/src/parser.old/init.lua deleted file mode 100644 index bc004f7..0000000 --- a/src/parser.old/init.lua +++ /dev/null @@ -1,8 +0,0 @@ -local api = { - compile = require 'parser.compile', - lines = require 'parser.lines', - guide = require 'parser.guide', - luadoc = require 'parser.luadoc', -} - -return api diff --git a/src/parser.old/lines.lua b/src/parser.old/lines.lua deleted file mode 100644 index 964aabf..0000000 --- a/src/parser.old/lines.lua +++ /dev/null @@ -1,24 +0,0 @@ -local sfind = string.find -local ssub = string.sub - ----@param text string -return function (text) - local current = 1 - local lines = {} - lines[0] = 1 - local i = 0 - while true do - local pos = sfind(text,'[\r\n]', current) - if not pos then - break - end - i = i + 1 - if ssub(text, pos, pos + 1) == '\r\n' then - current = pos + 2 - else - current = pos + 1 - end - lines[i] = current - end - return lines -end diff --git a/src/parser.old/luadoc.lua b/src/parser.old/luadoc.lua deleted file mode 100644 index 1a88d38..0000000 --- a/src/parser.old/luadoc.lua +++ /dev/null @@ -1,2070 +0,0 @@ -local m = require 'lpeglabel' -local re = require 'parser.relabel' -local guide = require 'parser.guide' -local compile = require 'parser.compile' -local util = require 'utility' - -local TokenTypes, TokenStarts, TokenFinishs, TokenContents, TokenMarks ----@type integer -local Ci ----@type integer -local Offset -local pushWarning, NextComment, Lines -local parseType, parseTypeUnit ----@type any -local Parser = re.compile([[ -Main <- (Token / Sp)* -Sp <- %s+ -X16 <- [a-fA-F0-9] -Token <- Integer / Name / String / Code / Symbol -Name <- ({} {%name} {}) - -> Name -Integer <- ({} {'-'? [0-9]+} !'.' {}) - -> Integer -Code <- ({} '`' { (!'`' .)*} '`' {}) - -> Code -String <- ({} StringDef {}) - -> String -StringDef <- {'"'} - {~(Esc / !'"' .)*~} -> 1 - ('"'?) - / {"'"} - {~(Esc / !"'" .)*~} -> 1 - ("'"?) - / '[' {:eq: '='* :} '[' - =eq -> LongStringMark - {(!StringClose .)*} -> 1 - StringClose? -StringClose <- ']' =eq ']' -Esc <- '\' -> '' - EChar -EChar <- 'a' -> ea - / 'b' -> eb - / 'f' -> ef - / 'n' -> en - / 'r' -> er - / 't' -> et - / 'v' -> ev - / '\' - / '"' - / "'" - / %nl - / ('z' (%nl / %s)*) -> '' - / ('x' {X16 X16}) -> Char16 - / ([0-9] [0-9]? [0-9]?) -> Char10 - / ('u{' {X16*} '}') -> CharUtf8 -Symbol <- ({} { - [:|,;<>()?+#{}] - / '[]' - / '...' - / '[' - / ']' - / '-' !'-' - } {}) - -> Symbol -]], { - s = m.S' \t\v\f', - ea = '\a', - eb = '\b', - ef = '\f', - en = '\n', - er = '\r', - et = '\t', - ev = '\v', - name = (m.R('az', 'AZ', '09', '\x80\xff') + m.S('_')) * (m.R('az', 'AZ', '__', '09', '\x80\xff') + m.S('_.*-'))^0, - Char10 = function (char) - ---@type integer? - char = tonumber(char) - if not char or char < 0 or char > 255 then - return '' - end - return string.char(char) - end, - Char16 = function (char) - return string.char(tonumber(char, 16)) - end, - CharUtf8 = function (char) - if #char == 0 then - return '' - end - local v = tonumber(char, 16) - if not v then - return '' - end - if v >= 0 and v <= 0x10FFFF then - return utf8.char(v) - end - return '' - end, - LongStringMark = function (back) - return '[' .. back .. '[' - end, - Name = function (start, content, finish) - Ci = Ci + 1 - TokenTypes[Ci] = 'name' - TokenStarts[Ci] = start - TokenFinishs[Ci] = finish - 1 - TokenContents[Ci] = content - end, - String = function (start, mark, content, finish) - Ci = Ci + 1 - TokenTypes[Ci] = 'string' - TokenStarts[Ci] = start - TokenFinishs[Ci] = finish - 1 - TokenContents[Ci] = content - TokenMarks[Ci] = mark - end, - Integer = function (start, content, finish) - Ci = Ci + 1 - TokenTypes[Ci] = 'integer' - TokenStarts[Ci] = start - TokenFinishs[Ci] = finish - 1 - TokenContents[Ci] = math.tointeger(content) - end, - Code = function (start, content, finish) - Ci = Ci + 1 - TokenTypes[Ci] = 'code' - TokenStarts[Ci] = start - TokenFinishs[Ci] = finish - 1 - TokenContents[Ci] = content - end, - Symbol = function (start, content, finish) - Ci = Ci + 1 - TokenTypes[Ci] = 'symbol' - TokenStarts[Ci] = start - TokenFinishs[Ci] = finish - 1 - TokenContents[Ci] = content - end, -}) - ----@alias parser.visibleType 'public' | 'protected' | 'private' | 'package' - ----@class parser.object ----@field literal boolean ----@field signs parser.object[] ----@field originalComment parser.object ----@field as? parser.object ----@field touch? integer ----@field module? string ----@field async? boolean ----@field versions? table[] ----@field names? parser.object[] ----@field path? string ----@field bindComments? parser.object[] ----@field visible? parser.visibleType ----@field operators? parser.object[] ----@field calls? parser.object[] ----@field generics? parser.object[] ----@field generic? parser.object - -local function parseTokens(text, offset) - Ci = 0 - Offset = offset - TokenTypes = {} - TokenStarts = {} - TokenFinishs = {} - TokenContents = {} - TokenMarks = {} - Parser:match(text) - Ci = 0 -end - -local function peekToken(offset) - offset = offset or 1 - return TokenTypes[Ci + offset], TokenContents[Ci + offset] -end - ----@return string? tokenType ----@return string? tokenContent -local function nextToken() - Ci = Ci + 1 - if not TokenTypes[Ci] then - Ci = Ci - 1 - return nil, nil - end - return TokenTypes[Ci], TokenContents[Ci] -end - -local function checkToken(tp, content, offset) - offset = offset or 0 - return TokenTypes[Ci + offset] == tp - and TokenContents[Ci + offset] == content -end - -local function getStart() - if Ci == 0 then - return Offset - end - return TokenStarts[Ci] + Offset -end - ----@return integer -local function getFinish() - if Ci == 0 then - return Offset - end - return TokenFinishs[Ci] + Offset + 1 -end - -local function getMark() - return TokenMarks[Ci] -end - -local function try(callback) - local savePoint = Ci - -- rollback - local suc = callback() - if not suc then - Ci = savePoint - end - return suc -end - -local function parseName(tp, parent) - local nameTp, nameText = peekToken() - if nameTp ~= 'name' then - return nil - end - nextToken() - local name = { - type = tp, - start = getStart(), - finish = getFinish(), - parent = parent, - [1] = nameText, - } - return name -end - -local function nextSymbolOrError(symbol) - if checkToken('symbol', symbol, 1) then - nextToken() - return true - end - pushWarning { - type = 'LUADOC_MISS_SYMBOL', - start = getFinish(), - finish = getFinish(), - info = { - symbol = symbol, - } - } - return false -end - -local function parseIndexField(parent) - if not checkToken('symbol', '[', 1) then - return nil - end - nextToken() - local field = parseType(parent) - nextSymbolOrError ']' - return field -end - -local function parseTable(parent) - if not checkToken('symbol', '{', 1) then - return nil - end - nextToken() - local typeUnit = { - type = 'doc.type.table', - start = getStart(), - parent = parent, - fields = {}, - } - - while true do - if checkToken('symbol', '}', 1) then - nextToken() - break - end - local field = { - type = 'doc.type.field', - parent = typeUnit, - } - - do - local needCloseParen - if checkToken('symbol', '(', 1) then - nextToken() - needCloseParen = true - end - field.name = parseName('doc.field.name', field) - or parseIndexField(field) - if not field.name then - pushWarning { - type = 'LUADOC_MISS_FIELD_NAME', - start = getFinish(), - finish = getFinish(), - } - break - end - if not field.start then - field.start = field.name.start - end - if checkToken('symbol', '?', 1) then - nextToken() - field.optional = true - end - field.finish = getFinish() - if not nextSymbolOrError(':') then - break - end - field.extends = parseType(field) - if not field.extends then - break - end - field.finish = getFinish() - if needCloseParen then - nextSymbolOrError ')' - end - end - - typeUnit.fields[#typeUnit.fields+1] = field - if checkToken('symbol', ',', 1) - or checkToken('symbol', ';', 1) then - nextToken() - else - nextSymbolOrError('}') - break - end - end - typeUnit.finish = getFinish() - return typeUnit -end - -local function parseSigns(parent) - if not checkToken('symbol', '<', 1) then - return nil - end - nextToken() - local signs = {} - while true do - local sign = parseName('doc.generic.name', parent) - if not sign then - pushWarning { - type = 'LUADOC_MISS_SIGN_NAME', - start = getFinish(), - finish = getFinish(), - } - break - end - signs[#signs+1] = sign - if checkToken('symbol', ',', 1) then - nextToken() - else - break - end - end - nextSymbolOrError '>' - return signs -end - -local function parseDots(tp, parent) - if not checkToken('symbol', '...', 1) then - return - end - nextToken() - local dots = { - type = tp, - start = getStart(), - finish = getFinish(), - parent = parent, - [1] = '...', - } - return dots -end - -local function parseTypeUnitFunction(parent) - if not checkToken('name', 'fun', 1) then - return nil - end - nextToken() - local typeUnit = { - type = 'doc.type.function', - parent = parent, - start = getStart(), - args = {}, - returns = {}, - } - if not nextSymbolOrError('(') then - return nil - end - while true do - if checkToken('symbol', ')', 1) then - nextToken() - break - end - local arg = { - type = 'doc.type.arg', - parent = typeUnit, - } - arg.name = parseName('doc.type.arg.name', arg) - or parseDots('doc.type.arg.name', arg) - if not arg.name then - pushWarning { - type = 'LUADOC_MISS_ARG_NAME', - start = getFinish(), - finish = getFinish(), - } - break - end - if not arg.start then - arg.start = arg.name.start - end - if checkToken('symbol', '?', 1) then - nextToken() - arg.optional = true - end - arg.finish = getFinish() - if checkToken('symbol', ':', 1) then - nextToken() - arg.extends = parseType(arg) - end - arg.finish = getFinish() - typeUnit.args[#typeUnit.args+1] = arg - if checkToken('symbol', ',', 1) then - nextToken() - else - nextSymbolOrError(')') - break - end - end - if checkToken('symbol', ':', 1) then - nextToken() - local needCloseParen - if checkToken('symbol', '(', 1) then - nextToken() - needCloseParen = true - end - while true do - local name - try(function () - local returnName = parseName('doc.return.name', typeUnit) - or parseDots('doc.return.name', typeUnit) - if not returnName then - return false - end - if checkToken('symbol', ':', 1) then - nextToken() - name = returnName - return true - end - if returnName[1] == '...' then - name = returnName - return false - end - return false - end) - local rtn = parseType(typeUnit) - if not rtn then - break - end - rtn.name = name - if checkToken('symbol', '?', 1) then - nextToken() - rtn.optional = true - end - typeUnit.returns[#typeUnit.returns+1] = rtn - if checkToken('symbol', ',', 1) then - nextToken() - else - break - end - end - if needCloseParen then - nextSymbolOrError ')' - end - end - typeUnit.finish = getFinish() - return typeUnit -end - -local function parseFunction(parent) - local _, content = peekToken() - if content == 'async' then - nextToken() - local pos = getStart() - local tp, cont = peekToken() - if tp == 'name' then - if cont == 'fun' then - local func = parseTypeUnit(parent) - if func then - func.async = true - func.asyncPos = pos - return func - end - end - end - end - if content == 'fun' then - return parseTypeUnitFunction(parent) - end -end - -local function parseTypeUnitArray(parent, node) - if not checkToken('symbol', '[]', 1) then - return nil - end - nextToken() - local result = { - type = 'doc.type.array', - start = node.start, - finish = getFinish(), - node = node, - parent = parent, - } - node.parent = result - return result -end - -local function parseTypeUnitSign(parent, node) - if not checkToken('symbol', '<', 1) then - return nil - end - nextToken() - local result = { - type = 'doc.type.sign', - start = node.start, - finish = getFinish(), - node = node, - parent = parent, - signs = {}, - } - node.parent = result - while true do - local sign = parseType(result) - if not sign then - pushWarning { - type = 'LUA_DOC_MISS_SIGN', - start = getFinish(), - finish = getFinish(), - } - break - end - result.signs[#result.signs+1] = sign - if checkToken('symbol', ',', 1) then - nextToken() - else - break - end - end - nextSymbolOrError '>' - result.finish = getFinish() - return result -end - -local function parseString(parent) - local tp, content = peekToken() - if not tp or tp ~= 'string' then - return nil - end - - nextToken() - local mark = getMark() - -- compatibility - if content:sub(1, 1) == '"' - or content:sub(1, 1) == "'" then - if content:sub(1, 1) == content:sub(-1, -1) then - mark = content:sub(1, 1) - content = content:sub(2, -2) - end - end - local str = { - type = 'doc.type.string', - start = getStart(), - finish = getFinish(), - parent = parent, - [1] = content, - [2] = mark, - } - return str -end - -local function parseCode(parent) - local tp, content = peekToken() - if not tp or tp ~= 'code' then - return nil - end - nextToken() - local code = { - type = 'doc.type.code', - start = getStart(), - finish = getFinish(), - parent = parent, - [1] = content, - } - return code -end - -local function parseInteger(parent) - local tp, content = peekToken() - if not tp or tp ~= 'integer' then - return nil - end - - nextToken() - local integer = { - type = 'doc.type.integer', - start = getStart(), - finish = getFinish(), - parent = parent, - [1] = content, - } - return integer -end - -local function parseBoolean(parent) - local tp, content = peekToken() - if not tp - or tp ~= 'name' - or (content ~= 'true' and content ~= 'false') then - return nil - end - - nextToken() - local boolean = { - type = 'doc.type.boolean', - start = getStart(), - finish = getFinish(), - parent = parent, - [1] = content == 'true' and true or false, - } - return boolean -end - -local function parseParen(parent) - if not checkToken('symbol', '(', 1) then - return - end - nextToken() - local tp = parseType(parent) - nextSymbolOrError(')') - return tp -end - -function parseTypeUnit(parent) - local result = parseFunction(parent) - or parseTable(parent) - or parseString(parent) - or parseCode(parent) - or parseInteger(parent) - or parseBoolean(parent) - or parseParen(parent) - if not result then - result = parseName('doc.type.name', parent) - or parseDots('doc.type.name', parent) - if not result then - return nil - end - if result[1] == '...' then - result[1] = 'unknown' - end - end - while true do - local newResult = parseTypeUnitSign(parent, result) - if not newResult then - break - end - result = newResult - end - while true do - local newResult = parseTypeUnitArray(parent, result) - if not newResult then - break - end - result = newResult - end - return result -end - -local function parseResume(parent) - local default, additional - if checkToken('symbol', '>', 1) then - nextToken() - default = true - end - - if checkToken('symbol', '+', 1) then - nextToken() - additional = true - end - - local result = parseTypeUnit(parent) - if result then - result.default = default - result.additional = additional - end - - return result -end - -function parseType(parent) - local result = { - type = 'doc.type', - parent = parent, - types = {}, - } - while true do - local typeUnit = parseTypeUnit(result) - if not typeUnit then - break - end - - result.types[#result.types+1] = typeUnit - if not result.start then - result.start = typeUnit.start - end - - if not checkToken('symbol', '|', 1) then - break - end - nextToken() - end - if not result.start then - result.start = getFinish() - end - if checkToken('symbol', '?', 1) then - nextToken() - result.optional = true - end - result.finish = getFinish() - result.firstFinish = result.finish - - local row = guide.rowColOf(result.finish) - - local function pushResume() - local comments - for i = 0, 100 do - local nextComm = NextComment(i,'peek') - if not nextComm then - return false - end - local nextCommRow = guide.rowColOf(nextComm.start) - local currentRow = row + i + 1 - if currentRow < nextCommRow then - return false - end - if nextComm.text:match '^%-%s*%@' then - return false - else - local resumeHead = nextComm.text:match '^%-%s*%|' - if resumeHead then - NextComment(i) - row = row + i + 1 - local finishPos = nextComm.text:find('#', #resumeHead + 1) or #nextComm.text - parseTokens(nextComm.text:sub(#resumeHead + 1, finishPos), nextComm.start + #resumeHead + 1) - local resume = parseResume(result) - if resume then - if comments then - resume.comment = table.concat(comments, '\n') - else - resume.comment = nextComm.text:match('%s*#?%s*(.+)', resume.finish - nextComm.start) - end - result.types[#result.types+1] = resume - result.finish = resume.finish - end - comments = nil - return true - else - if not comments then - comments = {} - end - comments[#comments+1] = nextComm.text:sub(2) - end - end - end - return false - end - - local checkResume = true - local nsymbol, ncontent = peekToken() - if nsymbol == 'symbol' then - if ncontent == ',' - or ncontent == ':' - or ncontent == '|' - or ncontent == ')' - or ncontent == '}' then - checkResume = false - end - end - - if checkResume then - while pushResume() do end - end - - if #result.types == 0 then - pushWarning { - type = 'LUADOC_MISS_TYPE_NAME', - start = getFinish(), - finish = getFinish(), - } - return nil - end - return result -end - -local docSwitch = util.switch() - : case 'class' - : call(function () - local result = { - type = 'doc.class', - fields = {}, - operators = {}, - calls = {}, - } - result.class = parseName('doc.class.name', result) - if not result.class then - pushWarning { - type = 'LUADOC_MISS_CLASS_NAME', - start = getFinish(), - finish = getFinish(), - } - return nil - end - result.start = getStart() - result.finish = getFinish() - result.signs = parseSigns(result) - if not checkToken('symbol', ':', 1) then - return result - end - nextToken() - - result.extends = {} - - while true do - local extend = parseName('doc.extends.name', result) - or parseTable(result) - if not extend then - pushWarning { - type = 'LUADOC_MISS_CLASS_EXTENDS_NAME', - start = getFinish(), - finish = getFinish(), - } - return result - end - result.extends[#result.extends+1] = extend - result.finish = getFinish() - if not checkToken('symbol', ',', 1) then - break - end - nextToken() - end - return result - end) - : case 'type' - : call(function () - local first = parseType() - if not first then - return nil - end - local rests - while checkToken('symbol', ',', 1) do - nextToken() - local rest = parseType() - if not rests then - rests = {} - end - rests[#rests+1] = rest - end - return first, rests - end) - : case 'alias' - : call(function () - local result = { - type = 'doc.alias', - } - result.alias = parseName('doc.alias.name', result) - if not result.alias then - pushWarning { - type = 'LUADOC_MISS_ALIAS_NAME', - start = getFinish(), - finish = getFinish(), - } - return nil - end - result.start = getStart() - result.signs = parseSigns(result) - result.extends = parseType(result) - if not result.extends then - pushWarning { - type = 'LUADOC_MISS_ALIAS_EXTENDS', - start = getFinish(), - finish = getFinish(), - } - return nil - end - result.finish = getFinish() - return result - end) - : case 'param' - : call(function () - local result = { - type = 'doc.param', - } - result.param = parseName('doc.param.name', result) - or parseDots('doc.param.name', result) - if not result.param then - pushWarning { - type = 'LUADOC_MISS_PARAM_NAME', - start = getFinish(), - finish = getFinish(), - } - return nil - end - if checkToken('symbol', '?', 1) then - nextToken() - result.optional = true - end - result.start = result.param.start - result.finish = getFinish() - result.extends = parseType(result) - if not result.extends then - pushWarning { - type = 'LUADOC_MISS_PARAM_EXTENDS', - start = getFinish(), - finish = getFinish(), - } - return result - end - result.finish = getFinish() - result.firstFinish = result.extends.firstFinish - return result - end) - : case 'return' - : call(function () - local result = { - type = 'doc.return', - returns = {}, - } - while true do - local dots = parseDots('doc.return.name') - if dots then - Ci = Ci - 1 - end - local docType = parseType(result) - if not docType then - break - end - if not result.start then - result.start = docType.start - end - if checkToken('symbol', '?', 1) then - nextToken() - docType.optional = true - end - if dots then - docType.name = dots - dots.parent = docType - else - docType.name = parseName('doc.return.name', docType) - or parseDots('doc.return.name', docType) - end - result.returns[#result.returns+1] = docType - if not checkToken('symbol', ',', 1) then - break - end - nextToken() - end - if #result.returns == 0 then - return nil - end - result.finish = getFinish() - return result - end) - : case 'field' - : call(function () - local result = { - type = 'doc.field', - } - try(function () - local tp, value = nextToken() - if tp == 'name' then - if value == 'public' - or value == 'protected' - or value == 'private' - or value == 'package' then - local tp2 = peekToken(1) - local tp3 = peekToken(2) - if tp2 == 'name' and not tp3 then - return false - end - result.visible = value - result.start = getStart() - return true - end - end - return false - end) - result.field = parseName('doc.field.name', result) - or parseIndexField(result) - if not result.field then - pushWarning { - type = 'LUADOC_MISS_FIELD_NAME', - start = getFinish(), - finish = getFinish(), - } - return nil - end - if not result.start then - result.start = result.field.start - end - if checkToken('symbol', '?', 1) then - nextToken() - result.optional = true - end - result.extends = parseType(result) - if not result.extends then - pushWarning { - type = 'LUADOC_MISS_FIELD_EXTENDS', - start = getFinish(), - finish = getFinish(), - } - return nil - end - result.finish = getFinish() - return result - end) - : case 'generic' - : call(function () - local result = { - type = 'doc.generic', - generics = {}, - } - while true do - local object = { - type = 'doc.generic.object', - parent = result, - } - object.generic = parseName('doc.generic.name', object) - if not object.generic then - pushWarning { - type = 'LUADOC_MISS_GENERIC_NAME', - start = getFinish(), - finish = getFinish(), - } - return nil - end - object.start = object.generic.start - if not result.start then - result.start = object.start - end - if checkToken('symbol', ':', 1) then - nextToken() - object.extends = parseType(object) - end - object.finish = getFinish() - result.generics[#result.generics+1] = object - if not checkToken('symbol', ',', 1) then - break - end - nextToken() - end - result.finish = getFinish() - return result - end) - : case 'vararg' - : call(function () - local result = { - type = 'doc.vararg', - } - result.vararg = parseType(result) - if not result.vararg then - pushWarning { - type = 'LUADOC_MISS_VARARG_TYPE', - start = getFinish(), - finish = getFinish(), - } - return - end - result.start = result.vararg.start - result.finish = result.vararg.finish - return result - end) - : case 'overload' - : call(function () - local tp, name = peekToken() - if tp ~= 'name' - or (name ~= 'fun' and name ~= 'async') then - pushWarning { - type = 'LUADOC_MISS_FUN_AFTER_OVERLOAD', - start = getFinish(), - finish = getFinish(), - } - return nil - end - local result = { - type = 'doc.overload', - } - result.overload = parseFunction(result) - if not result.overload then - return nil - end - result.overload.parent = result - result.start = result.overload.start - result.finish = result.overload.finish - return result - end) - : case 'deprecated' - : call(function () - return { - type = 'doc.deprecated', - start = getFinish(), - finish = getFinish(), - } - end) - : case 'meta' - : call(function () - return { - type = 'doc.meta', - start = getFinish(), - finish = getFinish(), - } - end) - : case 'version' - : call(function () - local result = { - type = 'doc.version', - versions = {}, - } - while true do - local tp, text = nextToken() - if not tp then - pushWarning { - type = 'LUADOC_MISS_VERSION', - start = getFinish(), - finish = getFinish(), - } - break - end - if not result.start then - result.start = getStart() - end - local version = { - type = 'doc.version.unit', - parent = result, - start = getStart(), - } - if tp == 'symbol' then - if text == '>' then - version.ge = true - elseif text == '<' then - version.le = true - end - tp, text = nextToken() - end - if tp ~= 'name' then - pushWarning { - type = 'LUADOC_MISS_VERSION', - start = getStart(), - finish = getFinish(), - } - break - end - version.version = tonumber(text) or text - version.finish = getFinish() - result.versions[#result.versions+1] = version - if not checkToken('symbol', ',', 1) then - break - end - nextToken() - end - if #result.versions == 0 then - return nil - end - result.finish = getFinish() - return result - end) - : case 'see' - : call(function () - local result = { - type = 'doc.see', - } - result.name = parseName('doc.see.name', result) - if not result.name then - pushWarning { - type = 'LUADOC_MISS_SEE_NAME', - start = getFinish(), - finish = getFinish(), - } - return nil - end - result.start = result.name.start - result.finish = result.name.finish - return result - end) - : case 'diagnostic' - : call(function () - local result = { - type = 'doc.diagnostic', - } - local nextTP, mode = nextToken() - if nextTP ~= 'name' then - pushWarning { - type = 'LUADOC_MISS_DIAG_MODE', - start = getFinish(), - finish = getFinish(), - } - return nil - end - result.mode = mode - result.start = getStart() - result.finish = getFinish() - if mode ~= 'disable-next-line' - and mode ~= 'disable-line' - and mode ~= 'disable' - and mode ~= 'enable' then - pushWarning { - type = 'LUADOC_ERROR_DIAG_MODE', - start = result.start, - finish = result.finish, - } - end - - if checkToken('symbol', ':', 1) then - nextToken() - result.names = {} - while true do - local name = parseName('doc.diagnostic.name', result) - if not name then - pushWarning { - type = 'LUADOC_MISS_DIAG_NAME', - start = getFinish(), - finish = getFinish(), - } - return result - end - result.names[#result.names+1] = name - if not checkToken('symbol', ',', 1) then - break - end - nextToken() - end - end - - result.finish = getFinish() - - return result - end) - : case 'module' - : call(function () - local result = { - type = 'doc.module', - start = getFinish(), - finish = getFinish(), - } - local tp, content = peekToken() - if tp == 'string' then - result.module = content - nextToken() - result.start = getStart() - result.finish = getFinish() - result.smark = getMark() - else - pushWarning { - type = 'LUADOC_MISS_MODULE_NAME', - start = getFinish(), - finish = getFinish(), - } - end - return result - end) - : case 'async' - : call(function () - return { - type = 'doc.async', - start = getFinish(), - finish = getFinish(), - } - end) - : case 'nodiscard' - : call(function () - return { - type = 'doc.nodiscard', - start = getFinish(), - finish = getFinish(), - } - end) - : case 'as' - : call(function () - local result = { - type = 'doc.as', - start = getFinish(), - finish = getFinish(), - } - result.as = parseType(result) - result.finish = getFinish() - return result - end) - : case 'cast' - : call(function () - local result = { - type = 'doc.cast', - start = getFinish(), - finish = getFinish(), - casts = {}, - } - - local loc = parseName('doc.cast.name', result) - if not loc then - pushWarning { - type = 'LUADOC_MISS_LOCAL_NAME', - start = getFinish(), - finish = getFinish(), - } - return result - end - - result.name = loc - result.finish = loc.finish - - while true do - local block = { - type = 'doc.cast.block', - parent = result, - start = getFinish(), - finish = getFinish(), - } - if checkToken('symbol', '+', 1) then - block.mode = '+' - nextToken() - block.start = getStart() - block.finish = getFinish() - elseif checkToken('symbol', '-', 1) then - block.mode = '-' - nextToken() - block.start = getStart() - block.finish = getFinish() - end - - if checkToken('symbol', '?', 1) then - block.optional = true - nextToken() - block.finish = getFinish() - else - block.extends = parseType(block) - if block.extends then - block.start = block.start or block.extends.start - block.finish = block.extends.finish - end - end - - if block.optional or block.extends then - result.casts[#result.casts+1] = block - end - result.finish = block.finish - - if checkToken('symbol', ',', 1) then - nextToken() - else - break - end - end - - return result - end) - : case 'operator' - : call(function () - local result = { - type = 'doc.operator', - start = getFinish(), - finish = getFinish(), - } - - local op = parseName('doc.operator.name', result) - if not op then - pushWarning { - type = 'LUADOC_MISS_OPERATOR_NAME', - start = getFinish(), - finish = getFinish(), - } - return nil - end - result.op = op - result.finish = op.finish - - if checkToken('symbol', '(', 1) then - nextToken() - if checkToken('symbol', ')', 1) then - nextToken() - else - local exp = parseType(result) - if exp then - result.exp = exp - result.finish = exp.finish - end - nextSymbolOrError ')' - end - end - - nextSymbolOrError ':' - - local ret = parseType(result) - if ret then - result.extends = ret - result.finish = ret.finish - end - - return result - end) - : case 'source' - : call(function (doc) - local fullSource = doc:sub(#'source' + 1) - if not fullSource or fullSource == '' then - return - end - fullSource = util.trim(fullSource) - if fullSource == '' then - return - end - local source, line, char = fullSource:match('^(.-):?(%d*):?(%d*)$') - source = source or fullSource - line = tonumber(line) or 1 - char = tonumber(char) or 0 - local result = { - type = 'doc.source', - start = getStart(), - finish = getFinish(), - path = source, - line = line, - char = char, - } - return result - end) - : case 'enum' - : call(function () - local name = parseName('doc.enum.name') - if not name then - return nil - end - local result = { - type = 'doc.enum', - start = name.start, - finish = name.finish, - enum = name, - } - name.parent = result - return result - end) - : case 'private' - : call(function () - return { - type = 'doc.private', - start = getFinish(), - finish = getFinish(), - } - end) - : case 'protected' - : call(function () - return { - type = 'doc.protected', - start = getFinish(), - finish = getFinish(), - } - end) - : case 'public' - : call(function () - return { - type = 'doc.public', - start = getFinish(), - finish = getFinish(), - } - end) - : case 'package' - : call(function () - return { - type = 'doc.package', - start = getFinish(), - finish = getFinish(), - } - end) - -local function convertTokens(doc) - local tp, text = nextToken() - if not tp then - return - end - if tp ~= 'name' then - pushWarning { - type = 'LUADOC_MISS_CATE_NAME', - start = getStart(), - finish = getFinish(), - } - return nil - end - return docSwitch(text, doc) -end - -local function trimTailComment(text) - local comment = text - if text:sub(1, 1) == '@' then - comment = text:sub(2) - end - if text:sub(1, 1) == '#' then - comment = text:sub(2) - end - if text:sub(1, 2) == '--' then - comment = text:sub(3) - end - if comment:find '^%s*[\'"[]' then - local state = compile(comment:gsub('^%s+', ''), 'String') - if state and state.ast then - comment = state.ast[1] - end - end - return comment -end - -local function buildLuaDoc(comment) - local text = comment.text - local startPos = (comment.type == 'comment.short' and text:match '^%-%s*@()') - or (comment.type == 'comment.long' and text:match '^@()') - if not startPos then - return { - type = 'doc.comment', - start = comment.start, - finish = comment.finish, - range = comment.finish, - comment = comment, - } - end - local startOffset = comment.start - if comment.type == 'comment.long' then - startOffset = startOffset + #comment.mark - 2 - end - - local doc = text:sub(startPos) - - parseTokens(doc, startOffset + startPos) - local result, rests = convertTokens(doc) - if result then - result.range = comment.finish - local finish = result.firstFinish or result.finish - if rests then - for _, rest in ipairs(rests) do - rest.range = comment.finish - finish = rest.firstFinish or result.finish - end - end - local cstart = text:find('%S', finish - comment.start) - if cstart and cstart < comment.finish then - result.comment = { - type = 'doc.tailcomment', - start = cstart + comment.start, - finish = comment.finish, - parent = result, - text = trimTailComment(text:sub(cstart)), - } - if rests then - for _, rest in ipairs(rests) do - rest.comment = result.comment - end - end - end - end - - if result then - return result, rests - end - - return { - type = 'doc.comment', - start = comment.start, - finish = comment.finish, - range = comment.finish, - comment = comment, - } -end - -local function isTailComment(text, doc) - if not doc then - return false - end - local left = doc.originalComment.start - local row, col = guide.rowColOf(left) - local lineStart = Lines[row] or 0 - local hasCodeBefore = text:sub(lineStart, lineStart + col):find '[%w_]' - return hasCodeBefore -end - -local function isContinuedDoc(lastDoc, nextDoc) - if not nextDoc then - return false - end - if nextDoc.type == 'doc.diagnostic' then - return true - end - if lastDoc.type == 'doc.type' - or lastDoc.type == 'doc.module' - or lastDoc.type == 'doc.enum' then - if nextDoc.type ~= 'doc.comment' then - return false - end - end - if lastDoc.type == 'doc.class' - or lastDoc.type == 'doc.field' - or lastDoc.type == 'doc.operator' then - if nextDoc.type ~= 'doc.field' - and nextDoc.type ~= 'doc.operator' - and nextDoc.type ~= 'doc.comment' - and nextDoc.type ~= 'doc.overload' - and nextDoc.type ~= 'doc.source' then - return false - end - end - if nextDoc.type == 'doc.cast' then - return false - end - return true -end - -local function isNextLine(lastDoc, nextDoc) - if not nextDoc then - return false - end - local lastRow = guide.rowColOf(lastDoc.finish) - local newRow = guide.rowColOf(nextDoc.start) - return newRow - lastRow == 1 -end - -local function bindGeneric(binded) - local generics = {} - for _, doc in ipairs(binded) do - if doc.type == 'doc.generic' then - for _, obj in ipairs(doc.generics) do - local name = obj.generic[1] - generics[name] = obj - end - end - if doc.type == 'doc.class' - or doc.type == 'doc.alias' then - if doc.signs then - for _, sign in ipairs(doc.signs) do - local name = sign[1] - generics[name] = sign - end - end - end - if doc.type == 'doc.param' - or doc.type == 'doc.vararg' - or doc.type == 'doc.return' - or doc.type == 'doc.type' - or doc.type == 'doc.class' - or doc.type == 'doc.alias' then - guide.eachSourceType(doc, 'doc.type.name', function (src) - local name = src[1] - if generics[name] then - src.type = 'doc.generic.name' - src.generic = generics[name] - end - end) - guide.eachSourceType(doc, 'doc.type.code', function (src) - local name = src[1] - if generics[name] then - src.type = 'doc.generic.name' - src.literal = true - end - end) - end - end -end - -local function bindDocWithSource(doc, source) - if not source.bindDocs then - source.bindDocs = {} - end - source.bindDocs[#source.bindDocs+1] = doc - doc.bindSource = source -end - -local function bindDoc(source, binded) - local isParam = source.type == 'self' - or source.type == 'local' - and (source.parent.type == 'funcargs' - or ( source.parent.type == 'in' - and source.finish <= source.parent.keys.finish - ) - ) - local ok = false - for _, doc in ipairs(binded) do - if doc.bindSource then - goto CONTINUE - end - if doc.type == 'doc.class' - or doc.type == 'doc.deprecated' - or doc.type == 'doc.version' - or doc.type == 'doc.module' - or doc.type == 'doc.source' - or doc.type == 'doc.private' - or doc.type == 'doc.protected' - or doc.type == 'doc.public' - or doc.type == 'doc.package' - or doc.type == 'doc.see' then - if source.type == 'function' - or isParam then - goto CONTINUE - end - bindDocWithSource(doc, source) - ok = true - elseif doc.type == 'doc.type' then - if source.type == 'function' - or isParam - or source._bindedDocType then - goto CONTINUE - end - source._bindedDocType = true - bindDocWithSource(doc, source) - ok = true - elseif doc.type == 'doc.overload' then - if not source.bindDocs then - source.bindDocs = {} - end - source.bindDocs[#source.bindDocs+1] = doc - if source.type == 'function' then - bindDocWithSource(doc, source) - end - ok = true - elseif doc.type == 'doc.param' then - if isParam - and doc.param[1] == source[1] then - bindDocWithSource(doc, source) - ok = true - elseif source.type == '...' - and doc.param[1] == '...' then - bindDocWithSource(doc, source) - ok = true - elseif source.type == 'self' - and doc.param[1] == 'self' then - bindDocWithSource(doc, source) - ok = true - elseif source.type == 'function' then - if not source.bindDocs then - source.bindDocs = {} - end - source.bindDocs[#source.bindDocs + 1] = doc - if source.args then - for _, arg in ipairs(source.args) do - if arg[1] == doc.param[1] then - bindDocWithSource(doc, arg) - break - end - end - end - end - elseif doc.type == 'doc.vararg' then - if source.type == '...' then - bindDocWithSource(doc, source) - ok = true - end - elseif doc.type == 'doc.return' - or doc.type == 'doc.generic' - or doc.type == 'doc.async' - or doc.type == 'doc.nodiscard' then - if source.type == 'function' then - bindDocWithSource(doc, source) - ok = true - end - elseif doc.type == 'doc.enum' then - if source.type == 'table' then - bindDocWithSource(doc, source) - ok = true - end - if source.value and source.value.type == 'table' then - bindDocWithSource(doc, source.value) - goto CONTINUE - end - elseif doc.type == 'doc.comment' then - bindDocWithSource(doc, source) - ok = true - end - ::CONTINUE:: - end - return ok -end - -local function bindDocsBetween(sources, binded, start, finish) - -- 用二分法找到第一个 - local max = #sources - local index - local left = 1 - local right = max - for _ = 1, 1000 do - index = left + (right - left) // 2 - if index <= left then - index = left - break - elseif index >= right then - index = right - break - end - local src = sources[index] - if src.start < start then - left = index + 1 - else - right = index - end - end - - local ok = false - -- 从前往后进行绑定 - for i = index, max do - local src = sources[i] - if src and src.start >= start then - if src.start >= finish then - break - end - if src.start >= start then - if src.type == 'local' - or src.type == 'self' - or src.type == 'setlocal' - or src.type == 'setglobal' - or src.type == 'tablefield' - or src.type == 'tableindex' - or src.type == 'setfield' - or src.type == 'setindex' - or src.type == 'setmethod' - or src.type == 'function' - or src.type == 'table' - or src.type == '...' then - if bindDoc(src, binded) then - ok = true - end - end - end - end - end - - return ok -end - -local function bindReturnIndex(binded) - local returnIndex = 0 - for _, doc in ipairs(binded) do - if doc.type == 'doc.return' then - for _, rtn in ipairs(doc.returns) do - returnIndex = returnIndex + 1 - rtn.returnIndex = returnIndex - end - end - end -end - -local function bindCommentsToDoc(doc, comments) - doc.bindComments = comments - for _, comment in ipairs(comments) do - comment.bindSource = doc - end -end - -local function bindCommentsAndFields(binded) - local class - local comments = {} - local source - for _, doc in ipairs(binded) do - if doc.type == 'doc.class' then - -- 多个class连续写在一起,只有最后一个class可以绑定source - if class then - class.bindSource = nil - end - if source then - doc.source = source - source.bindSource = doc - end - class = doc - bindCommentsToDoc(doc, comments) - comments = {} - elseif doc.type == 'doc.field' then - if class then - class.fields[#class.fields+1] = doc - doc.class = class - end - if source then - doc.source = source - source.bindSource = doc - end - bindCommentsToDoc(doc, comments) - comments = {} - elseif doc.type == 'doc.operator' then - if class then - class.operators[#class.operators+1] = doc - doc.class = class - end - bindCommentsToDoc(doc, comments) - comments = {} - elseif doc.type == 'doc.overload' then - if class then - class.calls[#class.calls+1] = doc - doc.class = class - end - elseif doc.type == 'doc.alias' - or doc.type == 'doc.enum' then - bindCommentsToDoc(doc, comments) - comments = {} - elseif doc.type == 'doc.comment' then - comments[#comments+1] = doc - elseif doc.type == 'doc.source' then - source = doc - goto CONTINUE - end - source = nil - ::CONTINUE:: - end -end - -local function bindDocWithSources(sources, binded) - if not binded then - return - end - local lastDoc = binded[#binded] - if not lastDoc then - return - end - for _, doc in ipairs(binded) do - doc.bindGroup = binded - end - bindGeneric(binded) - bindCommentsAndFields(binded) - bindReturnIndex(binded) - local row = guide.rowColOf(lastDoc.finish) - local suc = bindDocsBetween(sources, binded, guide.positionOf(row, 0), lastDoc.start) - if not suc then - bindDocsBetween(sources, binded, guide.positionOf(row + 1, 0), guide.positionOf(row + 2, 0)) - end -end - -local bindDocAccept = { - 'local' , 'setlocal' , 'setglobal', - 'setfield' , 'setmethod' , 'setindex' , - 'tablefield', 'tableindex', 'self' , - 'function' , 'table' , '...' , -} - -local function bindDocs(state) - local text = state.lua - local sources = {} - guide.eachSourceTypes(state.ast, bindDocAccept, function (src) - sources[#sources+1] = src - end) - table.sort(sources, function (a, b) - return a.start < b.start - end) - local binded - for i, doc in ipairs(state.ast.docs) do - if not binded then - binded = {} - state.ast.docs.groups[#state.ast.docs.groups+1] = binded - end - binded[#binded+1] = doc - if isTailComment(text, doc) then - bindDocWithSources(sources, binded) - binded = nil - else - local nextDoc = state.ast.docs[i+1] - if not isNextLine(doc, nextDoc) then - bindDocWithSources(sources, binded) - binded = nil - end - if not isContinuedDoc(doc, nextDoc) - and not isTailComment(text, nextDoc) then - bindDocWithSources(sources, binded) - binded = nil - end - end - end -end - -local function findTouch(state, doc) - local text = state.lua - local pos = guide.positionToOffset(state, doc.originalComment.start) - for i = pos - 2, 1, -1 do - local c = text:sub(i, i) - if c == '\r' - or c == '\n' then - break - elseif c ~= ' ' - and c ~= '\t' then - doc.touch = guide.offsetToPosition(state, i) - break - end - end -end - -return function (state) - local ast = state.ast - local comments = state.comms - table.sort(comments, function (a, b) - return a.start < b.start - end) - ast.docs = { - type = 'doc', - parent = ast, - groups = {}, - } - - pushWarning = 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 'Warning' - errs[#errs+1] = err - return err - end - Lines = state.lines - - local ci = 1 - NextComment = function (offset, peek) - local comment = comments[ci + (offset or 0)] - if not peek then - ci = ci + 1 + (offset or 0) - end - return comment - end - - local function insertDoc(doc, comment) - ast.docs[#ast.docs+1] = doc - doc.parent = ast.docs - if ast.start > doc.start then - ast.start = doc.start - end - if ast.finish < doc.finish then - ast.finish = doc.finish - end - doc.originalComment = comment - if comment.type == 'comment.long' then - findTouch(state, doc) - end - end - - while true do - local comment = NextComment() - if not comment then - break - end - local doc, rests = buildLuaDoc(comment) - if doc then - insertDoc(doc, comment) - if rests then - for _, rest in ipairs(rests) do - insertDoc(rest, comment) - end - end - end - end - - ast.docs.start = ast.start - ast.docs.finish = ast.finish - - if #ast.docs == 0 then - return - end - - bindDocs(state) -end diff --git a/src/parser.old/relabel.lua b/src/parser.old/relabel.lua deleted file mode 100644 index ac90240..0000000 --- a/src/parser.old/relabel.lua +++ /dev/null @@ -1,361 +0,0 @@ --- $Id: re.lua,v 1.44 2013/03/26 20:11:40 roberto Exp $ - --- imported functions and modules -local tonumber, type, print, error = tonumber, type, print, error -local pcall = pcall -local setmetatable = setmetatable -local tinsert, concat = table.insert, table.concat -local rep = string.rep -local m = require"lpeglabel" - --- 'm' will be used to parse expressions, and 'mm' will be used to --- create expressions; that is, 're' runs on 'm', creating patterns --- on 'mm' -local mm = m - --- pattern's metatable -local mt = getmetatable(mm.P(0)) - - - --- No more global accesses after this point -_ENV = nil - - -local any = m.P(1) -local dummy = mm.P(false) - - -local errinfo = { - NoPatt = "no pattern found", - ExtraChars = "unexpected characters after the pattern", - - ExpPatt1 = "expected a pattern after '/'", - - ExpPatt2 = "expected a pattern after '&'", - ExpPatt3 = "expected a pattern after '!'", - - ExpPatt4 = "expected a pattern after '('", - ExpPatt5 = "expected a pattern after ':'", - ExpPatt6 = "expected a pattern after '{~'", - ExpPatt7 = "expected a pattern after '{|'", - - ExpPatt8 = "expected a pattern after '<-'", - - ExpPattOrClose = "expected a pattern or closing '}' after '{'", - - ExpNumName = "expected a number, '+', '-' or a name (no space) after '^'", - ExpCap = "expected a string, number, '{}' or name after '->'", - - ExpName1 = "expected the name of a rule after '=>'", - ExpName2 = "expected the name of a rule after '=' (no space)", - ExpName3 = "expected the name of a rule after '<' (no space)", - - ExpLab1 = "expected a label after '{'", - - ExpNameOrLab = "expected a name or label after '%' (no space)", - - ExpItem = "expected at least one item after '[' or '^'", - - MisClose1 = "missing closing ')'", - MisClose2 = "missing closing ':}'", - MisClose3 = "missing closing '~}'", - MisClose4 = "missing closing '|}'", - MisClose5 = "missing closing '}'", -- for the captures - - MisClose6 = "missing closing '>'", - MisClose7 = "missing closing '}'", -- for the labels - - MisClose8 = "missing closing ']'", - - MisTerm1 = "missing terminating single quote", - MisTerm2 = "missing terminating double quote", -} - -local function expect (pattern, label) - return pattern + m.T(label) -end - - --- Pre-defined names -local Predef = { nl = m.P"\n" } - - -local mem -local fmem -local gmem - - -local function updatelocale () - mm.locale(Predef) - Predef.a = Predef.alpha - Predef.c = Predef.cntrl - Predef.d = Predef.digit - Predef.g = Predef.graph - Predef.l = Predef.lower - Predef.p = Predef.punct - Predef.s = Predef.space - Predef.u = Predef.upper - Predef.w = Predef.alnum - Predef.x = Predef.xdigit - Predef.A = any - Predef.a - Predef.C = any - Predef.c - Predef.D = any - Predef.d - Predef.G = any - Predef.g - Predef.L = any - Predef.l - Predef.P = any - Predef.p - Predef.S = any - Predef.s - Predef.U = any - Predef.u - Predef.W = any - Predef.w - Predef.X = any - Predef.x - mem = {} -- restart memoization - fmem = {} - gmem = {} - local mt = {__mode = "v"} - setmetatable(mem, mt) - setmetatable(fmem, mt) - setmetatable(gmem, mt) -end - - -updatelocale() - - - -local I = m.P(function (s,i) print(i, s:sub(1, i-1)); return i end) - - -local function getdef (id, defs) - local c = defs and defs[id] - if not c then - error("undefined name: " .. id) - end - return c -end - - -local function mult (p, n) - local np = mm.P(true) - while n >= 1 do - if n%2 >= 1 then np = np * p end - p = p * p - n = n/2 - end - return np -end - -local function equalcap (s, i, c) - if type(c) ~= "string" then return nil end - local e = #c + i - if s:sub(i, e - 1) == c then return e else return nil end -end - - -local S = (Predef.space + "--" * (any - Predef.nl)^0)^0 - -local name = m.C(m.R("AZ", "az", "__") * m.R("AZ", "az", "__", "09")^0) - -local arrow = S * "<-" - --- a defined name only have meaning in a given environment -local Def = name * m.Carg(1) - -local num = m.C(m.R"09"^1) * S / tonumber - -local String = "'" * m.C((any - "'" - m.P"\n")^0) * expect("'", "MisTerm1") - + '"' * m.C((any - '"' - m.P"\n")^0) * expect('"', "MisTerm2") - - -local defined = "%" * Def / function (c,Defs) - local cat = Defs and Defs[c] or Predef[c] - if not cat then - error("name '" .. c .. "' undefined") - end - return cat -end - -local Range = m.Cs(any * (m.P"-"/"") * (any - "]")) / mm.R - -local item = defined + Range + m.C(any - m.P"\n") - -local Class = - "[" - * (m.C(m.P"^"^-1)) -- optional complement symbol - * m.Cf(expect(item, "ExpItem") * (item - "]")^0, mt.__add) - / function (c, p) return c == "^" and any - p or p end - * expect("]", "MisClose8") - -local function adddef (t, k, exp) - if t[k] then - -- TODO 改了一下这里的代码,重复定义不会抛错 - --error("'"..k.."' already defined as a rule") - else - t[k] = exp - end - return t -end - -local function firstdef (n, r) return adddef({n}, n, r) end - - -local function NT (n, b) - if not b then - error("rule '"..n.."' used outside a grammar") - else return mm.V(n) - end -end - - -local exp = m.P{ "Exp", - Exp = S * ( m.V"Grammar" - + m.Cf(m.V"Seq" * (S * "/" * expect(S * m.V"Seq", "ExpPatt1"))^0, mt.__add) ); - Seq = m.Cf(m.Cc(m.P"") * m.V"Prefix" * (S * m.V"Prefix")^0, mt.__mul); - Prefix = "&" * expect(S * m.V"Prefix", "ExpPatt2") / mt.__len - + "!" * expect(S * m.V"Prefix", "ExpPatt3") / mt.__unm - + m.V"Suffix"; - Suffix = m.Cf(m.V"Primary" * - ( S * ( m.P"+" * m.Cc(1, mt.__pow) - + m.P"*" * m.Cc(0, mt.__pow) - + m.P"?" * m.Cc(-1, mt.__pow) - + "^" * expect( m.Cg(num * m.Cc(mult)) - + m.Cg(m.C(m.S"+-" * m.R"09"^1) * m.Cc(mt.__pow) - + name * m.Cc"lab" - ), - "ExpNumName") - + "->" * expect(S * ( m.Cg((String + num) * m.Cc(mt.__div)) - + m.P"{}" * m.Cc(nil, m.Ct) - + m.Cg(Def / getdef * m.Cc(mt.__div)) - ), - "ExpCap") - + "=>" * expect(S * m.Cg(Def / getdef * m.Cc(m.Cmt)), - "ExpName1") - ) - )^0, function (a,b,f) if f == "lab" then return a + mm.T(b) else return f(a,b) end end ); - Primary = "(" * expect(m.V"Exp", "ExpPatt4") * expect(S * ")", "MisClose1") - + String / mm.P - + Class - + defined - + "%" * expect(m.P"{", "ExpNameOrLab") - * expect(S * m.V"Label", "ExpLab1") - * expect(S * "}", "MisClose7") / mm.T - + "{:" * (name * ":" + m.Cc(nil)) * expect(m.V"Exp", "ExpPatt5") - * expect(S * ":}", "MisClose2") - / function (n, p) return mm.Cg(p, n) end - + "=" * expect(name, "ExpName2") - / function (n) return mm.Cmt(mm.Cb(n), equalcap) end - + m.P"{}" / mm.Cp - + "{~" * expect(m.V"Exp", "ExpPatt6") - * expect(S * "~}", "MisClose3") / mm.Cs - + "{|" * expect(m.V"Exp", "ExpPatt7") - * expect(S * "|}", "MisClose4") / mm.Ct - + "{" * expect(m.V"Exp", "ExpPattOrClose") - * expect(S * "}", "MisClose5") / mm.C - + m.P"." * m.Cc(any) - + (name * -arrow + "<" * expect(name, "ExpName3") - * expect(">", "MisClose6")) * m.Cb("G") / NT; - Label = num + name; - Definition = name * arrow * expect(m.V"Exp", "ExpPatt8"); - Grammar = m.Cg(m.Cc(true), "G") - * m.Cf(m.V"Definition" / firstdef * (S * m.Cg(m.V"Definition"))^0, - adddef) / mm.P; -} - -local pattern = S * m.Cg(m.Cc(false), "G") * expect(exp, "NoPatt") / mm.P - * S * expect(-any, "ExtraChars") - -local function lineno (s, i) - if i == 1 then return 1, 1 end - local adjustment = 0 - -- report the current line if at end of line, not the next - if s:sub(i,i) == '\n' then - i = i-1 - adjustment = 1 - end - local rest, num = s:sub(1,i):gsub("[^\n]*\n", "") - local r = #rest - return 1 + num, (r ~= 0 and r or 1) + adjustment -end - -local function calcline (s, i) - if i == 1 then return 1, 1 end - local rest, line = s:sub(1,i):gsub("[^\n]*\n", "") - local col = #rest - return 1 + line, col ~= 0 and col or 1 -end - - -local function splitlines(str) - local t = {} - local function helper(line) tinsert(t, line) return "" end - helper((str:gsub("(.-)\r?\n", helper))) - return t -end - -local function compile (p, defs) - if mm.type(p) == "pattern" then return p end -- already compiled - p = p .. " " -- for better reporting of column numbers in errors when at EOF - local ok, cp, label, poserr = pcall(function() return pattern:match(p, 1, defs) end) - if not ok and cp then - if type(cp) == "string" then - cp = cp:gsub("^[^:]+:[^:]+: ", "") - end - error(cp, 3) - end - if not cp then - local lines = splitlines(p) - local line, col = lineno(p, poserr) - local err = {} - tinsert(err, "L" .. line .. ":C" .. col .. ": " .. errinfo[label]) - tinsert(err, lines[line]) - tinsert(err, rep(" ", col-1) .. "^") - error("syntax error(s) in pattern\n" .. concat(err, "\n"), 3) - end - return cp -end - -local function match (s, p, i) - local cp = mem[p] - if not cp then - cp = compile(p) - mem[p] = cp - end - return cp:match(s, i or 1) -end - -local function find (s, p, i) - local cp = fmem[p] - if not cp then - cp = compile(p) / 0 - cp = mm.P{ mm.Cp() * cp * mm.Cp() + 1 * mm.V(1) } - fmem[p] = cp - end - local i, e = cp:match(s, i or 1) - if i then return i, e - 1 - else return i - end -end - -local function gsub (s, p, rep) - local g = gmem[p] or {} -- ensure gmem[p] is not collected while here - gmem[p] = g - local cp = g[rep] - if not cp then - cp = compile(p) - cp = mm.Cs((cp / rep + 1)^0) - g[rep] = cp - end - return cp:match(s) -end - - --- exported names -local re = { - compile = compile, - match = match, - find = find, - gsub = gsub, - updatelocale = updatelocale, - calcline = calcline -} - -return re diff --git a/src/parser.old/tokens.lua b/src/parser.old/tokens.lua deleted file mode 100644 index 5f455be..0000000 --- a/src/parser.old/tokens.lua +++ /dev/null @@ -1,48 +0,0 @@ -local m = require 'lpeglabel' - -local Sp = m.S' \t\v\f' -local Nl = m.P'\r\n' + m.S'\r\n' -local Number = m.R'09'^1 -local Word = m.R('AZ', 'az', '__', '\x80\xff') * m.R('AZ', 'az', '09', '__', '\x80\xff')^0 -local Symbol = m.P'==' - + m.P'~=' - + m.P'--' - -- non-standard: - + m.P'<<=' - + m.P'>>=' - + m.P'//=' - -- end non-standard - + m.P'<<' - + m.P'>>' - + m.P'<=' - + m.P'>=' - + m.P'//' - + m.P'...' - + m.P'..' - + m.P'::' - -- non-standard: - + m.P'!=' - + m.P'&&' - + m.P'||' - + m.P'/*' - + m.P'*/' - + m.P'+=' - + m.P'-=' - + m.P'*=' - + m.P'%=' - + m.P'&=' - + m.P'|=' - + m.P'^=' - + m.P'/=' - -- end non-standard - -- singles - + m.S'+-*/!#%^&()={}[]|\\\'":;<>,.?~`' -local Unknown = (1 - Number - Word - Symbol - Sp - Nl)^1 -local Token = m.Cp() * m.C(Nl + Number + Word + Symbol + Unknown) - -local Parser = m.Ct((Sp^1 + Token)^0) - -return function (lua) - local results = Parser:match(lua) - return results -end diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index f599de5..9db29b3 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -33,6 +33,7 @@ require 'parser.ast.main' ---@class LuaParser.Ast ---@field envMode 'fenv' | '_ENV' ---@field main LuaParser.Node.Main +---@field fullCompile? boolean # 是否是完整编译 ---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast local M = class.declare 'LuaParser.Ast' diff --git a/src/parser/ast/base.lua b/src/parser/ast/base.lua index 95cac0c..c006462 100644 --- a/src/parser/ast/base.lua +++ b/src/parser/ast/base.lua @@ -13,7 +13,7 @@ local class = require 'class' ---@field finishCol integer # 结束列号 ---@field code string # 对应的代码 ---@field parent unknown ----@field parentBlock LuaParser.Node.Block +---@field parentBlock LuaParser.Node.Block | false ---@field parentFunction LuaParser.Node.Function ---@field asNumber? number ---@field asString? string @@ -95,17 +95,23 @@ Base.__getter.code = function (self) end ---@param self LuaParser.Node.Base ----@return string +---@return any ---@return true Base.__getter.parent = function (self) + if self.start == 0 then + return false, true + end error('未设置父节点:' .. self.type) end ---@param self LuaParser.Node.Base ----@return LuaParser.Node.Block +---@return LuaParser.Node.Block | false ---@return true Base.__getter.parentBlock = function (self) local parent = self.parent + if not parent then + return false, true + end if parent.isBlock then return parent, true end diff --git a/src/parser/ast/block.lua b/src/parser/ast/block.lua index 9411656..e3488b4 100644 --- a/src/parser/ast/block.lua +++ b/src/parser/ast/block.lua @@ -16,26 +16,6 @@ Block.__getter.locals = function () return {}, true end ----@param self LuaParser.Node.Block ----@return table ----@return true -Block.__getter.localMap = function (self) - local blocks = self.ast.blocks - ---@class LuaParser.Node.Block - local parentBlock = blocks[#blocks - 1] - if not parentBlock then - return {}, true - end - local parentLocalMap = parentBlock.localMap - return setmetatable({}, { - __index = function (t, k) - local v = parentLocalMap[k] or false - t[k] = v - return v - end - }), true -end - ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' @@ -51,8 +31,24 @@ local FinishMap = { ---@private ---@param block LuaParser.Node.Block function Ast:blockStart(block) + local parentBlock = self.blocks[#self.blocks] self.blocks[#self.blocks+1] = block self.curBlock = block + + ---@diagnostic disable: invisible + if parentBlock then + local parentLocalMap = parentBlock.localMap + block.localMap = setmetatable({}, { + __index = function (t, k) + local v = parentLocalMap[k] or false + t[k] = v + return v + end + }) + else + block.localMap = {} + end + ---@diagnostic enable: invisible end ---@private diff --git a/src/parser/ast/main.lua b/src/parser/ast/main.lua index d9c2f6e..e0eee04 100644 --- a/src/parser/ast/main.lua +++ b/src/parser/ast/main.lua @@ -6,9 +6,18 @@ local Main = class.declare('LuaParser.Node.Main', 'LuaParser.Node.Function') Main.isMain = true +function Main.__getter.parent() + return false, true +end + +function Main.__getter.parentBlock() + return false, true +end + ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@private function Ast:skipShebang() if self.code:sub(1, 2) == '#!' then local pos = self.code:find('\n', 3, true) diff --git a/src/parser/compile.lua b/src/parser/compile.lua index d261243..4c7f3d9 100644 --- a/src/parser/compile.lua +++ b/src/parser/compile.lua @@ -32,6 +32,7 @@ local LuaParser = class.get 'LuaParser' ---@return LuaParser.Ast function LuaParser.compile(code, version, options) local ast = class.new 'LuaParser.Ast' (code, version, options) + ast.fullCompile = true ast.main = ast:parseMain() return ast end diff --git a/test/ast.old/Action.lua b/test/ast.old/Action.lua deleted file mode 100644 index b83511c..0000000 --- a/test/ast.old/Action.lua +++ /dev/null @@ -1,1570 +0,0 @@ -CHECK'x = 1' -{ - type = "setglobal", - start = 0, - finish = 1, - range = 5, - value = { - type = "integer", - start = 4, - finish = 5, - parent = "", - [1] = 1, - }, - [1] = "x", -} -CHECK'local x' -{ - type = "local", - start = 6, - finish = 7, - effect = 7, - locPos = 0, - [1] = "x", -} -CHECK'local x = 1' -{ - type = "local", - start = 6, - finish = 7, - effect = 11, - range = 11, - locPos = 0, - value = { - type = "integer", - start = 10, - finish = 11, - parent = "", - [1] = 1, - }, - [1] = "x", -} -CHECK'local x = x' -{ - type = "local", - start = 6, - finish = 7, - effect = 11, - range = 11, - locPos = 0, - value = { - type = "getglobal", - start = 10, - finish = 11, - parent = "", - [1] = "x", - }, - [1] = "x", -} -CHECK'local x = 1' -{ - type = "local", - start = 6, - finish = 7, - effect = 27, - range = 27, - locPos = 0, - value = { - type = "integer", - start = 26, - finish = 27, - parent = "", - [1] = 1, - }, - attrs = { - type = "localattrs", - parent = "", - [1] = { - type = "localattr", - start = 8, - finish = 15, - parent = "", - [1] = "close", - }, - [2] = { - type = "localattr", - start = 16, - finish = 23, - parent = "", - [1] = "const", - }, - }, - [1] = "x", -} -CHECK'local x < const > = 1' -{ - type = "local", - start = 6, - finish = 7, - effect = 21, - range = 21, - locPos = 0, - value = { - type = "integer", - start = 20, - finish = 21, - parent = "", - [1] = 1, - }, - attrs = { - type = "localattrs", - parent = "", - [1] = { - type = "localattr", - start = 8, - finish = 17, - parent = "", - [1] = "const", - }, - }, - [1] = "x", -} -CHECK 'x.y = 1' -{ - type = "setfield", - start = 0, - finish = 3, - range = 7, - node = "", - dot = { - type = ".", - start = 1, - finish = 2, - }, - field = { - type = "field", - start = 2, - finish = 3, - parent = "", - [1] = "y", - }, - value = { - type = "integer", - start = 6, - finish = 7, - parent = "", - [1] = 1, - }, -} -CHECK 'x[y] = 1' -{ - type = "setindex", - start = 0, - finish = 4, - range = 8, - node = "", - index = { - type = "getglobal", - start = 2, - finish = 3, - parent = "", - [1] = "y", - }, - value = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 1, - }, -} -CHECK'x = function () end' -{ - type = "setglobal", - start = 0, - finish = 1, - range = 19, - value = { - type = "function", - start = 4, - bstart = 15, - finish = 19, - keyword = { - [1] = 4, - [2] = 12, - [3] = 16, - [4] = 19, - }, - parent = "", - args = { - type = "funcargs", - start = 13, - finish = 15, - parent = "", - }, - }, - [1] = "x", -} -CHECK'x.y = function () end' -{ - type = "setfield", - start = 0, - finish = 3, - range = 21, - node = "", - dot = { - type = ".", - start = 1, - finish = 2, - }, - field = { - type = "field", - start = 2, - finish = 3, - parent = "", - [1] = "y", - }, - value = { - type = "function", - start = 6, - bstart = 17, - finish = 21, - keyword = { - [1] = 6, - [2] = 14, - [3] = 18, - [4] = 21, - }, - parent = "", - args = { - type = "funcargs", - start = 15, - finish = 17, - parent = "", - }, - }, -} -CHECK'require "xxx"' -{ - type = "call", - start = 0, - finish = 13, - node = "", - args = { - type = "callargs", - start = 8, - finish = 13, - parent = "", - [1] = { - type = "string", - start = 8, - finish = 13, - parent = "", - [1] = "xxx", - [2] = "\"", - }, - }, -} -CHECK'func.x(1, 2)' -{ - type = "call", - start = 0, - finish = 12, - node = "", - args = { - type = "callargs", - start = 6, - finish = 12, - parent = "", - [1] = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 10, - finish = 11, - parent = "", - [1] = 2, - }, - }, -} -CHECK'func:x(1, 2)' -{ - type = "call", - start = 0, - finish = 12, - node = "", - args = { - type = "callargs", - start = 6, - finish = 12, - parent = "", - [1] = { - type = "self", - start = 4, - finish = 5, - parent = "", - [1] = "self", - }, - [2] = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 1, - }, - [3] = { - type = "integer", - start = 10, - finish = 11, - parent = "", - [1] = 2, - }, - }, -} -CHECK'("%s"):format(1)' -{ - type = "call", - start = 0, - finish = 16, - node = "", - args = { - type = "callargs", - start = 13, - finish = 16, - parent = "", - [1] = { - type = "self", - start = 6, - finish = 7, - parent = "", - [1] = "self", - }, - [2] = { - type = "integer", - start = 14, - finish = 15, - parent = "", - [1] = 1, - }, - }, -} -CHECK'do end' -{ - type = "do", - start = 0, - bstart = 2, - finish = 6, - keyword = { - [1] = 0, - [2] = 2, - [3] = 3, - [4] = 6, - }, -} -CHECK'do x = 1 end' -{ - type = "do", - start = 0, - bstart = 2, - finish = 12, - keyword = { - [1] = 0, - [2] = 2, - [3] = 9, - [4] = 12, - }, - [1] = { - type = "setglobal", - start = 3, - finish = 4, - range = 8, - parent = "", - value = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 1, - }, - [1] = "x", - }, -} -CHECK'return' -{ - type = "return", - start = 0, - finish = 6, -} -CHECK'return 1' -{ - type = "return", - start = 0, - finish = 8, - [1] = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 1, - }, -} -CHECK'return 1, 2' -{ - type = "return", - start = 0, - finish = 11, - [1] = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 10, - finish = 11, - parent = "", - [1] = 2, - }, -} -CHECK'::CONTINUE::' -{ - type = "label", - start = 2, - finish = 10, - [1] = "CONTINUE", -} -CHECK'goto CONTINUE' -{ - type = "goto", - start = 5, - finish = 13, - keyStart = 0, - [1] = "CONTINUE", -} -CHECK[[if 1 then -end]] -{ - type = "if", - start = 0, - finish = 10003, - [1] = { - type = "ifblock", - start = 0, - bstart = 9, - finish = 10000, - keyword = { - [1] = 0, - [2] = 2, - [3] = 5, - [4] = 9, - }, - parent = "", - filter = { - type = "integer", - start = 3, - finish = 4, - parent = "", - [1] = 1, - }, - }, -} -CHECK[[if 1 then - return -end]] -{ - type = "if", - start = 0, - finish = 20003, - [1] = { - type = "ifblock", - start = 0, - bstart = 9, - finish = 20000, - keyword = { - [1] = 0, - [2] = 2, - [3] = 5, - [4] = 9, - }, - parent = "", - filter = { - type = "integer", - start = 3, - finish = 4, - parent = "", - [1] = 1, - }, - hasReturn = true, - [1] = { - type = "return", - start = 10004, - finish = 10010, - parent = "", - }, - }, -} -CHECK[[if 1 then - return -else - return -end]] -{ - type = "if", - start = 0, - finish = 40003, - [1] = { - type = "ifblock", - start = 0, - bstart = 9, - finish = 20000, - keyword = { - [1] = 0, - [2] = 2, - [3] = 5, - [4] = 9, - }, - parent = "", - filter = { - type = "integer", - start = 3, - finish = 4, - parent = "", - [1] = 1, - }, - hasReturn = true, - [1] = { - type = "return", - start = 10004, - finish = 10010, - parent = "", - }, - }, - [2] = { - type = "elseblock", - start = 20000, - bstart = 20004, - finish = 40000, - keyword = { - [1] = 20000, - [2] = 20004, - }, - parent = "", - hasReturn = true, - [1] = { - type = "return", - start = 30004, - finish = 30010, - parent = "", - }, - }, -} -CHECK[[if 1 then - return -elseif 1 then - return -end]] -{ - type = "if", - start = 0, - finish = 40003, - [1] = { - type = "ifblock", - start = 0, - bstart = 9, - finish = 20000, - keyword = { - [1] = 0, - [2] = 2, - [3] = 5, - [4] = 9, - }, - parent = "", - filter = { - type = "integer", - start = 3, - finish = 4, - parent = "", - [1] = 1, - }, - hasReturn = true, - [1] = { - type = "return", - start = 10004, - finish = 10010, - parent = "", - }, - }, - [2] = { - type = "elseifblock", - start = 20000, - bstart = 20013, - finish = 40000, - keyword = { - [1] = 20000, - [2] = 20006, - [3] = 20009, - [4] = 20013, - }, - parent = "", - filter = { - type = "integer", - start = 20007, - finish = 20008, - parent = "", - [1] = 1, - }, - hasReturn = true, - [1] = { - type = "return", - start = 30004, - finish = 30010, - parent = "", - }, - }, -} -CHECK[[if 1 then - return -elseif 1 then - return -else - return -end]] -{ - type = "if", - start = 0, - finish = 60003, - [1] = { - type = "ifblock", - start = 0, - bstart = 9, - finish = 20000, - keyword = { - [1] = 0, - [2] = 2, - [3] = 5, - [4] = 9, - }, - parent = "", - filter = { - type = "integer", - start = 3, - finish = 4, - parent = "", - [1] = 1, - }, - hasReturn = true, - [1] = { - type = "return", - start = 10004, - finish = 10010, - parent = "", - }, - }, - [2] = { - type = "elseifblock", - start = 20000, - bstart = 20013, - finish = 40000, - keyword = { - [1] = 20000, - [2] = 20006, - [3] = 20009, - [4] = 20013, - }, - parent = "", - filter = { - type = "integer", - start = 20007, - finish = 20008, - parent = "", - [1] = 1, - }, - hasReturn = true, - [1] = { - type = "return", - start = 30004, - finish = 30010, - parent = "", - }, - }, - [3] = { - type = "elseblock", - start = 40000, - bstart = 40004, - finish = 60000, - keyword = { - [1] = 40000, - [2] = 40004, - }, - parent = "", - hasReturn = true, - [1] = { - type = "return", - start = 50004, - finish = 50010, - parent = "", - }, - }, -} -CHECK[[ -if 1 then -elseif 1 then -elseif 1 then -elseif 1 then -end]] -{ - type = "if", - start = 0, - finish = 40003, - [1] = { - type = "ifblock", - start = 0, - bstart = 9, - finish = 10000, - keyword = { - [1] = 0, - [2] = 2, - [3] = 5, - [4] = 9, - }, - parent = "", - filter = { - type = "integer", - start = 3, - finish = 4, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "elseifblock", - start = 10000, - bstart = 10013, - finish = 20000, - keyword = { - [1] = 10000, - [2] = 10006, - [3] = 10009, - [4] = 10013, - }, - parent = "", - filter = { - type = "integer", - start = 10007, - finish = 10008, - parent = "", - [1] = 1, - }, - }, - [3] = { - type = "elseifblock", - start = 20000, - bstart = 20013, - finish = 30000, - keyword = { - [1] = 20000, - [2] = 20006, - [3] = 20009, - [4] = 20013, - }, - parent = "", - filter = { - type = "integer", - start = 20007, - finish = 20008, - parent = "", - [1] = 1, - }, - }, - [4] = { - type = "elseifblock", - start = 30000, - bstart = 30013, - finish = 40000, - keyword = { - [1] = 30000, - [2] = 30006, - [3] = 30009, - [4] = 30013, - }, - parent = "", - filter = { - type = "integer", - start = 30007, - finish = 30008, - parent = "", - [1] = 1, - }, - }, -} -CHECK[[ -if 1 then - if 2 then - end -end]] -{ - type = "if", - start = 0, - finish = 30003, - [1] = { - type = "ifblock", - start = 0, - bstart = 9, - finish = 30000, - keyword = { - [1] = 0, - [2] = 2, - [3] = 5, - [4] = 9, - }, - parent = "", - filter = { - type = "integer", - start = 3, - finish = 4, - parent = "", - [1] = 1, - }, - [1] = { - type = "if", - start = 10004, - finish = 20007, - parent = "", - [1] = { - type = "ifblock", - start = 10004, - bstart = 10013, - finish = 20004, - keyword = { - [1] = 10004, - [2] = 10006, - [3] = 10009, - [4] = 10013, - }, - parent = "", - filter = { - type = "integer", - start = 10007, - finish = 10008, - parent = "", - [1] = 2, - }, - }, - }, - }, -} -CHECK[[ -if 1 then -elseif 1 then -else -end]] -{ - type = "if", - start = 0, - finish = 30003, - [1] = { - type = "ifblock", - start = 0, - bstart = 9, - finish = 10000, - keyword = { - [1] = 0, - [2] = 2, - [3] = 5, - [4] = 9, - }, - parent = "", - filter = { - type = "integer", - start = 3, - finish = 4, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "elseifblock", - start = 10000, - bstart = 10013, - finish = 20000, - keyword = { - [1] = 10000, - [2] = 10006, - [3] = 10009, - [4] = 10013, - }, - parent = "", - filter = { - type = "integer", - start = 10007, - finish = 10008, - parent = "", - [1] = 1, - }, - }, - [3] = { - type = "elseblock", - start = 20000, - bstart = 20004, - finish = 30000, - keyword = { - [1] = 20000, - [2] = 20004, - }, - parent = "", - }, -} -CHECK[[ -for i = 1, i do - return -end]] -{ - type = "loop", - start = 0, - bstart = 13, - finish = 20003, - keyword = { - [1] = 0, - [2] = 3, - [3] = 13, - [4] = 15, - [5] = 20000, - [6] = 20003, - }, - loc = { - type = "local", - start = 4, - finish = 5, - effect = 12, - parent = "", - [1] = "i", - }, - init = { - type = "integer", - start = 8, - finish = 9, - parent = "", - [1] = 1, - }, - max = { - type = "getglobal", - start = 11, - finish = 12, - parent = "", - [1] = "i", - }, - locals = "", - [1] = { - type = "return", - start = 10004, - finish = 10010, - parent = "", - }, -} -CHECK[[ -for i = 1, 10, i do - return -end]] -{ - type = "loop", - start = 0, - bstart = 17, - finish = 20003, - keyword = { - [1] = 0, - [2] = 3, - [3] = 17, - [4] = 19, - [5] = 20000, - [6] = 20003, - }, - loc = { - type = "local", - start = 4, - finish = 5, - effect = 16, - parent = "", - [1] = "i", - }, - init = { - type = "integer", - start = 8, - finish = 9, - parent = "", - [1] = 1, - }, - max = { - type = "integer", - start = 11, - finish = 13, - parent = "", - [1] = 10, - }, - step = { - type = "getglobal", - start = 15, - finish = 16, - parent = "", - [1] = "i", - }, - locals = "", - [1] = { - type = "return", - start = 10004, - finish = 10010, - parent = "", - }, -} -CHECK[[ -for a in a do - return -end]] -{ - type = "in", - start = 0, - bstart = 11, - finish = 20003, - keyword = { - [1] = 0, - [2] = 3, - [3] = 6, - [4] = 8, - [5] = 11, - [6] = 13, - [7] = 20000, - [8] = 20003, - }, - keys = { - type = "list", - start = 4, - finish = 5, - range = 8, - parent = "", - [1] = { - type = "local", - start = 4, - finish = 5, - effect = 10, - parent = "", - [1] = "a", - }, - }, - exps = { - type = "list", - start = 9, - finish = 10, - parent = "", - [1] = { - type = "getglobal", - start = 9, - finish = 10, - parent = "", - [1] = "a", - }, - }, - locals = "", - [1] = { - type = "return", - start = 10004, - finish = 10010, - parent = "", - }, -} -CHECK[[ -for a, b, c in a, b, c do - return -end]] -{ - type = "in", - start = 0, - bstart = 23, - finish = 20003, - keyword = { - [1] = 0, - [2] = 3, - [3] = 12, - [4] = 14, - [5] = 23, - [6] = 25, - [7] = 20000, - [8] = 20003, - }, - keys = { - type = "list", - start = 4, - finish = 11, - range = 14, - parent = "", - [1] = { - type = "local", - start = 4, - finish = 5, - effect = 22, - parent = "", - [1] = "a", - }, - [2] = { - type = "local", - start = 7, - finish = 8, - effect = 22, - parent = "", - [1] = "b", - }, - [3] = { - type = "local", - start = 10, - finish = 11, - effect = 22, - parent = "", - [1] = "c", - }, - }, - exps = { - type = "list", - start = 15, - finish = 22, - parent = "", - [1] = { - type = "getglobal", - start = 15, - finish = 16, - parent = "", - [1] = "a", - }, - [2] = { - type = "getglobal", - start = 18, - finish = 19, - parent = "", - [1] = "b", - }, - [3] = { - type = "getglobal", - start = 21, - finish = 22, - parent = "", - [1] = "c", - }, - }, - locals = "", - [1] = { - type = "return", - start = 10004, - finish = 10010, - parent = "", - }, -} -CHECK[[ -while true do - return -end]] -{ - type = "while", - start = 0, - bstart = 11, - finish = 20003, - keyword = { - [1] = 0, - [2] = 5, - [3] = 11, - [4] = 13, - [5] = 20000, - [6] = 20003, - }, - filter = { - type = "boolean", - start = 6, - finish = 10, - parent = "", - [1] = true, - }, - [1] = { - type = "return", - start = 10004, - finish = 10010, - parent = "", - }, -} -CHECK[[ -repeat - break -until 1]] -{ - type = "repeat", - start = 0, - bstart = 6, - finish = 20007, - keyword = { - [1] = 0, - [2] = 6, - [3] = 20000, - [4] = 20005, - }, - filter = { - type = "integer", - start = 20006, - finish = 20007, - parent = "", - [1] = 1, - }, - breaks = { - [1] = { - type = "break", - start = 10004, - finish = 10009, - parent = "", - }, - }, - [1] = { - type = "break", - start = 10004, - finish = 10009, - parent = "", - }, -} -CHECK[[ -function test() - return -end]] -{ - type = "setglobal", - start = 9, - vstart = 0, - finish = 13, - range = 20003, - value = { - type = "function", - start = 0, - bstart = 15, - finish = 20003, - keyword = { - [1] = 0, - [2] = 8, - [3] = 20000, - [4] = 20003, - }, - parent = "", - args = { - type = "funcargs", - start = 13, - finish = 15, - parent = "", - }, - returns = "", - hasReturn = true, - [1] = { - type = "return", - start = 10004, - finish = 10010, - parent = "", - }, - }, - [1] = "test", -} -CHECK[[ -function test(a) - return -end]] -{ - type = "setglobal", - start = 9, - vstart = 0, - finish = 13, - range = 20003, - value = { - type = "function", - start = 0, - bstart = 16, - finish = 20003, - keyword = { - [1] = 0, - [2] = 8, - [3] = 20000, - [4] = 20003, - }, - parent = "", - args = { - type = "funcargs", - start = 13, - finish = 16, - parent = "", - [1] = { - type = "local", - start = 14, - finish = 15, - effect = 15, - parent = "", - [1] = "a", - }, - }, - locals = "", - returns = "", - hasReturn = true, - [1] = { - type = "return", - start = 10004, - finish = 10010, - parent = "", - }, - }, - [1] = "test", -} -CHECK[[ -function a.b:c(a, b, c) - return -end]] -{ - type = "setmethod", - start = 9, - vstart = 0, - finish = 14, - range = 20003, - node = "", - colon = { - type = ":", - start = 12, - finish = 13, - }, - method = { - type = "method", - start = 13, - finish = 14, - parent = "", - [1] = "c", - }, - value = { - type = "function", - start = 0, - bstart = 23, - finish = 20003, - keyword = { - [1] = 0, - [2] = 8, - [3] = 20000, - [4] = 20003, - }, - parent = "", - args = { - type = "funcargs", - start = 14, - finish = 23, - parent = "", - [1] = { - type = "self", - start = 8, - finish = 8, - effect = 8, - parent = "", - [1] = "self", - }, - [2] = { - type = "local", - start = 15, - finish = 16, - effect = 16, - parent = "", - [1] = "a", - }, - [3] = { - type = "local", - start = 18, - finish = 19, - effect = 19, - parent = "", - [1] = "b", - }, - [4] = { - type = "local", - start = 21, - finish = 22, - effect = 22, - parent = "", - [1] = "c", - }, - }, - locals = "", - returns = "", - hasReturn = true, - [1] = { - type = "return", - start = 10004, - finish = 10010, - parent = "", - }, - }, -} -CHECK[[ -function m:f() - return self -end]] -{ - type = "setmethod", - start = 9, - vstart = 0, - finish = 12, - range = 20003, - node = "", - colon = { - type = ":", - start = 10, - finish = 11, - }, - method = { - type = "method", - start = 11, - finish = 12, - parent = "", - [1] = "f", - }, - value = { - type = "function", - start = 0, - bstart = 14, - finish = 20003, - keyword = { - [1] = 0, - [2] = 8, - [3] = 20000, - [4] = 20003, - }, - parent = "", - args = { - type = "funcargs", - start = 12, - finish = 14, - parent = "", - [1] = { - type = "self", - start = 8, - finish = 8, - effect = 8, - parent = "", - ref = "", - [1] = "self", - }, - }, - locals = "", - returns = "", - hasReturn = true, - [1] = { - type = "return", - start = 10004, - finish = 10015, - parent = "", - [1] = { - type = "getlocal", - start = 10011, - finish = 10015, - parent = "", - node = "", - [1] = "self", - }, - }, - }, -} - -CHECK [[ -if true then - X = print -end -]] -{ - type = "if", - start = 0, - finish = 20003, - [1] = { - type = "ifblock", - start = 0, - bstart = 12, - finish = 20000, - keyword = { - [1] = 0, - [2] = 2, - [3] = 8, - [4] = 12, - }, - parent = "", - filter = { - type = "boolean", - start = 3, - finish = 7, - parent = "", - [1] = true, - }, - [1] = { - type = "setglobal", - start = 10004, - finish = 10005, - range = 10013, - parent = "", - value = { - type = "getglobal", - start = 10008, - finish = 10013, - parent = "", - [1] = "print", - }, - [1] = "X", - }, - }, -} - -CHECK [[ -f:read('a') -]] -{ - type = "call", - start = 0, - finish = 11, - node = "", - args = { - type = "callargs", - start = 6, - finish = 11, - parent = "", - [1] = { - type = "self", - start = 1, - finish = 2, - parent = "", - [1] = "self", - }, - [2] = { - type = "string", - start = 7, - finish = 10, - parent = "", - [1] = "a", - [2] = "'", - }, - }, -} - -CHECK [[ -f:read 'a' -]] -{ - type = "call", - start = 0, - finish = 10, - node = "", - args = { - type = "callargs", - start = 7, - finish = 10, - parent = "", - [1] = { - type = "self", - start = 1, - finish = 2, - parent = "", - [1] = "self", - }, - [2] = { - type = "string", - start = 7, - finish = 10, - parent = "", - [1] = "a", - [2] = "'", - }, - }, -} diff --git a/test/ast.old/Boolean.lua b/test/ast.old/Boolean.lua deleted file mode 100644 index d3b292e..0000000 --- a/test/ast.old/Boolean.lua +++ /dev/null @@ -1,14 +0,0 @@ -CHECK [[true]] -{ - type = "boolean", - start = 0, - finish = 4, - [1] = true, -} -CHECK [[false]] -{ - type = "boolean", - start = 0, - finish = 5, - [1] = false, -} diff --git a/test/ast.old/Comment.lua b/test/ast.old/Comment.lua deleted file mode 100644 index 81c1c92..0000000 --- a/test/ast.old/Comment.lua +++ /dev/null @@ -1,43 +0,0 @@ -Comment [[--AAA]] -{ - [1] = { - type = "comment.short", - start = 0, - finish = 5, - text = "AAA", - }, -} - -Comment [[ ---AAA -]] -{ - [1] = { - type = "comment.short", - start = 0, - finish = 5, - text = "AAA", - }, -} - -Comment [[//AAA]] -{ - [1] = { - type = "comment.cshort", - start = 0, - finish = 5, - text = "AAA", - }, -} - -Comment [[ -//AAA -]] -{ - [1] = { - type = "comment.cshort", - start = 0, - finish = 5, - text = "AAA", - }, -} diff --git a/test/ast.old/Dirty.lua b/test/ast.old/Dirty.lua deleted file mode 100644 index 373a495..0000000 --- a/test/ast.old/Dirty.lua +++ /dev/null @@ -1,1366 +0,0 @@ -CHECK'a.' -{ - type = "main", - start = 0, - finish = 2, - locals = "", - [1] = { - type = "getfield", - start = 0, - finish = 2, - parent = "", - node = "", - dot = { - type = ".", - start = 1, - finish = 2, - }, - }, -} - -CHECK'a:' -{ - type = "main", - start = 0, - finish = 2, - locals = "", - [1] = { - type = "getmethod", - start = 0, - finish = 2, - parent = "", - node = "", - colon = { - type = ":", - start = 1, - finish = 2, - }, - }, -} - -CHECK [[ -if true -a -]] -{ - type = "main", - start = 0, - finish = 20000, - locals = "", - [1] = { - type = "if", - start = 0, - finish = 20000, - parent = "", - [1] = { - type = "ifblock", - start = 0, - bstart = 7, - finish = 20000, - keyword = { - [1] = 0, - [2] = 2, - }, - parent = "", - filter = { - type = "boolean", - start = 3, - finish = 7, - parent = "", - [1] = true, - }, - [1] = { - type = "getglobal", - start = 10000, - finish = 10001, - parent = "", - node = "", - [1] = "a", - }, - }, - }, -} - -CHECK [[ -if true then -a -]] -{ - type = "main", - start = 0, - finish = 20000, - locals = "", - [1] = { - type = "if", - start = 0, - finish = 20000, - parent = "", - [1] = { - type = "ifblock", - start = 0, - bstart = 12, - finish = 20000, - keyword = { - [1] = 0, - [2] = 2, - [3] = 8, - [4] = 12, - }, - parent = "", - filter = { - type = "boolean", - start = 3, - finish = 7, - parent = "", - [1] = true, - }, - [1] = { - type = "getglobal", - start = 10000, - finish = 10001, - parent = "", - node = "", - [1] = "a", - }, - }, - }, -} - -CHECK [[ -x = -]] -{ - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "setglobal", - start = 0, - finish = 1, - parent = "", - node = "", - [1] = "x", - }, -} - -CHECK'1 == 2' -{ - type = "main", - start = 0, - finish = 6, - locals = "", - [1] = { - type = "binary", - start = 0, - finish = 6, - parent = "", - op = { - type = "==", - start = 2, - finish = 4, - }, - [1] = { - type = "integer", - start = 0, - finish = 1, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 2, - }, - }, -} - -CHECK 'local function a' -{ - type = "main", - start = 0, - finish = 16, - locals = "", - [1] = { - type = "local", - start = 15, - vstart = 6, - finish = 16, - effect = 16, - range = 16, - parent = "", - locPos = 0, - value = { - type = "function", - start = 6, - bstart = 16, - finish = 16, - keyword = { - [1] = 6, - [2] = 14, - }, - parent = "", - }, - [1] = "a", - }, -} - -CHECK 'local function' -{ - type = "main", - start = 0, - finish = 14, - locals = "", - [1] = { - type = "function", - start = 6, - bstart = 14, - finish = 14, - keyword = { - [1] = 6, - [2] = 14, - }, - parent = "", - }, -} - -CHECK 'local function f(' -{ - type = "main", - start = 0, - finish = 17, - locals = "", - [1] = { - type = "local", - start = 15, - vstart = 6, - finish = 16, - effect = 16, - range = 17, - parent = "", - locPos = 0, - value = { - type = "function", - start = 6, - bstart = 17, - finish = 17, - keyword = { - [1] = 6, - [2] = 14, - }, - parent = "", - args = { - type = "funcargs", - start = 16, - finish = 17, - parent = "", - }, - }, - [1] = "f", - }, -} - -CHECK 'local function a(v' -{ - type = "main", - start = 0, - finish = 18, - locals = "", - [1] = { - type = "local", - start = 15, - vstart = 6, - finish = 16, - effect = 16, - range = 18, - parent = "", - locPos = 0, - value = { - type = "function", - start = 6, - bstart = 18, - finish = 18, - keyword = { - [1] = 6, - [2] = 14, - }, - parent = "", - args = { - type = "funcargs", - start = 16, - finish = 18, - parent = "", - [1] = { - type = "local", - start = 17, - finish = 18, - effect = 18, - parent = "", - [1] = "v", - }, - }, - locals = "", - }, - [1] = "a", - }, -} - -CHECK 'function a' -{ - type = "main", - start = 0, - finish = 10, - locals = "", - [1] = { - type = "setglobal", - start = 9, - vstart = 0, - finish = 10, - range = 10, - parent = "", - node = "", - value = { - type = "function", - start = 0, - bstart = 10, - finish = 10, - keyword = { - [1] = 0, - [2] = 8, - }, - parent = "", - }, - [1] = "a", - }, -} - -CHECK 'function a:' -{ - type = "main", - start = 0, - finish = 11, - locals = "", - [1] = { - type = "setmethod", - start = 9, - vstart = 0, - finish = 11, - range = 11, - parent = "", - node = "", - colon = { - type = ":", - start = 10, - finish = 11, - }, - value = { - type = "function", - start = 0, - bstart = 11, - finish = 11, - keyword = { - [1] = 0, - [2] = 8, - }, - parent = "", - locals = "", - }, - }, -} - -CHECK 'function a:b(v' -{ - type = "main", - start = 0, - finish = 14, - locals = "", - [1] = { - type = "setmethod", - start = 9, - vstart = 0, - finish = 12, - range = 14, - parent = "", - node = "", - colon = { - type = ":", - start = 10, - finish = 11, - }, - method = { - type = "method", - start = 11, - finish = 12, - parent = "", - [1] = "b", - }, - value = { - type = "function", - start = 0, - bstart = 14, - finish = 14, - keyword = { - [1] = 0, - [2] = 8, - }, - parent = "", - args = { - type = "funcargs", - start = 12, - finish = 14, - parent = "", - [1] = { - type = "self", - start = 8, - finish = 8, - effect = 8, - parent = "", - [1] = "self", - }, - [2] = { - type = "local", - start = 13, - finish = 14, - effect = 14, - parent = "", - [1] = "v", - }, - }, - locals = "", - }, - }, -} - -CHECK 'return local a' -{ - type = "main", - start = 0, - finish = 14, - locals = "", - returns = "", - [1] = { - type = "return", - start = 0, - finish = 6, - parent = "", - }, - [2] = { - type = "local", - start = 13, - finish = 14, - effect = 14, - parent = "", - locPos = 7, - [1] = "a", - }, -} - -CHECK 'end' -{ - type = "main", - start = 0, - finish = 3, - locals = "", -} - -CHECK 'local x = ,' -{ - type = "main", - start = 0, - finish = 11, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 7, - parent = "", - locPos = 0, - [1] = "x", - }, -} - -CHECK 'local x = (a && b)' -{ - type = "main", - start = 0, - finish = 18, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 18, - range = 18, - parent = "", - locPos = 0, - value = { - type = "paren", - start = 10, - finish = 18, - parent = "", - exp = { - type = "binary", - start = 11, - finish = 17, - parent = "", - op = { - type = "and", - start = 13, - finish = 15, - }, - [1] = { - type = "getglobal", - start = 11, - finish = 12, - parent = "", - node = "", - [1] = "a", - }, - [2] = { - type = "getglobal", - start = 16, - finish = 17, - parent = "", - node = "", - [1] = "b", - }, - }, - }, - [1] = "x", - }, -} - -CHECK 'return 1 + + 1' -{ - type = "main", - start = 0, - finish = 14, - locals = "", - returns = "", - [1] = { - type = "return", - start = 0, - finish = 14, - parent = "", - [1] = { - type = "binary", - start = 7, - finish = 14, - parent = "", - op = { - type = "+", - start = 9, - finish = 10, - }, - [1] = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 13, - finish = 14, - parent = "", - [1] = 1, - }, - }, - }, -} - -CHECK 'return 1 + # + 2' -{ - type = "main", - start = 0, - finish = 16, - locals = "", - returns = "", - [1] = { - type = "return", - start = 0, - finish = 16, - parent = "", - [1] = { - type = "binary", - start = 7, - finish = 16, - parent = "", - op = { - type = "+", - start = 13, - finish = 14, - }, - [1] = { - type = "binary", - start = 7, - finish = 12, - parent = "", - op = { - type = "+", - start = 9, - finish = 10, - }, - [1] = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 1, - }, - [2] = { - type = "unary", - start = 11, - finish = 12, - parent = "", - op = { - type = "#", - start = 11, - finish = 12, - }, - }, - }, - [2] = { - type = "integer", - start = 15, - finish = 16, - parent = "", - [1] = 2, - }, - }, - }, -} - -CHECK 'return 1 + 2 + # + 3' -{ - type = "main", - start = 0, - finish = 20, - locals = "", - returns = "", - [1] = { - type = "return", - start = 0, - finish = 20, - parent = "", - [1] = { - type = "binary", - start = 7, - finish = 20, - parent = "", - op = { - type = "+", - start = 17, - finish = 18, - }, - [1] = { - type = "binary", - start = 7, - finish = 16, - parent = "", - op = { - type = "+", - start = 13, - finish = 14, - }, - [1] = { - type = "binary", - start = 7, - finish = 12, - parent = "", - op = { - type = "+", - start = 9, - finish = 10, - }, - [1] = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 11, - finish = 12, - parent = "", - [1] = 2, - }, - }, - [2] = { - type = "unary", - start = 15, - finish = 16, - parent = "", - op = { - type = "#", - start = 15, - finish = 16, - }, - }, - }, - [2] = { - type = "integer", - start = 19, - finish = 20, - parent = "", - [1] = 3, - }, - }, - }, -} - -CHECK [[ -- -return -]] -{ - type = "main", - start = 0, - finish = 20000, - locals = "", - [1] = { - type = "unary", - start = 0, - finish = 10006, - parent = "", - op = { - type = "-", - start = 0, - finish = 1, - }, - [1] = { - type = "getglobal", - start = 10000, - finish = 10006, - parent = "", - node = "", - [1] = "return", - }, - }, -} - -CHECK [[ -return; -:::: -return; -]] -{ - type = "main", - start = 0, - finish = 30000, - locals = "", - returns = "", - [1] = { - type = "return", - start = 0, - finish = 6, - parent = "", - }, - [2] = { - type = "return", - start = 20000, - finish = 20006, - parent = "", - }, -} - -CHECK [[ -return; -goto; -return; -]] -{ - type = "main", - start = 0, - finish = 30000, - locals = "", - returns = "", - [1] = { - type = "return", - start = 0, - finish = 6, - parent = "", - }, - [2] = { - type = "return", - start = 20000, - finish = 20006, - parent = "", - }, -} - -CHECK [[ -call(,-,not,1) -]] -{ - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "call", - start = 0, - finish = 14, - parent = "", - node = "", - args = { - type = "callargs", - start = 4, - finish = 14, - parent = "", - [1] = { - type = "unary", - start = 6, - finish = 7, - parent = "", - op = { - type = "-", - start = 6, - finish = 7, - }, - }, - [2] = { - type = "unary", - start = 8, - finish = 11, - parent = "", - op = { - type = "not", - start = 8, - finish = 11, - }, - }, - [3] = { - type = "integer", - start = 12, - finish = 13, - parent = "", - [1] = 1, - }, - }, - }, -} - -CHECK [[ -{ - ;,-;,1 -} -]] -{ - type = "main", - start = 0, - finish = 30000, - locals = "", - [1] = { - type = "table", - start = 0, - finish = 20001, - parent = "", - [1] = { - type = "tableexp", - start = 10006, - finish = 10007, - tindex = 1, - parent = "", - value = { - type = "unary", - start = 10006, - finish = 10007, - parent = "", - op = { - type = "-", - start = 10006, - finish = 10007, - }, - }, - }, - [2] = { - type = "tableexp", - start = 10009, - finish = 10010, - tindex = 2, - parent = "", - value = { - type = "integer", - start = 10009, - finish = 10010, - parent = "", - [1] = 1, - }, - }, - }, -} - -CHECK [[ -local a,b,,d -]] -{ - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 12, - parent = "", - locPos = 0, - [1] = "a", - }, - [2] = { - type = "local", - start = 8, - finish = 9, - effect = 12, - parent = "", - [1] = "b", - }, - [3] = { - type = "local", - start = 11, - finish = 12, - effect = 12, - parent = "", - [1] = "d", - }, -} - -CHECK [[ -if /**/ then -end -]] -{ - type = "main", - start = 0, - finish = 20000, - locals = "", - [1] = { - type = "if", - start = 0, - finish = 10003, - parent = "", - [1] = { - type = "ifblock", - start = 0, - bstart = 12, - finish = 10000, - keyword = { - [1] = 0, - [2] = 2, - [3] = 8, - [4] = 12, - }, - parent = "", - }, - }, -} - -CHECK [[ -f(break) -]] -{ - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "call", - start = 0, - finish = 8, - parent = "", - node = "", - args = { - type = "callargs", - start = 1, - finish = 8, - parent = "", - [1] = { - type = "getglobal", - start = 2, - finish = 7, - parent = "", - node = "", - [1] = "break", - }, - }, - }, -} - -CHECK [[ -print(x == ) -]] -{ - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "call", - start = 0, - finish = 12, - parent = "", - node = "", - args = { - type = "callargs", - start = 5, - finish = 12, - parent = "", - [1] = { - type = "binary", - start = 6, - finish = 10, - parent = "", - op = { - type = "==", - start = 8, - finish = 10, - }, - [1] = { - type = "getglobal", - start = 6, - finish = 7, - parent = "", - node = "", - [1] = "x", - }, - }, - }, - }, -} -CHECK [[ -local t = { - a = 1, -]] -{ - type = "main", - start = 0, - finish = 20000, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 10010, - range = 10010, - parent = "", - locPos = 0, - value = { - type = "table", - start = 10, - finish = 10010, - parent = "", - [1] = { - type = "tablefield", - start = 10004, - finish = 10005, - range = 10009, - parent = "", - node = "", - field = { - type = "field", - start = 10004, - finish = 10005, - parent = "", - [1] = "a", - }, - value = { - type = "integer", - start = 10008, - finish = 10009, - parent = "", - [1] = 1, - }, - }, - }, - [1] = "t", - }, -} - -CHECK [[ -local t = function f() end -]] -{ - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 26, - range = 26, - parent = "", - locPos = 0, - value = { - type = "function", - start = 10, - bstart = 22, - finish = 26, - keyword = { - [1] = 10, - [2] = 18, - [3] = 23, - [4] = 26, - }, - parent = "", - args = { - type = "funcargs", - start = 20, - finish = 22, - parent = "", - }, - name = { - type = "getglobal", - start = 19, - finish = 20, - parent = "", - node = "", - [1] = "f", - }, - }, - [1] = "t", - }, -} - -CHECK [[ -function F() - in -end -]] -{ - type = "main", - start = 0, - finish = 30000, - locals = "", - [1] = { - type = "setglobal", - start = 9, - vstart = 0, - finish = 10, - range = 20003, - parent = "", - node = "", - value = { - type = "function", - start = 0, - bstart = 12, - finish = 20003, - keyword = { - [1] = 0, - [2] = 8, - [3] = 20000, - [4] = 20003, - }, - parent = "", - args = { - type = "funcargs", - start = 10, - finish = 12, - parent = "", - }, - }, - [1] = "F", - }, -} - -CHECK [[ -if true then - 1 -end -]] -{ - type = "main", - start = 0, - finish = 30000, - locals = "", - [1] = { - type = "if", - start = 0, - finish = 20003, - parent = "", - [1] = { - type = "ifblock", - start = 0, - bstart = 12, - finish = 20000, - keyword = { - [1] = 0, - [2] = 2, - [3] = 8, - [4] = 12, - }, - parent = "", - filter = { - type = "boolean", - start = 3, - finish = 7, - parent = "", - [1] = true, - }, - [1] = { - type = "integer", - start = 10004, - finish = 10005, - parent = "", - [1] = 1, - }, - }, - }, -} -CHECK [[ -local -local x = 1 -]] -{ - type = "main", - start = 0, - finish = 20000, - locals = "", - [1] = { - type = "local", - start = 10006, - finish = 10007, - effect = 10011, - range = 10011, - parent = "", - locPos = 10000, - value = { - type = "integer", - start = 10010, - finish = 10011, - parent = "", - [1] = 1, - }, - [1] = "x", - }, -} -CHECK [[ -return function () - local function fff(xxx) - for f in xx - end -end -]] -{ - type = "main", - start = 0, - finish = 50000, - locals = "", - returns = "", - [1] = { - type = "return", - start = 0, - finish = 40003, - parent = "", - [1] = { - type = "function", - start = 7, - bstart = 18, - finish = 40003, - keyword = { - [1] = 7, - [2] = 15, - }, - parent = "", - args = { - type = "funcargs", - start = 16, - finish = 18, - parent = "", - }, - locals = "", - [1] = { - type = "local", - start = 10019, - vstart = 10010, - finish = 10022, - effect = 10022, - range = 40003, - parent = "", - locPos = 10004, - value = { - type = "function", - start = 10010, - bstart = 10027, - finish = 40003, - keyword = { - [1] = 10010, - [2] = 10018, - [3] = 40000, - [4] = 40003, - }, - parent = "", - args = { - type = "funcargs", - start = 10022, - finish = 10027, - parent = "", - [1] = { - type = "local", - start = 10023, - finish = 10026, - effect = 10026, - parent = "", - [1] = "xxx", - }, - }, - locals = "", - [1] = { - type = "in", - start = 20008, - bstart = 20019, - finish = 30007, - keyword = { - [1] = 20008, - [2] = 20011, - [3] = 20014, - [4] = 20016, - [5] = 30004, - [6] = 30007, - }, - parent = "", - keys = { - type = "list", - start = 20012, - finish = 20013, - range = 20016, - parent = "", - [1] = { - type = "local", - start = 20012, - finish = 20013, - effect = 20019, - parent = "", - [1] = "f", - }, - }, - exps = { - type = "list", - start = 20017, - finish = 20019, - parent = "", - [1] = { - type = "getglobal", - start = 20017, - finish = 20019, - parent = "", - node = "", - [1] = "xx", - }, - }, - locals = "", - }, - }, - [1] = "fff", - }, - }, - }, -} diff --git a/test/ast.old/Exp.lua b/test/ast.old/Exp.lua deleted file mode 100644 index 8ba4a4a..0000000 --- a/test/ast.old/Exp.lua +++ /dev/null @@ -1,1582 +0,0 @@ -CHECK'nil' -{ - type = "nil", - start = 0, - finish = 3, -} -CHECK'a' -{ - type = "getglobal", - start = 0, - finish = 1, - [1] = "a", -} -CHECK'a.b' -{ - type = "getfield", - start = 0, - finish = 3, - node = "", - dot = { - type = ".", - start = 1, - finish = 2, - }, - field = { - type = "field", - start = 2, - finish = 3, - parent = "", - [1] = "b", - }, -} -CHECK'a.b.c' -{ - type = "getfield", - start = 0, - finish = 5, - node = "", - dot = { - type = ".", - start = 3, - finish = 4, - }, - field = { - type = "field", - start = 4, - finish = 5, - parent = "", - [1] = "c", - }, -} -CHECK'func()' -{ - type = "call", - start = 0, - finish = 6, - node = "", -} -CHECK'a.b.c()' -{ - type = "call", - start = 0, - finish = 7, - node = "", -} -CHECK'1 or 2' -{ - type = "binary", - start = 0, - finish = 6, - op = { - type = "or", - start = 2, - finish = 4, - }, - [1] = { - type = "integer", - start = 0, - finish = 1, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 2, - }, -} -CHECK'1 < 2' -{ - type = "binary", - start = 0, - finish = 5, - op = { - type = "<", - start = 2, - finish = 3, - }, - [1] = { - type = "integer", - start = 0, - finish = 1, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 4, - finish = 5, - parent = "", - [1] = 2, - }, -} -CHECK'- 1' -{ - type = "integer", - start = 0, - finish = 3, - [1] = -1, -} -CHECK'not not true' -{ - type = "unary", - start = 0, - finish = 12, - op = { - type = "not", - start = 0, - finish = 3, - }, - [1] = { - type = "unary", - start = 4, - finish = 12, - parent = "", - op = { - type = "not", - start = 4, - finish = 7, - }, - [1] = { - type = "boolean", - start = 8, - finish = 12, - parent = "", - [1] = true, - }, - }, -} -CHECK'1 ^ 2' -{ - type = "binary", - start = 0, - finish = 5, - op = { - type = "^", - start = 2, - finish = 3, - }, - [1] = { - type = "integer", - start = 0, - finish = 1, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 4, - finish = 5, - parent = "", - [1] = 2, - }, -} -CHECK'1 ^ -2' -{ - type = "binary", - start = 0, - finish = 6, - op = { - type = "^", - start = 2, - finish = 3, - }, - [1] = { - type = "integer", - start = 0, - finish = 1, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 4, - finish = 6, - parent = "", - [1] = -2, - }, -} -CHECK'-1 ^ 2' -{ - type = "unary", - start = 0, - finish = 6, - op = { - type = "-", - start = 0, - finish = 1, - }, - [1] = { - type = "binary", - start = 1, - finish = 6, - parent = "", - op = { - type = "^", - start = 3, - finish = 4, - }, - [1] = { - type = 'integer', - start = 1, - finish = 2, - parent = "", - [1] = 1, - }, - [2] = { - type = 'integer', - start = 5, - finish = 6, - parent = "", - [1] = 2, - }, - }, -} -CHECK'...' -{ - type = "varargs", - start = 0, - finish = 3, -} -CHECK'1 + 2 + 3' -{ - type = "binary", - start = 0, - finish = 9, - op = { - type = "+", - start = 6, - finish = 7, - }, - [1] = { - type = "binary", - start = 0, - finish = 5, - parent = "", - op = { - type = "+", - start = 2, - finish = 3, - }, - [1] = { - type = "integer", - start = 0, - finish = 1, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 4, - finish = 5, - parent = "", - [1] = 2, - }, - }, - [2] = { - type = "integer", - start = 8, - finish = 9, - parent = "", - [1] = 3, - }, -} -CHECK'1 + 2 * 3' -{ - type = "binary", - start = 0, - finish = 9, - op = { - type = "+", - start = 2, - finish = 3, - }, - [1] = { - type = "integer", - start = 0, - finish = 1, - parent = "", - [1] = 1, - }, - [2] = { - type = "binary", - start = 4, - finish = 9, - parent = "", - op = { - type = "*", - start = 6, - finish = 7, - }, - [1] = { - type = "integer", - start = 4, - finish = 5, - parent = "", - [1] = 2, - }, - [2] = { - type = "integer", - start = 8, - finish = 9, - parent = "", - [1] = 3, - }, - }, -} -CHECK'- 1 + 2 * 3' -{ - type = "binary", - start = 0, - finish = 11, - op = { - type = "+", - start = 4, - finish = 5, - }, - [1] = { - type = "integer", - start = 0, - finish = 3, - parent = "", - [1] = -1, - }, - [2] = { - type = "binary", - start = 6, - finish = 11, - parent = "", - op = { - type = "*", - start = 8, - finish = 9, - }, - [1] = { - type = "integer", - start = 6, - finish = 7, - parent = "", - [1] = 2, - }, - [2] = { - type = "integer", - start = 10, - finish = 11, - parent = "", - [1] = 3, - }, - }, -} -CHECK'-1 + 2 * 3' -{ - type = "binary", - start = 0, - finish = 10, - op = { - type = "+", - start = 3, - finish = 4, - }, - [1] = { - type = "integer", - start = 0, - finish = 2, - parent = "", - [1] = -1, - }, - [2] = { - type = "binary", - start = 5, - finish = 10, - parent = "", - op = { - type = "*", - start = 7, - finish = 8, - }, - [1] = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 2, - }, - [2] = { - type = "integer", - start = 9, - finish = 10, - parent = "", - [1] = 3, - }, - }, -} -CHECK"x and y == 'unary' and z" -{ - type = "binary", - start = 0, - finish = 24, - op = { - type = "and", - start = 19, - finish = 22, - }, - [1] = { - type = "binary", - start = 0, - finish = 18, - parent = "", - op = { - type = "and", - start = 2, - finish = 5, - }, - [1] = { - type = "getglobal", - start = 0, - finish = 1, - parent = "", - [1] = "x", - }, - [2] = { - type = "binary", - start = 6, - finish = 18, - parent = "", - op = { - type = "==", - start = 8, - finish = 10, - }, - [1] = { - type = "getglobal", - start = 6, - finish = 7, - parent = "", - [1] = "y", - }, - [2] = { - type = "string", - start = 11, - finish = 18, - parent = "", - [1] = "unary", - [2] = "'", - }, - }, - }, - [2] = { - type = "getglobal", - start = 23, - finish = 24, - parent = "", - [1] = "z", - }, -} -CHECK"x and y or '' .. z" -{ - type = "binary", - start = 0, - finish = 18, - op = { - type = "or", - start = 8, - finish = 10, - }, - [1] = { - type = "binary", - start = 0, - finish = 7, - parent = "", - op = { - type = "and", - start = 2, - finish = 5, - }, - [1] = { - type = "getglobal", - start = 0, - finish = 1, - parent = "", - [1] = "x", - }, - [2] = { - type = "getglobal", - start = 6, - finish = 7, - parent = "", - [1] = "y", - }, - }, - [2] = { - type = "binary", - start = 11, - finish = 18, - parent = "", - op = { - type = "..", - start = 14, - finish = 16, - }, - [1] = { - type = "string", - start = 11, - finish = 13, - parent = "", - [1] = "", - [2] = "'", - }, - [2] = { - type = "getglobal", - start = 17, - finish = 18, - parent = "", - [1] = "z", - }, - }, -} --- 幂运算从右向左连接 -CHECK'1 ^ 2 ^ 3' -{ - type = "binary", - start = 0, - finish = 9, - op = { - type = "^", - start = 2, - finish = 3, - }, - [1] = { - type = "integer", - start = 0, - finish = 1, - parent = "", - [1] = 1, - }, - [2] = { - type = "binary", - start = 4, - finish = 9, - parent = "", - op = { - type = "^", - start = 6, - finish = 7, - }, - [1] = { - type = "integer", - start = 4, - finish = 5, - parent = "", - [1] = 2, - }, - [2] = { - type = "integer", - start = 8, - finish = 9, - parent = "", - [1] = 3, - }, - }, -} --- 连接运算从右向左连接 -CHECK'1 .. 2 .. 3' -{ - type = "binary", - start = 0, - finish = 11, - op = { - type = "..", - start = 2, - finish = 4, - }, - [1] = { - type = "integer", - start = 0, - finish = 1, - parent = "", - [1] = 1, - }, - [2] = { - type = "binary", - start = 5, - finish = 11, - parent = "", - op = { - type = "..", - start = 7, - finish = 9, - }, - [1] = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 2, - }, - [2] = { - type = "integer", - start = 10, - finish = 11, - parent = "", - [1] = 3, - }, - }, -} -CHECK'1 + - - - - - - - 1' -{ - type = "binary", - start = 0, - finish = 19, - op = { - type = "+", - start = 2, - finish = 3, - }, - [1] = { - type = "integer", - start = 0, - finish = 1, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 4, - finish = 19, - parent = "", - [1] = -1, - }, -} -CHECK'(1)' -{ - type = "paren", - start = 0, - finish = 3, - exp = { - type = "integer", - start = 1, - finish = 2, - parent = "", - [1] = 1, - }, -} -CHECK'(1 + 2)' -{ - type = "paren", - start = 0, - finish = 7, - exp = { - type = "binary", - start = 1, - finish = 6, - parent = "", - op = { - type = "+", - start = 3, - finish = 4, - }, - [1] = { - type = "integer", - start = 1, - finish = 2, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 2, - }, - }, -} -CHECK'func(1)' -{ - type = "call", - start = 0, - finish = 7, - node = "", - args = { - type = "callargs", - start = 4, - finish = 7, - parent = "", - [1] = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 1, - }, - }, -} -CHECK'func(1, 2)' -{ - type = "call", - start = 0, - finish = 10, - node = "", - args = { - type = "callargs", - start = 4, - finish = 10, - parent = "", - [1] = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 1, - }, - [2] = { - type = "integer", - start = 8, - finish = 9, - parent = "", - [1] = 2, - }, - }, -} -CHECK'func(...)' -{ - type = "call", - start = 0, - finish = 9, - node = "", - args = { - type = "callargs", - start = 4, - finish = 9, - parent = "", - [1] = { - type = "varargs", - start = 5, - finish = 8, - parent = "", - }, - }, -} -CHECK'func(1, ...)' -{ - type = "call", - start = 0, - finish = 12, - node = "", - args = { - type = "callargs", - start = 4, - finish = 12, - parent = "", - [1] = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 1, - }, - [2] = { - type = "varargs", - start = 8, - finish = 11, - parent = "", - }, - }, -} -CHECK'func ""' -{ - type = "call", - start = 0, - finish = 7, - node = "", - args = { - type = "callargs", - start = 5, - finish = 7, - parent = "", - [1] = { - type = "string", - start = 5, - finish = 7, - parent = "", - [1] = "", - [2] = "\"", - }, - }, -} -CHECK'func {}' -{ - type = "call", - start = 0, - finish = 7, - node = "", - args = { - type = "callargs", - start = 5, - finish = 7, - parent = "", - [1] = { - type = "table", - start = 5, - finish = 7, - parent = "", - }, - }, -} -CHECK'table[1]' -{ - type = "getindex", - start = 0, - finish = 8, - node = "", - index = { - type = "integer", - start = 6, - finish = 7, - parent = "", - [1] = 1, - }, -} -CHECK'table[[1]]' -{ - type = "call", - start = 0, - finish = 10, - node = "", - args = { - type = "callargs", - start = 5, - finish = 10, - parent = "", - [1] = { - type = "string", - start = 5, - finish = 10, - parent = "", - [1] = '1', - [2] = '[[' - }, - }, -} -CHECK'get_point().x' -{ - type = "getfield", - start = 0, - finish = 13, - node = "", - dot = { - type = ".", - start = 11, - finish = 12, - }, - field = { - type = "field", - start = 12, - finish = 13, - parent = "", - [1] = "x", - }, -} -CHECK'obj:remove()' -{ - type = "call", - start = 0, - finish = 12, - node = "", - args = { - type = "callargs", - start = 0, - finish = 12, - parent = "", - [1] = { - type = "self", - start = 3, - finish = 4, - parent = "", - [1] = "self", - }, - }, -} -CHECK'(...)[1]' -{ - type = "getindex", - start = 0, - finish = 8, - node = "", - index = { - type = "integer", - start = 6, - finish = 7, - parent = "", - [1] = 1, - }, -} -CHECK'function () end' -{ - type = "function", - start = 0, - bstart = 11, - finish = 15, - keyword = { - [1] = 0, - [2] = 8, - [3] = 12, - [4] = 15, - }, - args = { - type = "funcargs", - start = 9, - finish = 11, - parent = "", - }, -} -CHECK'function (...) end' -{ - type = "function", - start = 0, - bstart = 14, - finish = 18, - keyword = { - [1] = 0, - [2] = 8, - [3] = 15, - [4] = 18, - }, - vararg = "", - args = { - type = "funcargs", - start = 9, - finish = 14, - parent = "", - [1] = { - type = "...", - start = 10, - finish = 13, - parent = "", - [1] = "...", - }, - }, -} -CHECK'function (a, ...) end' -{ - type = "function", - start = 0, - bstart = 17, - finish = 21, - keyword = { - [1] = 0, - [2] = 8, - [3] = 18, - [4] = 21, - }, - vararg = "", - args = { - type = "funcargs", - start = 9, - finish = 17, - parent = "", - [1] = { - type = "local", - start = 10, - finish = 11, - effect = 11, - parent = "", - [1] = "a", - }, - [2] = { - type = "...", - start = 13, - finish = 16, - parent = "", - [1] = "...", - }, - }, - locals = "", -} -CHECK'{}' -{ - type = "table", - start = 0, - finish = 2, -} -CHECK'{...}' -{ - type = "table", - start = 0, - finish = 5, - [1] = { - type = "varargs", - start = 1, - finish = 4, - parent = "", - }, -} -CHECK'{1, 2, 3}' -{ - type = "table", - start = 0, - finish = 9, - [1] = { - type = "tableexp", - start = 1, - finish = 2, - tindex = 1, - parent = "", - value = { - type = "integer", - start = 1, - finish = 2, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "tableexp", - start = 4, - finish = 5, - tindex = 2, - parent = "", - value = { - type = "integer", - start = 4, - finish = 5, - parent = "", - [1] = 2, - }, - }, - [3] = { - type = "tableexp", - start = 7, - finish = 8, - tindex = 3, - parent = "", - value = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 3, - }, - }, -} -CHECK'{x = 1, y = 2}' -{ - type = "table", - start = 0, - finish = 14, - [1] = { - type = "tablefield", - start = 1, - finish = 2, - range = 6, - parent = "", - node = "", - field = { - type = "field", - start = 1, - finish = 2, - parent = "", - [1] = "x", - }, - value = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "tablefield", - start = 8, - finish = 9, - range = 13, - parent = "", - node = "", - field = { - type = "field", - start = 8, - finish = 9, - parent = "", - [1] = "y", - }, - value = { - type = "integer", - start = 12, - finish = 13, - parent = "", - [1] = 2, - }, - }, -} -CHECK'{["x"] = 1, ["y"] = 2}' -{ - type = "table", - start = 0, - finish = 22, - [1] = { - type = "tableindex", - start = 1, - finish = 6, - range = 10, - parent = "", - node = "", - index = { - type = "string", - start = 2, - finish = 5, - parent = "", - [1] = "x", - [2] = "\"", - }, - value = { - type = "integer", - start = 9, - finish = 10, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "tableindex", - start = 12, - finish = 17, - range = 21, - parent = "", - node = "", - index = { - type = "string", - start = 13, - finish = 16, - parent = "", - [1] = "y", - [2] = "\"", - }, - value = { - type = "integer", - start = 20, - finish = 21, - parent = "", - [1] = 2, - }, - }, -} -CHECK'{[x] = 1, [y] = 2}' -{ - type = "table", - start = 0, - finish = 18, - [1] = { - type = "tableindex", - start = 1, - finish = 4, - range = 8, - parent = "", - node = "", - index = { - type = "getglobal", - start = 2, - finish = 3, - parent = "", - [1] = "x", - }, - value = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "tableindex", - start = 10, - finish = 13, - range = 17, - parent = "", - node = "", - index = { - type = "getglobal", - start = 11, - finish = 12, - parent = "", - [1] = "y", - }, - value = { - type = "integer", - start = 16, - finish = 17, - parent = "", - [1] = 2, - }, - }, -} -CHECK'{x = 1, y = 2, 3}' -{ - type = "table", - start = 0, - finish = 17, - [1] = { - type = "tablefield", - start = 1, - finish = 2, - range = 6, - parent = "", - node = "", - field = { - type = "field", - start = 1, - finish = 2, - parent = "", - [1] = "x", - }, - value = { - type = "integer", - start = 5, - finish = 6, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "tablefield", - start = 8, - finish = 9, - range = 13, - parent = "", - node = "", - field = { - type = "field", - start = 8, - finish = 9, - parent = "", - [1] = "y", - }, - value = { - type = "integer", - start = 12, - finish = 13, - parent = "", - [1] = 2, - }, - }, - [3] = { - type = "tableexp", - start = 15, - finish = 16, - tindex = 1, - parent = "", - value = { - type = "integer", - start = 15, - finish = 16, - parent = "", - [1] = 3, - }, - }, -} -CHECK'{{}}' -{ - type = "table", - start = 0, - finish = 4, - [1] = { - type = "tableexp", - start = 1, - finish = 3, - tindex = 1, - parent = "", - value = { - type = "table", - start = 1, - finish = 3, - parent = "", - }, - }, -} -CHECK'{ a = { b = { c = {} } } }' -{ - type = "table", - start = 0, - finish = 26, - [1] = { - type = "tablefield", - start = 2, - finish = 3, - range = 24, - parent = "", - node = "", - field = { - type = "field", - start = 2, - finish = 3, - parent = "", - [1] = "a", - }, - value = { - type = "table", - start = 6, - finish = 24, - parent = "", - [1] = { - type = "tablefield", - start = 8, - finish = 9, - range = 22, - parent = "", - node = "", - field = { - type = "field", - start = 8, - finish = 9, - parent = "", - [1] = "b", - }, - value = { - type = "table", - start = 12, - finish = 22, - parent = "", - [1] = { - type = "tablefield", - start = 14, - finish = 15, - range = 20, - parent = "", - node = "", - field = { - type = "field", - start = 14, - finish = 15, - parent = "", - [1] = "c", - }, - value = { - type = "table", - start = 18, - finish = 20, - parent = "", - }, - }, - }, - }, - }, - }, -} -CHECK'{{}, {}, {{}, {}}}' -{ - type = "table", - start = 0, - finish = 18, - [1] = { - type = "tableexp", - start = 1, - finish = 3, - tindex = 1, - parent = "", - value = { - type = "table", - start = 1, - finish = 3, - parent = "", - }, - }, - [2] = { - type = "tableexp", - start = 5, - finish = 7, - tindex = 2, - parent = "", - value = { - type = "table", - start = 5, - finish = 7, - parent = "", - }, - }, - [3] = { - type = "tableexp", - start = 9, - finish = 17, - tindex = 3, - parent = "", - value = { - type = "table", - start = 9, - finish = 17, - parent = "", - [1] = { - type = "tableexp", - start = 10, - finish = 12, - tindex = 1, - parent = "", - value = { - type = "table", - start = 10, - finish = 12, - parent = "", - }, - }, - [2] = { - type = "tableexp", - start = 14, - finish = 16, - tindex = 2, - parent = "", - value = { - type = "table", - start = 14, - finish = 16, - parent = "", - }, - }, - }, - }, -} -CHECK'{1, 2, 3,}' -{ - type = "table", - start = 0, - finish = 10, - [1] = { - type = "tableexp", - start = 1, - finish = 2, - tindex = 1, - parent = "", - value = { - type = "integer", - start = 1, - finish = 2, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "tableexp", - start = 4, - finish = 5, - tindex = 2, - parent = "", - value = { - type = "integer", - start = 4, - finish = 5, - parent = "", - [1] = 2, - }, - }, - [3] = { - type = "tableexp", - start = 7, - finish = 8, - tindex = 3, - parent = "", - value = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 3, - }, - }, -} - -CHECK 'notify' -{ - type = "getglobal", - start = 0, - finish = 6, - [1] = "notify", -} - -CHECK 'a ^ - b' -{ - type = "binary", - start = 0, - finish = 7, - op = { - type = "^", - start = 2, - finish = 3, - }, - [1] = { - type = "getglobal", - start = 0, - finish = 1, - parent = "", - [1] = "a", - }, - [2] = { - type = "unary", - start = 4, - finish = 7, - parent = "", - op = { - type = "-", - start = 4, - finish = 5, - }, - [1] = { - type = "getglobal", - start = 6, - finish = 7, - parent = "", - [1] = "b", - }, - }, -} - -CHECK [=[ -{ -[[]] -}]=] -{ - type = "table", - start = 0, - finish = 20001, - [1] = { - type = "tableexp", - start = 10000, - finish = 10004, - tindex = 1, - parent = "", - value = { - type = "string", - start = 10000, - finish = 10004, - parent = "", - [1] = "", - [2] = "[[", - }, - }, -} - -CHECK [[ -{ - [xxx] -} -]] -{ - type = "table", - start = 0, - finish = 20001, - [1] = { - type = "tableindex", - start = 10004, - finish = 10009, - parent = "", - node = "", - index = { - type = "getglobal", - start = 10005, - finish = 10008, - parent = "", - [1] = "xxx", - }, - }, -} diff --git a/test/ast.old/Lua.lua b/test/ast.old/Lua.lua deleted file mode 100644 index e171b13..0000000 --- a/test/ast.old/Lua.lua +++ /dev/null @@ -1,1379 +0,0 @@ -CHECK'' -{ - type = "main", - start = 0, - finish = 0, - locals = "", -} - -CHECK';;;' -{ - type = "main", - start = 0, - finish = 3, - locals = "", -} - -CHECK';;;x = 1' -{ - type = "main", - start = 0, - finish = 8, - locals = "", - [1] = { - type = "setglobal", - start = 3, - finish = 4, - range = 8, - parent = "", - node = "", - value = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 1, - }, - [1] = "x", - }, -} -CHECK'x, y, z = 1, 2, 3' -{ - type = "main", - start = 0, - finish = 17, - locals = "", - [1] = { - type = "setglobal", - start = 0, - finish = 1, - range = 11, - parent = "", - node = "", - value = { - type = "integer", - start = 10, - finish = 11, - parent = "", - [1] = 1, - }, - [1] = "x", - }, - [2] = { - type = "setglobal", - start = 3, - finish = 4, - range = 14, - parent = "", - node = "", - value = { - type = "integer", - start = 13, - finish = 14, - parent = "", - [1] = 2, - }, - [1] = "y", - }, - [3] = { - type = "setglobal", - start = 6, - finish = 7, - range = 17, - parent = "", - node = "", - value = { - type = "integer", - start = 16, - finish = 17, - parent = "", - [1] = 3, - }, - [1] = "z", - }, -} -CHECK'local x, y, z' -{ - type = "main", - start = 0, - finish = 13, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 13, - parent = "", - locPos = 0, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 13, - parent = "", - [1] = "y", - }, - [3] = { - type = "local", - start = 12, - finish = 13, - effect = 13, - parent = "", - [1] = "z", - }, -} -CHECK'local x, y, z = 1, 2, 3' -{ - type = "main", - start = 0, - finish = 23, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 23, - range = 17, - parent = "", - locPos = 0, - value = { - type = "integer", - start = 16, - finish = 17, - parent = "", - [1] = 1, - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 23, - range = 20, - parent = "", - value = { - type = "integer", - start = 19, - finish = 20, - parent = "", - [1] = 2, - }, - [1] = "y", - }, - [3] = { - type = "local", - start = 12, - finish = 13, - effect = 23, - range = 23, - parent = "", - value = { - type = "integer", - start = 22, - finish = 23, - parent = "", - [1] = 3, - }, - [1] = "z", - }, -} -CHECK'local x, y = y, x' -{ - type = "main", - start = 0, - finish = 17, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 17, - range = 14, - parent = "", - locPos = 0, - value = { - type = "getglobal", - start = 13, - finish = 14, - parent = "", - node = "", - [1] = "y", - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 17, - range = 17, - parent = "", - value = { - type = "getglobal", - start = 16, - finish = 17, - parent = "", - node = "", - [1] = "x", - }, - [1] = "y", - }, -} -CHECK'local x, y = f()' -{ - type = "main", - start = 0, - finish = 16, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 16, - range = 16, - parent = "", - locPos = 0, - value = { - type = "select", - start = 13, - finish = 16, - parent = "", - vararg = "", - sindex = 1, - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 16, - range = 16, - parent = "", - value = { - type = "select", - start = 13, - finish = 16, - parent = "", - vararg = "", - sindex = 2, - }, - [1] = "y", - }, -} -CHECK'local x, y = (f())' -{ - type = "main", - start = 0, - finish = 18, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 18, - range = 18, - parent = "", - locPos = 0, - value = { - type = "paren", - start = 13, - finish = 18, - parent = "", - exp = { - type = "call", - start = 14, - finish = 17, - parent = "", - node = "", - }, - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 18, - parent = "", - [1] = "y", - }, -} -CHECK'local x, y = f(), nil' -{ - type = "main", - start = 0, - finish = 21, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 21, - range = 16, - parent = "", - locPos = 0, - value = { - type = "select", - start = 13, - finish = 16, - parent = "", - vararg = "", - sindex = 1, - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 21, - range = 21, - parent = "", - value = { - type = "nil", - start = 18, - finish = 21, - parent = "", - }, - [1] = "y", - }, -} -CHECK'local x, y = ...' -{ - type = "main", - start = 0, - finish = 16, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 16, - range = 16, - parent = "", - locPos = 0, - value = { - type = "select", - start = 13, - finish = 16, - parent = "", - vararg = "", - sindex = 1, - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 16, - range = 16, - parent = "", - value = { - type = "select", - start = 13, - finish = 16, - parent = "", - vararg = "", - sindex = 2, - }, - [1] = "y", - }, -} -CHECK'local x, y = (...)' -{ - type = "main", - start = 0, - finish = 18, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 18, - range = 18, - parent = "", - locPos = 0, - value = { - type = "paren", - start = 13, - finish = 18, - parent = "", - exp = { - type = "varargs", - start = 14, - finish = 17, - parent = "", - }, - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 18, - parent = "", - [1] = "y", - }, -} -CHECK'local x, y = ..., nil' -{ - type = "main", - start = 0, - finish = 21, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 21, - range = 16, - parent = "", - locPos = 0, - value = { - type = "select", - start = 13, - finish = 16, - parent = "", - vararg = "", - sindex = 1, - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 21, - range = 21, - parent = "", - value = { - type = "nil", - start = 18, - finish = 21, - parent = "", - }, - [1] = "y", - }, -} -CHECK'local x , y = 1' -{ - type = "main", - start = 0, - finish = 30, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 30, - range = 30, - parent = "", - locPos = 0, - value = { - type = "integer", - start = 29, - finish = 30, - parent = "", - [1] = 1, - }, - attrs = { - type = "localattrs", - parent = "", - [1] = { - type = "localattr", - start = 8, - finish = 15, - parent = "", - [1] = "const", - }, - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 17, - finish = 18, - effect = 30, - parent = "", - attrs = { - type = "localattrs", - parent = "", - [1] = { - type = "localattr", - start = 19, - finish = 26, - parent = "", - [1] = "close", - }, - }, - [1] = "y", - }, -} -CHECK[[ -x = 1 -y = 2 -]] -{ - type = "main", - start = 0, - finish = 20000, - locals = "", - [1] = { - type = "setglobal", - start = 0, - finish = 1, - range = 5, - parent = "", - node = "", - value = { - type = "integer", - start = 4, - finish = 5, - parent = "", - [1] = 1, - }, - [1] = "x", - }, - [2] = { - type = "setglobal", - start = 10000, - finish = 10001, - range = 10005, - parent = "", - node = "", - value = { - type = "integer", - start = 10004, - finish = 10005, - parent = "", - [1] = 2, - }, - [1] = "y", - }, -} - -CHECK[[ -x, y = 1, 2 -]] -{ - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "setglobal", - start = 0, - finish = 1, - range = 8, - parent = "", - node = "", - value = { - type = "integer", - start = 7, - finish = 8, - parent = "", - [1] = 1, - }, - [1] = "x", - }, - [2] = { - type = "setglobal", - start = 3, - finish = 4, - range = 11, - parent = "", - node = "", - value = { - type = "integer", - start = 10, - finish = 11, - parent = "", - [1] = 2, - }, - [1] = "y", - }, -} -CHECK[[ -local function a() - return -end]] -{ - type = "main", - start = 0, - finish = 20003, - locals = "", - [1] = { - type = "local", - start = 15, - vstart = 6, - finish = 16, - effect = 16, - range = 20003, - parent = "", - locPos = 0, - value = { - type = "function", - start = 6, - bstart = 18, - finish = 20003, - keyword = { - [1] = 6, - [2] = 14, - [3] = 20000, - [4] = 20003, - }, - parent = "", - args = { - type = "funcargs", - start = 16, - finish = 18, - parent = "", - }, - returns = "", - hasReturn = true, - [1] = { - type = "return", - start = 10004, - finish = 10010, - parent = "", - }, - }, - [1] = "a", - }, -} -CHECK[[ -local function f() - return f() -end]] -{ - type = "main", - start = 0, - finish = 20003, - locals = "", - [1] = { - type = "local", - start = 15, - vstart = 6, - finish = 16, - effect = 16, - range = 20003, - parent = "", - locPos = 0, - value = { - type = "function", - start = 6, - bstart = 18, - finish = 20003, - keyword = { - [1] = 6, - [2] = 14, - [3] = 20000, - [4] = 20003, - }, - parent = "", - args = { - type = "funcargs", - start = 16, - finish = 18, - parent = "", - }, - returns = "", - hasReturn = true, - [1] = { - type = "return", - start = 10004, - finish = 10014, - parent = "", - [1] = { - type = "call", - start = 10011, - finish = 10014, - parent = "", - node = "", - }, - }, - }, - ref = "", - [1] = "f", - }, -} -CHECK[[ -local function a(b, c) - return -end]] -{ - type = "main", - start = 0, - finish = 20003, - locals = "", - [1] = { - type = "local", - start = 15, - vstart = 6, - finish = 16, - effect = 16, - range = 20003, - parent = "", - locPos = 0, - value = { - type = "function", - start = 6, - bstart = 22, - finish = 20003, - keyword = { - [1] = 6, - [2] = 14, - [3] = 20000, - [4] = 20003, - }, - parent = "", - args = { - type = "funcargs", - start = 16, - finish = 22, - parent = "", - [1] = { - type = "local", - start = 17, - finish = 18, - effect = 18, - parent = "", - [1] = "b", - }, - [2] = { - type = "local", - start = 20, - finish = 21, - effect = 21, - parent = "", - [1] = "c", - }, - }, - locals = "", - returns = "", - hasReturn = true, - [1] = { - type = "return", - start = 10004, - finish = 10010, - parent = "", - }, - }, - [1] = "a", - }, -} - -CHECK[[ -local x, y, z = 1, 2 -local function f() -end -y, z = 3, 4 -]] -{ - type = "main", - start = 0, - finish = 40000, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 20, - range = 17, - parent = "", - locPos = 0, - value = { - type = "integer", - start = 16, - finish = 17, - parent = "", - [1] = 1, - }, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 20, - range = 20, - parent = "", - value = { - type = "integer", - start = 19, - finish = 20, - parent = "", - [1] = 2, - }, - ref = "", - [1] = "y", - }, - [3] = { - type = "local", - start = 12, - finish = 13, - effect = 20, - parent = "", - ref = "", - [1] = "z", - }, - [4] = { - type = "local", - start = 10015, - vstart = 10006, - finish = 10016, - effect = 10016, - range = 20003, - parent = "", - locPos = 10000, - value = { - type = "function", - start = 10006, - bstart = 10018, - finish = 20003, - keyword = { - [1] = 10006, - [2] = 10014, - [3] = 20000, - [4] = 20003, - }, - parent = "", - args = { - type = "funcargs", - start = 10016, - finish = 10018, - parent = "", - }, - }, - [1] = "f", - }, - [5] = { - type = "setlocal", - start = 30000, - finish = 30001, - range = 30008, - parent = "", - node = "", - value = { - type = "integer", - start = 30007, - finish = 30008, - parent = "", - [1] = 3, - }, - [1] = "y", - }, - [6] = { - type = "setlocal", - start = 30003, - finish = 30004, - range = 30011, - parent = "", - node = "", - value = { - type = "integer", - start = 30010, - finish = 30011, - parent = "", - [1] = 4, - }, - [1] = "z", - }, -} -CHECK[[ -local f = require -local z = f -z'xxx' -]] -{ - type = "main", - start = 0, - finish = 30000, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 17, - range = 17, - special = "require", - parent = "", - locPos = 0, - value = { - type = "getglobal", - start = 10, - finish = 17, - special = "require", - parent = "", - node = "", - [1] = "require", - }, - ref = "", - [1] = "f", - }, - [2] = { - type = "local", - start = 10006, - finish = 10007, - effect = 10011, - range = 10011, - special = "require", - parent = "", - locPos = 10000, - value = { - type = "getlocal", - start = 10010, - finish = 10011, - special = "require", - parent = "", - node = "", - [1] = "f", - }, - ref = "", - [1] = "z", - }, - [3] = { - type = "call", - start = 20000, - finish = 20006, - parent = "", - node = "", - args = { - type = "callargs", - start = 20001, - finish = 20006, - parent = "", - [1] = { - type = "string", - start = 20001, - finish = 20006, - parent = "", - [1] = "xxx", - [2] = "'", - }, - }, - }, -} -CHECK[[ -A:B(1):C(2) -]] -{ - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "call", - start = 0, - finish = 11, - parent = "", - node = "", - args = { - type = "callargs", - start = 8, - finish = 11, - parent = "", - [1] = { - type = "self", - start = 6, - finish = 7, - parent = "", - [1] = "self", - }, - [2] = { - type = "integer", - start = 9, - finish = 10, - parent = "", - [1] = 2, - }, - }, - }, -} - -CHECK [[ - -local s = [ [=[111]=] ] -]] -{ - type = "main", - start = 0, - finish = 20000, - locals = "", - [1] = { - type = "local", - start = 10006, - finish = 10007, - effect = 10007, - parent = "", - locPos = 10000, - [1] = "s", - }, - [2] = { - type = "string", - start = 10012, - finish = 10021, - parent = "", - [1] = "111", - [2] = "[=[", - }, -} -CHECK([[ -local x = 1 // 2 -]], { - nonstandardSymbol = { ['//'] = true } -}) -{ - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 11, - range = 11, - parent = "", - locPos = 0, - value = { - type = "integer", - start = 10, - finish = 11, - parent = "", - [1] = 1, - }, - [1] = "x", - }, -} - -CHECK([[ -local x = { - 1, // BAD - 2, // GOOD - 3, // GOOD -} -]], { - nonstandardSymbol = { ['//'] = true } -}) -{ - type = "main", - start = 0, - finish = 50000, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 40001, - range = 40001, - parent = "", - locPos = 0, - value = { - type = "table", - start = 10, - finish = 40001, - parent = "", - [1] = { - type = "tableexp", - start = 10004, - finish = 10005, - tindex = 1, - parent = "", - value = { - type = "integer", - start = 10004, - finish = 10005, - parent = "", - [1] = 1, - }, - }, - [2] = { - type = "tableexp", - start = 20004, - finish = 20005, - tindex = 2, - parent = "", - value = { - type = "integer", - start = 20004, - finish = 20005, - parent = "", - [1] = 2, - }, - }, - [3] = { - type = "tableexp", - start = 30004, - finish = 30005, - tindex = 3, - parent = "", - value = { - type = "integer", - start = 30004, - finish = 30005, - parent = "", - [1] = 3, - }, - }, - }, - [1] = "x", - }, -} - -CHECK [[ -local x -return { - x = 1, -} -]] -{ - type = "main", - start = 0, - finish = 40000, - locals = "", - returns = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 7, - parent = "", - locPos = 0, - [1] = "x", - }, - [2] = { - type = "return", - start = 10000, - finish = 30001, - parent = "", - [1] = { - type = "table", - start = 10007, - finish = 30001, - parent = "", - [1] = { - type = "tablefield", - start = 20004, - finish = 20005, - range = 20009, - parent = "", - node = "", - field = { - type = "field", - start = 20004, - finish = 20005, - parent = "", - [1] = "x", - }, - value = { - type = "integer", - start = 20008, - finish = 20009, - parent = "", - [1] = 1, - }, - }, - }, - }, -} - -CHECK [[ -local x -a = { - x -} -]] -{ - type = "main", - start = 0, - finish = 40000, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 7, - parent = "", - locPos = 0, - ref = "", - [1] = "x", - }, - [2] = { - type = "setglobal", - start = 10000, - finish = 10001, - range = 30001, - parent = "", - node = "", - value = { - type = "table", - start = 10004, - finish = 30001, - parent = "", - [1] = { - type = "tableexp", - start = 20004, - finish = 20005, - tindex = 1, - parent = "", - value = { - type = "getlocal", - start = 20004, - finish = 20005, - parent = "", - node = "", - [1] = "x", - }, - }, - }, - [1] = "a", - }, -} - -CHECK [[ -x, y, z = 1, func() -]] -{ - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "setglobal", - start = 0, - finish = 1, - range = 11, - parent = "", - node = "", - value = { - type = "integer", - start = 10, - finish = 11, - parent = "", - [1] = 1, - }, - [1] = "x", - }, - [2] = { - type = "setglobal", - start = 3, - finish = 4, - range = 19, - parent = "", - node = "", - value = { - type = "select", - start = 13, - finish = 19, - parent = "", - vararg = "", - sindex = 1, - }, - [1] = "y", - }, - [3] = { - type = "setglobal", - start = 6, - finish = 7, - range = 19, - parent = "", - node = "", - value = { - type = "select", - start = 13, - finish = 19, - parent = "", - vararg = "", - sindex = 2, - }, - [1] = "z", - }, -} - -CHECK [[ -local x, y --- comments -]] -{ - type = "main", - start = 0, - finish = 20000, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 7, - effect = 10, - parent = "", - locPos = 0, - [1] = "x", - }, - [2] = { - type = "local", - start = 9, - finish = 10, - effect = 10, - parent = "", - [1] = "y", - }, -} - -CHECK [[ -local _ENV = nil -]] -{ - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "local", - start = 6, - finish = 10, - effect = 16, - range = 16, - parent = "", - locPos = 0, - value = { - type = "nil", - start = 13, - finish = 16, - parent = "", - }, - [1] = "_ENV", - }, -} - -CHECK [[ -_ENV = nil -]] -{ - type = "main", - start = 0, - finish = 10000, - locals = "", - [1] = { - type = "local", - start = 0, - finish = 4, - effect = 10, - range = 10, - parent = "", - node = "", - locPos = 0, - value = { - type = "nil", - start = 7, - finish = 10, - parent = "", - }, - [1] = "_ENV", - }, -} diff --git a/test/ast.old/LuaDoc.lua b/test/ast.old/LuaDoc.lua deleted file mode 100644 index 2b13a58..0000000 --- a/test/ast.old/LuaDoc.lua +++ /dev/null @@ -1,919 +0,0 @@ -LuaDoc [[ ----@class Class -]] -{ - type = "doc", - start = 0, - finish = 10000, - parent = "", - [1] = { - type = "doc.class", - start = 10, - finish = 15, - range = 15, - parent = "", - bindComments = { - }, - calls = { - }, - class = { - type = "doc.class.name", - start = 10, - finish = 15, - parent = "", - [1] = "Class", - }, - fields = { - }, - operators = { - }, - originalComment = "", - }, -} - -LuaDoc [[ ----@class Class : SuperClass -local x = 1 -]] -{ - type = "doc", - start = 0, - finish = 20000, - parent = "", - [1] = { - type = "doc.class", - start = 10, - finish = 28, - range = 28, - parent = "", - bindComments = { - }, - bindSource = "", - calls = { - }, - class = { - type = "doc.class.name", - start = 10, - finish = 15, - parent = "", - [1] = "Class", - }, - extends = { - [1] = { - type = "doc.extends.name", - start = 18, - finish = 28, - parent = "", - [1] = "SuperClass", - }, - }, - fields = { - }, - operators = { - }, - originalComment = "", - }, -} - -LuaDoc [[ ----@type Type -x = 1 -]] -{ - type = "doc", - start = 0, - finish = 20000, - parent = "", - [1] = { - type = "doc.type", - start = 9, - finish = 13, - range = 13, - parent = "", - bindSource = "", - firstFinish = 13, - originalComment = "", - types = { - [1] = { - type = "doc.type.name", - start = 9, - finish = 13, - parent = "", - [1] = "Type", - }, - }, - }, -} - -LuaDoc [[ ----@type Type1|Type2|Type3 -x = 1 -]] -{ - type = "doc", - start = 0, - finish = 20000, - parent = "", - [1] = { - type = "doc.type", - start = 9, - finish = 26, - range = 26, - parent = "", - bindSource = "", - firstFinish = 26, - originalComment = "", - types = { - [1] = { - type = "doc.type.name", - start = 9, - finish = 14, - parent = "", - [1] = "Type1", - }, - [2] = { - type = "doc.type.name", - start = 15, - finish = 20, - parent = "", - [1] = "Type2", - }, - [3] = { - type = "doc.type.name", - start = 21, - finish = 26, - parent = "", - [1] = "Type3", - }, - }, - }, -} - -LuaDoc [[ ----@type x | "'a'" | "'b'" -]] -{ - type = "doc", - start = 0, - finish = 10000, - parent = "", - [1] = { - type = "doc.type", - start = 9, - finish = 26, - range = 26, - parent = "", - firstFinish = 26, - originalComment = "", - types = { - [1] = { - type = "doc.type.name", - start = 9, - finish = 10, - parent = "", - [1] = "x", - }, - [2] = { - type = "doc.type.string", - start = 13, - finish = 18, - parent = "", - [1] = "a", - [2] = "'", - }, - [3] = { - type = "doc.type.string", - start = 21, - finish = 26, - parent = "", - [1] = "b", - [2] = "'", - }, - }, - }, -} - -LuaDoc [[ ----@type "'a'" | "'b'" | "'c'" -]] -{ - type = "doc", - start = 0, - finish = 10000, - parent = "", - [1] = { - type = "doc.type", - start = 9, - finish = 30, - range = 30, - parent = "", - firstFinish = 30, - originalComment = "", - types = { - [1] = { - type = "doc.type.string", - start = 9, - finish = 14, - parent = "", - [1] = "a", - [2] = "'", - }, - [2] = { - type = "doc.type.string", - start = 17, - finish = 22, - parent = "", - [1] = "b", - [2] = "'", - }, - [3] = { - type = "doc.type.string", - start = 25, - finish = 30, - parent = "", - [1] = "c", - [2] = "'", - }, - }, - }, -} - -LuaDoc [[ ----@alias Handler LongType -x = 1 -]] -{ - type = "doc", - start = 0, - finish = 20000, - parent = "", - [1] = { - type = "doc.alias", - start = 10, - finish = 26, - range = 26, - parent = "", - alias = { - type = "doc.alias.name", - start = 10, - finish = 17, - parent = "", - [1] = "Handler", - }, - bindComments = { - }, - extends = { - type = "doc.type", - start = 18, - finish = 26, - parent = "", - firstFinish = 26, - types = { - [1] = { - type = "doc.type.name", - start = 18, - finish = 26, - parent = "", - [1] = "LongType", - }, - }, - }, - originalComment = "", - }, -} - -LuaDoc [[ ----@param a1 t1 -]] -{ - type = "doc", - start = 0, - finish = 10000, - parent = "", - [1] = { - type = "doc.param", - start = 10, - finish = 15, - range = 15, - parent = "", - extends = { - type = "doc.type", - start = 13, - finish = 15, - parent = "", - firstFinish = 15, - types = { - [1] = { - type = "doc.type.name", - start = 13, - finish = 15, - parent = "", - [1] = "t1", - }, - }, - }, - firstFinish = 15, - originalComment = "", - param = { - type = "doc.param.name", - start = 10, - finish = 12, - parent = "", - [1] = "a1", - }, - }, -} - -LuaDoc [[ ----@return Type1|Type2|Type3 -]] -{ - type = "doc", - start = 0, - finish = 10000, - parent = "", - [1] = { - type = "doc.return", - start = 11, - finish = 28, - range = 28, - parent = "", - returns = "", - originalComment = "", - }, -} - -LuaDoc [[ ----@return Type1,Type2,Type3 -]] -{ - type = "doc", - start = 0, - finish = 10000, - parent = "", - [1] = { - type = "doc.return", - start = 11, - finish = 28, - range = 28, - parent = "", - returns = "", - originalComment = "", - }, -} - -LuaDoc [[ ----@field open function -]] -{ - type = "doc", - start = 0, - finish = 10000, - parent = "", - [1] = { - type = "doc.field", - start = 10, - finish = 23, - range = 23, - parent = "", - field = { - type = "doc.field.name", - start = 10, - finish = 14, - parent = "", - [1] = "open", - }, - bindComments = { - }, - extends = { - type = "doc.type", - start = 15, - finish = 23, - parent = "", - firstFinish = 23, - types = { - [1] = { - type = "doc.type.name", - start = 15, - finish = 23, - parent = "", - [1] = "function", - }, - }, - }, - originalComment = "", - }, -} - -LuaDoc [[ ----@field private open function|string -]] -{ - type = "doc", - start = 0, - finish = 10000, - parent = "", - [1] = { - type = "doc.field", - start = 10, - finish = 38, - range = 38, - parent = "", - field = { - type = "doc.field.name", - start = 18, - finish = 22, - parent = "", - [1] = "open", - }, - bindComments = { - }, - extends = { - type = "doc.type", - start = 23, - finish = 38, - parent = "", - firstFinish = 38, - types = { - [1] = { - type = "doc.type.name", - start = 23, - finish = 31, - parent = "", - [1] = "function", - }, - [2] = { - type = "doc.type.name", - start = 32, - finish = 38, - parent = "", - [1] = "string", - }, - }, - }, - originalComment = "", - visible = "private", - }, -} - -LuaDoc [[ ----@generic T -]] -{ - type = "doc", - start = 0, - finish = 10000, - parent = "", - [1] = { - type = "doc.generic", - start = 12, - finish = 13, - range = 13, - parent = "", - generics = { - [1] = { - type = "doc.generic.object", - start = 12, - finish = 13, - parent = "", - generic = { - type = "doc.generic.name", - start = 12, - finish = 13, - parent = "", - [1] = "T", - }, - }, - }, - originalComment = "", - }, -} - -LuaDoc [[ ----@generic T : handle -]] -{ - type = "doc", - start = 0, - finish = 10000, - parent = "", - [1] = { - type = "doc.generic", - start = 12, - finish = 22, - range = 22, - parent = "", - generics = { - [1] = { - type = "doc.generic.object", - start = 12, - finish = 22, - parent = "", - extends = { - type = "doc.type", - start = 16, - finish = 22, - parent = "", - firstFinish = 22, - types = { - [1] = { - type = "doc.type.name", - start = 16, - finish = 22, - parent = "", - [1] = "handle", - }, - }, - }, - generic = { - type = "doc.generic.name", - start = 12, - finish = 13, - parent = "", - [1] = "T", - }, - }, - }, - originalComment = "", - }, -} - -LuaDoc [[ ----@generic T : handle, K : handle -]] -{ - type = "doc", - start = 0, - finish = 10000, - parent = "", - [1] = { - type = "doc.generic", - start = 12, - finish = 34, - range = 34, - parent = "", - generics = { - [1] = { - type = "doc.generic.object", - start = 12, - finish = 22, - parent = "", - extends = { - type = "doc.type", - start = 16, - finish = 22, - parent = "", - firstFinish = 22, - types = { - [1] = { - type = "doc.type.name", - start = 16, - finish = 22, - parent = "", - [1] = "handle", - }, - }, - }, - generic = { - type = "doc.generic.name", - start = 12, - finish = 13, - parent = "", - [1] = "T", - }, - }, - [2] = { - type = "doc.generic.object", - start = 24, - finish = 34, - parent = "", - extends = { - type = "doc.type", - start = 28, - finish = 34, - parent = "", - firstFinish = 34, - types = { - [1] = { - type = "doc.type.name", - start = 28, - finish = 34, - parent = "", - [1] = "handle", - }, - }, - }, - generic = { - type = "doc.generic.name", - start = 24, - finish = 25, - parent = "", - [1] = "K", - }, - }, - }, - originalComment = "", - }, -} - -LuaDoc [[ ----@vararg string -]] -{ - type = "doc", - start = 0, - finish = 10000, - parent = "", - [1] = { - type = "doc.vararg", - start = 11, - finish = 17, - range = 17, - parent = "", - vararg = "", - originalComment = "", - }, -} - -LuaDoc [[ ----@type Type[] -]] -{ - type = "doc", - start = 0, - finish = 10000, - parent = "", - [1] = { - type = "doc.type", - start = 9, - finish = 15, - range = 15, - parent = "", - firstFinish = 15, - originalComment = "", - types = { - [1] = { - type = "doc.type.array", - start = 9, - finish = 15, - parent = "", - node = "", - }, - }, - }, -} - -LuaDoc [[ ----@type table -]] -{ - type = "doc", - start = 0, - finish = 10000, - parent = "", - [1] = { - type = "doc.type", - start = 9, - finish = 26, - range = 26, - parent = "", - firstFinish = 26, - originalComment = "", - types = { - [1] = { - type = "doc.type.sign", - start = 9, - finish = 26, - parent = "", - node = "", - signs = { - [1] = { - type = "doc.type", - start = 15, - finish = 18, - parent = "", - firstFinish = 18, - types = { - [1] = { - type = "doc.type.name", - start = 15, - finish = 18, - parent = "", - [1] = "key", - }, - }, - }, - [2] = { - type = "doc.type", - start = 20, - finish = 25, - parent = "", - firstFinish = 25, - types = { - [1] = { - type = "doc.type.name", - start = 20, - finish = 25, - parent = "", - [1] = "value", - }, - }, - }, - }, - }, - }, - }, -} - -IGNORE_MAP['returns'] = nil -IGNORE_MAP['extends'] = true -LuaDoc [[ ----@type fun(key1:t1, key2:t2):t3 -]] -{ - type = "doc", - start = 0, - finish = 10000, - parent = "", - [1] = { - type = "doc.type", - start = 9, - finish = 33, - range = 33, - parent = "", - firstFinish = 33, - originalComment = "", - types = { - [1] = { - type = "doc.type.function", - start = 9, - finish = 33, - parent = "", - args = { - [1] = { - type = "doc.type.arg", - start = 13, - finish = 20, - parent = "", - extends = "", - name = { - type = "doc.type.arg.name", - start = 13, - finish = 17, - parent = "", - [1] = "key1", - }, - }, - [2] = { - type = "doc.type.arg", - start = 22, - finish = 29, - parent = "", - extends = "", - name = { - type = "doc.type.arg.name", - start = 22, - finish = 26, - parent = "", - [1] = "key2", - }, - }, - }, - returns = { - [1] = { - type = "doc.type", - start = 31, - finish = 33, - parent = "", - firstFinish = 33, - types = { - [1] = { - type = "doc.type.name", - start = 31, - finish = 33, - parent = "", - [1] = "t3", - }, - }, - }, - }, - }, - }, - }, -} - -IGNORE_MAP['returns'] = true -IGNORE_MAP['extends'] = nil -LuaDoc [[ ----@param event string | "'onClosed'" | "'onData'" -]] -{ - type = "doc", - start = 0, - finish = 10000, - parent = "", - [1] = { - type = "doc.param", - start = 10, - finish = 50, - range = 50, - parent = "", - extends = { - type = "doc.type", - start = 16, - finish = 50, - parent = "", - firstFinish = 50, - types = { - [1] = { - type = "doc.type.name", - start = 16, - finish = 22, - parent = "", - [1] = "string", - }, - [2] = { - type = "doc.type.string", - start = 25, - finish = 37, - parent = "", - [1] = "onClosed", - [2] = "'", - }, - [3] = { - type = "doc.type.string", - start = 40, - finish = 50, - parent = "", - [1] = "onData", - [2] = "'", - }, - }, - }, - firstFinish = 50, - originalComment = "", - param = { - type = "doc.param.name", - start = 10, - finish = 15, - parent = "", - [1] = "event", - }, - }, -} - -LuaDoc [[ ----@overload fun(a:number):number -]] -{ - type = "doc", - start = 0, - finish = 10000, - parent = "", - [1] = { - type = "doc.overload", - start = 13, - finish = 33, - range = 33, - parent = "", - originalComment = "", - overload = { - type = "doc.type.function", - start = 13, - finish = 33, - parent = "", - args = { - [1] = { - type = "doc.type.arg", - start = 17, - finish = 25, - parent = "", - extends = { - type = "doc.type", - start = 19, - finish = 25, - parent = "", - firstFinish = 25, - types = { - [1] = { - type = "doc.type.name", - start = 19, - finish = 25, - parent = "", - [1] = "number", - }, - }, - }, - name = { - type = "doc.type.arg.name", - start = 17, - finish = 18, - parent = "", - [1] = "a", - }, - }, - }, - returns = "", - }, - }, -} diff --git a/test/ast.old/Nil.lua b/test/ast.old/Nil.lua deleted file mode 100644 index 70744e2..0000000 --- a/test/ast.old/Nil.lua +++ /dev/null @@ -1,12 +0,0 @@ -CHECK [[nil]] -{ - type = "nil", - start = 0, - finish = 3, -} -CHECK [[ nil]] -{ - type = "nil", - start = 3, - finish = 6, -} diff --git a/test/ast.old/Number.lua b/test/ast.old/Number.lua deleted file mode 100644 index c956850..0000000 --- a/test/ast.old/Number.lua +++ /dev/null @@ -1,84 +0,0 @@ -CHECK'345' -{ - type = "integer", - start = 0, - finish = 3, - [1] = 345, -} -CHECK'345.0' -{ - type = "number", - start = 0, - finish = 5, - [1] = 0x1.59p+8, -} -CHECK'0xff' -{ - type = "integer", - start = 0, - finish = 4, - [1] = 255, -} -CHECK'314.16e-2' -{ - type = "number", - start = 0, - finish = 9, - [1] = 3.1416, -} -CHECK'0.31416E1' -{ - type = "number", - start = 0, - finish = 9, - [1] = 0x1.921ff2e48e8a7p+1, -} -CHECK'.31416E1' -{ - type = "number", - start = 0, - finish = 8, - [1] = 0x1.921ff2e48e8a7p+1, -} -CHECK'34e1' -{ - type = "number", - start = 0, - finish = 4, - [1] = 0x1.54p+8, -} -CHECK'0x0.1E' -{ - type = "number", - start = 0, - finish = 6, - [1] = 0x1.ep-4, -} -CHECK'0xA23p-4' -{ - type = "number", - start = 0, - finish = 8, - [1] = 0x1.446p+7, -} -CHECK'0X1.921FB54442D18P+1' -{ - type = "number", - start = 0, - finish = 20, - [1] = 0x1.921fb54442d18p+1, -} -CHECK'-345' -{ - type = "integer", - start = 0, - finish = 4, - [1] = -345, -} -CHECK'0b110110' -{ - type = "integer", - start = 0, - finish = 8, - [1] = 54, -} diff --git a/test/ast.old/String.lua b/test/ast.old/String.lua deleted file mode 100644 index ac32030..0000000 --- a/test/ast.old/String.lua +++ /dev/null @@ -1,215 +0,0 @@ -CHECK[['123']] -{ - type = "string", - start = 0, - finish = 5, - [1] = "123", - [2] = "'", -} -CHECK[['123\'']] -{ - type = "string", - start = 0, - finish = 7, - escs = { - [1] = 4, - [2] = 6, - [3] = "normal", - }, - [1] = "123'", - [2] = "'", -} -CHECK[['123\z - 345']] -{ - type = "string", - start = 0, - finish = 10008, - escs = { - [1] = 4, - [2] = 6, - [3] = "normal", - }, - [1] = "123345", - [2] = "'", -} - -CHECK[['123\ -345']] -{ - type = "string", - start = 0, - finish = 10004, - escs = { - [1] = 4, - [2] = 5, - [3] = "normal", - }, - [1] = "123\ -345", - [2] = "'", -} -CHECK[===[[[123]]]===] -{ - type = "string", - start = 0, - finish = 7, - [1] = "123", - [2] = "[[", -} -CHECK[===[[[123 -345]]]===] -{ - type = "string", - start = 0, - finish = 10005, - [1] = "123\ -345", - [2] = "[[", -} -CHECK[['alo\n123"']] -{ - type = "string", - start = 0, - finish = 11, - escs = { - [1] = 4, - [2] = 6, - [3] = "normal", - }, - [1] = "alo\ -123\"", - [2] = "'", -} -CHECK[['\97lo\10\04923"']] -{ - type = "string", - start = 0, - finish = 17, - escs = { - [1] = 1, - [2] = 4, - [3] = "byte", - [4] = 6, - [5] = 9, - [6] = "byte", - [7] = 9, - [8] = 13, - [9] = "byte", - }, - [1] = "alo\ -123\"", - [2] = "'", -} -CHECK[['\xff']] -{ - type = "string", - start = 0, - finish = 6, - escs = { - [1] = 1, - [2] = 6, - [3] = "byte", - }, - [1] = "\xff", - [2] = "'", -} -CHECK[['\x1A']] -{ - type = "string", - start = 0, - finish = 6, - escs = { - [1] = 1, - [2] = 6, - [3] = "byte", - }, - [1] = "\26", - [2] = "'", -} -CHECK[['\492']] -{ - type = "string", - start = 0, - finish = 6, - escs = { - [1] = 1, - [2] = 5, - [3] = "byte", - }, - [1] = "", - [2] = "'", -} -CHECK[['\u{3b1}']] -{ - type = "string", - start = 0, - finish = 9, - escs = { - [1] = 1, - [2] = 9, - [3] = "unicode", - }, - [1] = "α", - [2] = "'", -} -CHECK[['\u{0}']] -{ - type = "string", - start = 0, - finish = 7, - escs = { - [1] = 1, - [2] = 7, - [3] = "unicode", - }, - [1] = "\0", - [2] = "'", -} -CHECK[['\u{ffffff}']] -{ - type = "string", - start = 0, - finish = 12, - escs = { - [1] = 1, - [2] = 12, - [3] = "unicode", - }, - [1] = "", - [2] = "'", -} -CHECK[=[[[ -abcdef]]]=] -{ - type = "string", - start = 0, - finish = 10008, - [1] = "abcdef", - [2] = "[[", -} -CHECK[['aaa]] -{ - type = "string", - start = 0, - finish = 4, - [1] = "aaa", - [2] = "'", -} -CHECK[['aaa -]] -{ - type = "string", - start = 0, - finish = 4, - [1] = "aaa", - [2] = "'", -} -CHECK[[`12345`]] -{ - type = "string", - start = 0, - finish = 7, - [1] = "12345", - [2] = "`", -} diff --git a/test/ast.old/init.lua b/test/ast.old/init.lua deleted file mode 100644 index fa4eed8..0000000 --- a/test/ast.old/init.lua +++ /dev/null @@ -1,230 +0,0 @@ -local parser = require 'parser' -local fs = require 'bee.filesystem' -local utility = require 'utility' - -EXISTS = {} - -local function eq(a, b) - local tp1, tp2 = type(a), type(b) - if tp1 ~= tp2 then - return false - end - if a == '' and tp1 == 'table' then - return true - end - if b == '' and tp2 == 'table' then - return true - end - if tp1 == 'table' then - local checked = {} - for k in pairs(a) do - if not eq(a[k], b[k]) then - return false - end - checked[k] = true - end - for k in pairs(b) do - if not checked[k] then - return false - end - end - return true - end - if tp1 == 'number' then - return ('%q'):format(a) == ('%q'):format(b) - end - return a == b -end - ----@type {[string]: integer, [integer]: string} -local sortList = { - 'specials', - 'type', 'start', 'vstart', 'bstart', 'finish', 'effect', 'range', 'tindex', - 'tag', 'special', 'keyword', - 'parent', 'extParent', 'child', - 'filter', - 'vararg', - 'node', 'locPos', - 'op', 'args', - 'loc', 'init', 'max', 'step', 'keys', 'exps', 'call', 'func', - 'dot', 'colon', - 'field', 'index', 'method', - 'exp', 'value', 'vref', - 'attrs', 'escs', - 'locals', 'ref', 'returns', 'breaks', -} -for i, v in ipairs(sortList) do - sortList[v] = i -end -local ignoreList = { - 'specials', 'locals', 'ref', 'node', 'parent', 'extParent', 'returns', 'state', 'mirror', 'next', 'vararg', 'originalComment', 'typeCache', 'eachCache', 'bindSource' -} -local ignoreMap = {} -for i, v in ipairs(ignoreList) do - ignoreMap[v] = true -end - -IGNORE_MAP = ignoreMap - -local myOption = { - alignment = true, - sorter = function (keys, keymap) - table.sort(keys, function (a, b) - local tp1 = type(a) - local tp2 = type(b) - if tp1 == 'number' and tp2 ~= 'number' then - return false - end - if tp1 ~= 'number' and tp2 == 'number' then - return true - end - if tp1 == 'number' and tp2 == 'number' then - return a < b - end - local s1 = sortList[a] or 9999 - local s2 = sortList[b] or 9999 - if s1 == s2 then - return a < b - else - return s1 < s2 - end - end) - end, - loop = ('%q'):format(''), - number = function (n) - return ('%q'):format(n) - end, - format = setmetatable({}, { __index = function (_, key) - return function (value, _, _, _) - if ignoreMap[key] then - return '' - end - if type(key) == 'string' and key:sub(1, 1) == '_' then - return nil - end - return value - end - end}), -} - -local targetOption = { - alignment = true, - sorter = function (keys, keymap) - table.sort(keys, function (a, b) - local tp1 = type(a) - local tp2 = type(b) - if tp1 == 'number' and tp2 ~= 'number' then - return false - end - if tp1 ~= 'number' and tp2 == 'number' then - return true - end - if tp1 == 'number' and tp2 == 'number' then - return a < b - end - local s1 = sortList[a] or 9999 - local s2 = sortList[b] or 9999 - if s1 == s2 then - return a < b - else - return s1 < s2 - end - end) - end, - loop = ('%q'):format(''), - number = function (n) - return ('%q'):format(n) - end, -} - -local function autoFix(myBuf, targetBuf) - local info = debug.getinfo(3, 'Sl') - local filename = info.source:sub(2) - local fileBuf = utility.loadFile(filename) - assert(fileBuf) - local pos = fileBuf:find(targetBuf, 1, true) - local newFileBuf = fileBuf:sub(1, pos-1) .. myBuf .. fileBuf:sub(pos + #targetBuf) - utility.saveFile(filename, newFileBuf) -end - -local function test(type) - local mode = type - if mode == 'Dirty' then - mode = 'Lua' - end - CHECK = function (buf, opt) - return function (target_ast) - local state, err = parser.compile(buf, mode, 'Lua 5.4', opt) - if not state then - error(('语法树生成失败:%s'):format(err)) - end - state.ast.state = nil - local result = utility.dump(state.ast, myOption) - local expect = utility.dump(target_ast, targetOption) - if result ~= expect then - fs.create_directory(ROOT / 'test' / 'log') - utility.saveFile((ROOT / 'test' / 'log' / 'my_ast.ast'):string(), result) - utility.saveFile((ROOT / 'test' / 'log' / 'target_ast.ast'):string(), expect) - autoFix(result, expect) - error(('语法树不相等:%s\n%s'):format(type, buf)) - end - end - end - LuaDoc = function (buf) - return function (target_doc) - local state, err = parser.compile(buf, 'Lua', 'Lua 5.4') - if not state then - error(('语法树生成失败:%s'):format(err)) - end - parser.luadoc(state) - for _, doc in ipairs(state.ast.docs) do - doc.bindGroup = nil - doc.bindSources = nil - end - state.ast.docs.groups = nil - local result = utility.dump(state.ast.docs, myOption) - local expect = utility.dump(target_doc, targetOption) - if result ~= expect then - fs.create_directory(ROOT / 'test' / 'log') - utility.saveFile((ROOT / 'test' / 'log' / 'my_doc.ast'):string(), result) - utility.saveFile((ROOT / 'test' / 'log' / 'target_doc.ast'):string(), expect) - autoFix(result, expect) - error(('语法树不相等:%s\n%s'):format(type, buf)) - end - end - end - Comment = function (buf) - return function (target_comment) - local state, err = parser.compile(buf, mode, 'Lua 5.4', { - ['nonstandardSymbol'] = { - ['//'] = true, - }, - }) - if not state then - error(('语法树生成失败:%s'):format(err)) - end - state.ast.state = nil - local result = utility.dump(state.comms, myOption) - local expect = utility.dump(target_comment, targetOption) - if result ~= expect then - fs.create_directory(ROOT / 'test' / 'log') - utility.saveFile((ROOT / 'test' / 'log' / 'my_ast.ast'):string(), result) - utility.saveFile((ROOT / 'test' / 'log' / 'target_ast.ast'):string(), expect) - --autoFix(result, expect) - error(('语法树不相等:%s\n%s'):format(type, buf)) - end - end - end - require('ast.' .. type) -end - -test 'Nil' -test 'Boolean' -test 'String' -test 'Number' -test 'Exp' -test 'Action' -test 'Lua' -test 'Dirty' -test 'LuaDoc' -test 'Comment' diff --git a/test/main.lua b/test/main.lua index 104b0b6..17aa39f 100644 --- a/test/main.lua +++ b/test/main.lua @@ -16,14 +16,13 @@ local function unitTest(name) end local function main() - --collectgarbage 'stop' unitTest 'ast' --unitTest 'grammar' --unitTest 'lines' --unitTest 'grammar_check' --unitTest 'syntax_check' --unitTest 'guide' - --unitTest 'perform' + unitTest 'perform' print('测试完成') end diff --git a/test/perform/init.lua b/test/perform/init.lua index 5e0e4b3..7cd489f 100644 --- a/test/perform/init.lua +++ b/test/perform/init.lua @@ -41,11 +41,10 @@ local function performTest() end local clock = os.clock() for path, buf in pairs(files) do - local state = parser.compile(buf, 'Lua', 'Lua 5.4') - if not state then + local ast = parser.compile(buf, 'Lua 5.4') + if not ast then error(('文件解析失败:%s'):format(path:string())) end - parser.luadoc(state) --local dump = utility.unpack(state.root) --utility.pack(dump) @@ -62,11 +61,11 @@ local function test(path) return end local testTimes = 10 - local state + local ast local clock = os.clock() for i = 1, testTimes do - state = parser.compile(buf, 'Lua', 'Lua 5.4') - if not state then + ast = parser.compile(buf, 'Lua 5.4') + if not ast then error(('文件解析失败:%s'):format(path:string())) end if os.clock() - clock > 1.0 then diff --git a/test/performance.lua b/test/performance.lua index 1bf3b06..e8c99a9 100644 --- a/test/performance.lua +++ b/test/performance.lua @@ -48,8 +48,7 @@ print(('Loaded %d files, total size = %.3f KB'):format(#files, size / 1000)) print('Start parsing...') local clock = os.clock() for _, file in ipairs(files) do - local state = parser.compile(file, 'Lua', 'Lua 5.4') - parser.luadoc(state) + local ast = parser.compile(file) end local passed = os.clock() - clock From faf15d202adbdd306493b976b2cd702675b41d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 27 Sep 2023 16:39:50 +0800 Subject: [PATCH 57/94] =?UTF-8?q?=E6=95=B4=E7=90=86=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/error.lua | 2 +- src/parser/ast/id.lua | 2 +- test/catch.lua | 89 +++++++++++++++++++++++++++ test/grammar.lua | 38 +++++------- test/main.lua | 8 +-- test/syntax_check.lua | 130 ++++++--------------------------------- 6 files changed, 127 insertions(+), 142 deletions(-) create mode 100644 test/catch.lua diff --git a/src/parser/ast/error.lua b/src/parser/ast/error.lua index 8714644..f7e3b09 100644 --- a/src/parser/ast/error.lua +++ b/src/parser/ast/error.lua @@ -1,6 +1,6 @@ local class = require 'class' ----@class LuaParser.Node.Error +---@class LuaParser.Node.Error: LuaParser.Node.Base local Error = class.declare('LuaParser.Node.Error', 'LuaParser.Node.Base') Error.code = 'UNKNOWN' diff --git a/src/parser/ast/id.lua b/src/parser/ast/id.lua index 9fa2faa..38410d8 100644 --- a/src/parser/ast/id.lua +++ b/src/parser/ast/id.lua @@ -38,7 +38,7 @@ function Ast:parseID(nodeType, required) end end self.lexer:next() - return self:createNode(nodeType, { + return self:createNode(nodeType or 'LuaParser.Node.Var', { id = token, start = pos, finish = pos + #token, diff --git a/test/catch.lua b/test/catch.lua new file mode 100644 index 0000000..b66d3e6 --- /dev/null +++ b/test/catch.lua @@ -0,0 +1,89 @@ +local m = require 'lpeglabel' + +---@class catched +---@operator add: catched +local mt = {} + +local function catchedTable() + return setmetatable({}, mt) +end + +function mt.__add(a, b) + if not a or not b then + return a or b + end + local t = catchedTable() + for _, v in ipairs(a) do + t[#t+1] = v + end + for _, v in ipairs(b) do + t[#t+1] = v + end + return t +end + +local function parseTokens(script, seps) + local parser = m.P { + m.Ct(m.V 'Token'^0), + Token = m.Cp() * (m.V 'Mark' + m.V 'Nl' + m.V 'Text'), + Mark = m.Cc 'ML' * m.P '<' * m.C(m.S(seps)) + + m.Cc 'MR' * m.C(m.S(seps)) * m.P '>', + Nl = m.Cc 'NL' * m.C(m.P '\r\n' + m.S '\r\n'), + Text = m.Cc 'TX' * m.C((1 - m.V 'Nl' - m.V 'Mark')^1), + } + local results = parser:match(script) + return results +end + +---@param script string +---@param seps string +---@return string +---@return table +return function (script, seps) + local tokens = parseTokens(script, seps) + local newBuf = {} + local result = {} + local marks = {} + + for s in seps:gmatch '.' do + result[s] = catchedTable() + end + + local lineOffset = 1 + local line = 0 + local skipOffset = 0 + for i = 1, #tokens, 3 do + local offset = tokens[i + 0] + local mode = tokens[i + 1] + local text = tokens[i + 2] + if mode == 'TX' then + newBuf[#newBuf+1] = text + end + if mode == 'NL' then + newBuf[#newBuf+1] = text + line = line + 1 + lineOffset = offset + #text - skipOffset + end + if mode == 'ML' then + marks[#marks+1] = { + char = text, + position = line * 10000 + offset - skipOffset - lineOffset, + } + skipOffset = skipOffset + 1 + #text + end + if mode == 'MR' then + for j = #marks, 1, -1 do + local mark = marks[j] + if mark.char == text then + local position = line * 10000 + offset - skipOffset - lineOffset + result[text][#result[text]+1] = { mark.position, position } + table.remove(marks, j) + break + end + end + skipOffset = skipOffset + 1 + #text + end + end + + return table.concat(newBuf), result +end diff --git a/test/grammar.lua b/test/grammar.lua index a044c77..a036f07 100644 --- a/test/grammar.lua +++ b/test/grammar.lua @@ -1,9 +1,15 @@ -local parser = require 'parser' - -local function check_str(str, name, mode) - local ast = parser.compile(str, mode, 'Lua 5.3') - assert(ast) - if #ast.errs > 0 and mode ~= 'Dirty' then +local class = require 'class' + +local function check_str(code, name, mode) + ---@class LuaParser.Ast + local ast = class.new 'LuaParser.Ast' (code) + local parser = 'parse' .. mode + if mode == 'Dirty' then + parser = 'parseMain' + end + local node = ast[parser](ast) + assert(node) + if #ast.errors > 0 and mode ~= 'Dirty' then error(([[ [%s]测试失败: %s @@ -12,7 +18,7 @@ local function check_str(str, name, mode) ]]):format( name, ('='):rep(30), - str, + code, ('='):rep(30) )) end @@ -40,20 +46,6 @@ check 'Comment' [===[-- [[Abc]]a]===], } -check 'Sp' -{ -'', -' ', -' ', -'\t', -'--', -'--123', -' \t', -[===[--[[123 -123 -123]]]===], -} - check 'Nil' { 'nil', @@ -127,7 +119,7 @@ check 'Number' '9.' } -check 'Name' +check 'ID' { '_', 'And', @@ -211,7 +203,7 @@ check 'Exp' '{1, 2, 3,}', } -check 'Action' +check 'State' { 'x = 1', 'x, y, z = 1, 2, 3', diff --git a/test/main.lua b/test/main.lua index 17aa39f..a42565f 100644 --- a/test/main.lua +++ b/test/main.lua @@ -17,11 +17,9 @@ end local function main() unitTest 'ast' - --unitTest 'grammar' - --unitTest 'lines' - --unitTest 'grammar_check' - --unitTest 'syntax_check' - --unitTest 'guide' + unitTest 'grammar' + unitTest 'grammar_check' + unitTest 'syntax_check' unitTest 'perform' print('测试完成') diff --git a/test/syntax_check.lua b/test/syntax_check.lua index 26fb115..abad379 100644 --- a/test/syntax_check.lua +++ b/test/syntax_check.lua @@ -1,98 +1,16 @@ local parser = require 'parser' - -local EXISTS = {} - -local function eq(a, b) - if a == EXISTS and b ~= nil then - return true - end - local tp1, tp2 = type(a), type(b) - if tp1 ~= tp2 then - return false - end - if tp1 == 'table' then - local mark = {} - for k in pairs(a) do - if not eq(a[k], b[k]) then - return false - end - mark[k] = true - end - for k in pairs(b) do - if not mark[k] then - return false - end - end - return true - end - return a == b -end - -local function getLine(offset, lns) - for i = 0, #lns do - if offset >= lns[i] - and offset < lns[i+1] then - return i - end - end -end - -local function getPosition(offset, lns) - for i = 0, #lns do - if offset >= lns[i] - and offset < lns[i+1] then - return 10000 * i + offset - lns[i] - end - end -end +local catch = require 'test.catch' ---@param script string ----@param sep string -local function catchTarget(script, sep) - local pattern = ('()<%%%s.-%%%s>()'):format(sep, sep) - local lns = {} - lns[0] = 0 - for pos in script:gmatch '()\n' do - lns[#lns+1] = pos - end - lns[#lns+1] = math.maxinteger - local codes = {} - local pos = 1 - local list = {} - local cuted = 0 - local lastLine = 0 - for a, b in script:gmatch(pattern) do - codes[#codes+1] = script:sub(pos, a - 1) - codes[#codes+1] = script:sub(a + 2, b - 3) - pos = b - local line1 = getLine(a + 1, lns) - if line1 ~= lastLine then - cuted = 0 - lastLine = line1 - end - cuted = cuted + 2 - local left = getPosition(a + 1, lns) - cuted - local line2 = getLine(b - 3, lns) - if line2 ~= lastLine then - cuted = 0 - lastLine = line2 - end - local right = getPosition(b - 3, lns) - cuted - cuted = cuted + 2 - list[#list+1] = { left, right } - end - codes[#codes+1] = script:sub(pos) - return table.concat(codes), list -end - -local Version - -local function TEST(script) +---@param version? LuaParser.LuaVersion +---@param optional? LuaParser.CompileOptions +---@return fun(expect: table|nil) +local function TEST(script, version, optional) return function (expect) - local newScript, list = catchTarget(script, '!') - local ast = parser.compile(newScript, 'Lua', Version) + local newScript, list = catch(script, '!') + local ast = parser.compile(newScript, version, optional) assert(ast) - local errs = ast.errs + local errs = ast.errors local first = errs[1] local target = list[1] if not expect then @@ -109,18 +27,6 @@ local function TEST(script) assert(first.type == expect.type) assert(first.start == target[1]) assert(first.finish == target[2]) - assert(eq(first.version, expect.version)) - assert(eq(first.info, expect.info)) - end -end - -function TestWith(version) - return function (script) - return function (expect) - Version = version - TEST(script)(expect) - Version = 'Lua 5.4' - end end end @@ -785,27 +691,27 @@ local t = [[]] ]=] (nil) -TestWith 'LuaJIT' [[ +TEST ([[ goto LABEL ::LABEL:: -]] +]], 'Lua 5.1', { jit = true }) (nil) -TestWith 'LuaJIT' [[ +TEST ([[ local goto = 1 -]] +]], 'Lua 5.1', { jit = true }) (nil) -TestWith 'LuaJIT' [[ -local goto]] +TEST ([[ +local goto]], 'Lua 5.1', { jit = true }) (nil) -TestWith 'LuaJIT' [[ +TEST ([[ f(1, goto, 2) -]] +]], 'Lua 5.1', { jit = true }) (nil) -TestWith 'LuaJIT' [[ +TEST ([[ local function f(x, goto, y) end -]] +]], 'Lua 5.1', { jit = true }) (nil) From 12f107a456a590cd81028dc79d2c73626d340188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 27 Sep 2023 17:29:37 +0800 Subject: [PATCH 58/94] =?UTF-8?q?=E6=95=B4=E7=90=86=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/error.lua | 1 + src/parser/ast/field.lua | 9 +- src/parser/ast/local.lua | 2 +- src/parser/ast/main.lua | 4 +- src/parser/ast/string.lua | 14 +- src/parser/ast/unary.lua | 4 +- test/emmy_check.lua | 103 --- test/grammar.lua | 2 +- test/grammar_check.lua | 1680 ------------------------------------- test/main.lua | 1 - test/syntax_check.lua | 1581 +++++++++++++++++++++++++++++++++- 11 files changed, 1592 insertions(+), 1809 deletions(-) delete mode 100644 test/emmy_check.lua delete mode 100644 test/grammar_check.lua diff --git a/src/parser/ast/error.lua b/src/parser/ast/error.lua index f7e3b09..5826d93 100644 --- a/src/parser/ast/error.lua +++ b/src/parser/ast/error.lua @@ -1,6 +1,7 @@ local class = require 'class' ---@class LuaParser.Node.Error: LuaParser.Node.Base +---@field extra? table local Error = class.declare('LuaParser.Node.Error', 'LuaParser.Node.Base') Error.code = 'UNKNOWN' diff --git a/src/parser/ast/field.lua b/src/parser/ast/field.lua index ff4f2ca..0a33147 100644 --- a/src/parser/ast/field.lua +++ b/src/parser/ast/field.lua @@ -145,9 +145,12 @@ function Ast:parseTableFieldAsIndex() self:skipSpace() local pos2 = self:assertSymbol ']' self:skipSpace() - self:assertSymbol '=' - self:skipSpace() - local value = self:parseExp(true) + local eqPos = self:assertSymbol '=' + local value + if eqPos then + self:skipSpace() + value = self:parseExp(true) + end local tfield = self:createNode('LuaParser.Node.TableField', { subtype = 'index', key = key, diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index 417cb5b..e804277 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -92,7 +92,7 @@ function Ast:parseLocal() localdef.vars = vars self:skipSpace() - local symbolPos = self:assertSymbol '=' + local symbolPos = self.lexer:consume '=' if symbolPos then localdef.symbolPos = symbolPos self:skipSpace() diff --git a/src/parser/ast/main.lua b/src/parser/ast/main.lua index e0eee04..fe7ce00 100644 --- a/src/parser/ast/main.lua +++ b/src/parser/ast/main.lua @@ -19,8 +19,8 @@ local Ast = class.declare 'LuaParser.Ast' ---@private function Ast:skipShebang() - if self.code:sub(1, 2) == '#!' then - local pos = self.code:find('\n', 3, true) + if self.code:sub(1, 1) == '#' then + local pos = self.code:find('\n', 2, true) if pos then self.lexer:fastForward(pos + 1) else diff --git a/src/parser/ast/string.lua b/src/parser/ast/string.lua index 5e8359b..39e212d 100644 --- a/src/parser/ast/string.lua +++ b/src/parser/ast/string.lua @@ -173,9 +173,9 @@ function Ast:parseShortString() local num = tonumber(code, 16) pieces[#pieces+1] = string.char(num) else - pushEsc('err', offset - 2, offset - 1) + pushEsc('err', offset - 2, offset) + self:throw('MISS_ESC_X', offset, offset) curOffset = offset + 1 - self:throw('ERR_ESC_X', curPos, curPos + 1) end if self.versionNum <= 51 then self:throw('ERR_ESC', curPos, curPos + 3) @@ -185,8 +185,8 @@ function Ast:parseShortString() if escChar == 'u' then local leftP, word, rightP, tailOffset = self.code:match('^({)(%w*)(}?)()', offset + 1) if not leftP then - pushEsc('err', offset - 2, offset - 1) - self:throwMissSymbol(offset - 1, '{') + pushEsc('err', offset - 2, offset) + self:throwMissSymbol(offset, '{') curOffset = offset goto continue end @@ -198,13 +198,13 @@ function Ast:parseShortString() if num then if self.versionNum >= 54 then if num < 0 or num > 0x7FFFFFFF then - self:throw('UTF8_MAX', offset + 1, offset + #word, { + self:throw('UTF8_MAX', offset + 1, offset + #word + 1, { min = '00000000', max = '7FFFFFFF', }) end else - self:throw('UTF8_MAX', offset + 1, offset + #word, { + self:throw('UTF8_MAX', offset + 1, offset + #word + 1, { min = '000000', max = '10FFFF', needVer = num <= 0x7FFFFFFF and 'Lua 5.4' or nil, @@ -214,7 +214,7 @@ function Ast:parseShortString() pieces[#pieces+1] = utf8.char(num) end else - self:throw('MUST_X16', offset + 1, offset + #word) + self:throw('MUST_X16', offset + 1, offset + #word + 1) end end if rightP == '' then diff --git a/src/parser/ast/unary.lua b/src/parser/ast/unary.lua index 9ab1a1b..b572f8c 100644 --- a/src/parser/ast/unary.lua +++ b/src/parser/ast/unary.lua @@ -70,7 +70,9 @@ function Ast:parseUnary() op = token, exp = exp, }) - exp.parent = unary + if exp then + exp.parent = unary + end return unary end diff --git a/test/emmy_check.lua b/test/emmy_check.lua deleted file mode 100644 index 4e9a797..0000000 --- a/test/emmy_check.lua +++ /dev/null @@ -1,103 +0,0 @@ -local parser = require 'parser' - -local EXISTS = {} - -local function eq(a, b) - if a == EXISTS and b ~= nil then - return true - end - local tp1, tp2 = type(a), type(b) - if tp1 ~= tp2 then - return false - end - if tp1 == 'table' then - local mark = {} - for k in pairs(a) do - if not eq(a[k], b[k]) then - return false - end - mark[k] = true - end - for k in pairs(b) do - if not mark[k] then - return false - end - end - return true - end - return a == b -end - -local function catchTarget(script, sep) - local list = {} - local cur = 1 - local cut = 0 - while true do - local start, finish = script:find(('<%%%s.-%%%s>'):format(sep, sep), cur) - if not start then - break - end - list[#list+1] = { start - cut, math.max(start - cut, finish - 4 - cut) } - cur = finish + 1 - cut = cut + 4 - end - local new_script = script:gsub(('<%%%s(.-)%%%s>'):format(sep, sep), '%1') - return new_script, list -end - -local Version - -local function TEST(script) - return function (expect) - local newScript, list = catchTarget(script, '!') - local _, errs, emmy = parser:ast(newScript, 'lua', Version) - assert(emmy) - assert(errs) - local first = errs[1] - local target = list[1] - if not expect then - assert(#errs == 0) - return - end - if expect.multi then - assert(#errs > 1) - first = errs[expect.multi] - else - assert(#errs == 1) - end - assert(first) - assert(first.type == expect.type) - assert(first.start == target[1]) - assert(first.finish == target[2]) - assert(eq(first.version, expect.version)) - assert(eq(first.info, expect.info)) - end -end - -TEST[[ ----@class -]] -{ - type = 'MISS_NAME', -} - -TEST[[ ----@class Class : -]] -{ - type = 'MISS_NAME', -} - -TEST[[ ----@type -]] -{ - type = 'MISS_NAME', -} - -TEST[[ ----@type Type1| -]] -{ - type = 'MISS_NAME', -} diff --git a/test/grammar.lua b/test/grammar.lua index a036f07..4eaeef4 100644 --- a/test/grammar.lua +++ b/test/grammar.lua @@ -294,7 +294,7 @@ until 1]], end]], } -check 'Lua' +check 'Main' { '', [[ diff --git a/test/grammar_check.lua b/test/grammar_check.lua deleted file mode 100644 index 8b5e134..0000000 --- a/test/grammar_check.lua +++ /dev/null @@ -1,1680 +0,0 @@ -local parser = require 'parser' - -local EXISTS = {} - -local function eq(a, b) - if a == EXISTS and b ~= nil then - return true - end - local tp1, tp2 = type(a), type(b) - if tp1 ~= tp2 then - return false - end - if tp1 == 'table' then - local mark = {} - for k in pairs(a) do - if not eq(a[k], b[k]) then - return false - end - mark[k] = true - end - for k in pairs(b) do - if not mark[k] then - return false - end - end - return true - end - return a == b -end - -local function getLine(offset, lns) - for i = 0, #lns do - if offset >= lns[i] - and offset < lns[i+1] then - return i - end - end -end - -local function getPosition(offset, lns) - for i = 0, #lns do - if offset >= lns[i] - and offset < lns[i+1] then - return 10000 * i + offset - lns[i] - end - end -end - ----@param script string ----@param sep string -local function catchTarget(script, sep) - local pattern = ('()<%%%s.-%%%s>()'):format(sep, sep) - local lns = {} - lns[0] = 0 - for pos in script:gmatch '()\n' do - lns[#lns+1] = pos - end - lns[#lns+1] = math.maxinteger - local codes = {} - local pos = 1 - ---@type integer[] - local list = {} - local cuted = 0 - local lastLine = 0 - for a, b in script:gmatch(pattern) do - codes[#codes+1] = script:sub(pos, a - 1) - codes[#codes+1] = script:sub(a + 2, b - 3) - pos = b - local line1 = getLine(a + 1, lns) - if line1 ~= lastLine then - cuted = 0 - lastLine = line1 - end - cuted = cuted + 2 - local left = getPosition(a + 1, lns) - cuted - local line2 = getLine(b - 3, lns) - if line2 ~= lastLine then - cuted = 0 - lastLine = line2 - end - local right = getPosition(b - 3, lns) - cuted - cuted = cuted + 2 - list[#list+1] = { left, right } - end - codes[#codes+1] = script:sub(pos) - return table.concat(codes), list -end - -local Version - -local function TEST(script) - return function (expect) - local newScript, list = catchTarget(script, '!') - local ast = parser.compile(newScript, 'Lua', Version) - assert(ast) - local errs = ast.errs - local first = errs[1] - local target = list[1] - if not expect then - assert(#errs == 0) - return - end - if expect.multi then - assert(#errs > 1) - first = errs[expect.multi] - else - assert(#errs == 1) - end - assert(first) - assert(first.type == expect.type) - assert(first.start == target[1]) - assert(first.finish == target[2]) - assert(eq(first.version, expect.version)) - assert(eq(first.info, expect.info)) - end -end - -Version = 'Lua 5.3' - -TEST[[ -local -]] -{ - type = 'MISS_NAME', -} - -TEST[[ - -]] -{ - type = 'UNICODE_NAME', -} - -TEST[[ -n = 1 -]] -{ - type = 'MALFORMED_NUMBER', -} - -TEST[[ -s = 'a -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = "'", - } -} - -TEST[[ -s = "a -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = '"', - } -} - -TEST[======[ -s = [[a]======] -{ - type = 'MISS_SYMBOL', - info = { - symbol = ']]', - } -} - -TEST[======[ -s = [===[a]======] -{ - type = 'MISS_SYMBOL', - info = { - symbol = ']===]', - } -} - -TEST[======[ -s = [===[a]=]]======] -{ - type = 'MISS_SYMBOL', - info = { - symbol = ']===]', - } -} - -TEST[[ -s = '\xzzz' -]] -{ - type = 'MISS_ESC_X', -} - -TEST[[ -s = '\u' -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = '{' - } -} - -TEST[[ -s = '\u' -]] -{ - type = 'UTF8_SMALL', -} - -TEST[[ -s = '\u' -]] -{ - type = 'UTF8_MAX', - info = { - min = '000000', - max = '10FFFF', - } -} - -TEST[[ -s = '\u' -]] -{ - type = 'UTF8_MAX', - version = 'Lua 5.4', - info = { - min = '000000', - max = '10FFFF', - } -} - -TEST[[ -s = '\u{aaa' -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = '}', - } -} - -TEST[[ -s = '\u{abc}' -]] -{ - type = 'MUST_X16', -} - -TEST[[ -s = '' -]] -{ - type = 'ERR_ESC', -} - -TEST[[ -s = '' -]] -{ - type = 'ERR_ESC', -} - -TEST[[ -s = '' -]] -{ - type = 'ERR_ESC', -} - -TEST[[ -n = 0x -]] -{ - type = 'MUST_X16', -} - -TEST[[ -n = 0xzzzz -]] -{ - type = 'MUST_X16', - multi = 1, -} - - -TEST[[ -n = 0x.zzzz -]] -{ - type = 'MUST_X16', - multi = 1, -} - -TEST[[ -t = { -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = '}', - } -} - -TEST[[ -t = {1 -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = '}', - } -} - -TEST[[ -t = {1, -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = '}', - } -} - -TEST[[ -t = {name =} -]] -{ - type = 'MISS_EXP', -} - -TEST[[ -t = {['name'] =} -]] -{ - type = 'MISS_EXP', -} - -TEST[[ -t = {['name']} -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = '=', - } -} - -TEST[[ -t = {[]=1} -]] -{ - type = 'MISS_EXP', -} - -TEST[[ -t = {,} -]] -{ - type = 'MISS_EXP', -} - -TEST[[ -t = {12} -]] -{ - type = 'MISS_SEP_IN_TABLE', -} - -TEST[[ -f( -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = ')', - }, -} - -TEST[[ -f(1) -]] -{ - type = 'UNEXPECT_SYMBOL', - info = { - symbol = ',', - } -} - -TEST[[ -f(1,) -]] -{ - type = 'MISS_EXP', -} - -TEST[[ -f(11) -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = ',', - }, -} - -TEST[[ -().x() -]] -{ - type = 'MISS_EXP', -} - -TEST[[ -print(x -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = ')', - } -} - -TEST[[ -x.() -]] -{ - type = 'MISS_FIELD', -} - -TEST[[ -x:() -]] -{ - type = 'MISS_METHOD', -} - -TEST[[ -x[] = 1 -]] -{ - type = 'MISS_EXP', -} - -TEST[[ -y = x[1 -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = ']', - } -} - -TEST[[ -x:m -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = '(', - } -} - -TEST[[ -x = 1 and -]] -{ - type = 'MISS_EXP', -} - -TEST[[ -x = # -]] -{ - type = 'MISS_EXP', -} - -TEST[[ -local x = 1, -]] -{ - type = 'MISS_EXP', -} - -TEST[[ -local x, = 1, 2 -]] -{ - type = 'MISS_NAME', -} - -TEST[[ -:: -]] -{ - type = 'MISS_NAME', -} - -TEST[[ -::LABEL -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = '::', - } -} - -TEST[[ -goto -]] -{ - type = 'MISS_NAME', -} - -TEST[[ -return 1, -]] -{ - type = 'MISS_EXP', -} - -TEST[[ -local function() end -]] -{ - type = 'MISS_NAME', -} - -TEST[[ -function() end -]] -{ - type = 'MISS_NAME', -} - -TEST[[ -function f( -]] -{ - multi = 1, - type = 'MISS_SYMBOL', - info = { - symbol = ')', - } -} - -TEST[[ -function f end -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = '(', - } -} - -TEST[[ -f = function ( -]] -{ - multi = 1, - type = 'MISS_SYMBOL', - info = { - symbol = ')', - } -} - -TEST[[ -f = function end -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = '(', - } -} - -TEST[[ -f = function () end -]] -{ - type = 'UNEXPECT_EFUNC_NAME', -} - -TEST[[ -function f() -]] -{ - multi = 1, - type = 'MISS_SYMBOL', - info = { - symbol = 'end', - related = { - { - start = 0, - finish = 8, - } - }, - } -} - -TEST[[ - f() -]] -{ - multi = 2, - type = 'MISS_END', -} - -TEST[[ -function f:() end -]] -{ - type = 'MISS_METHOD', -} - -TEST[[ -function f:x.y() end -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = '(', - } -} - -TEST[[ -function f(a,) end -]] -{ - type = 'MISS_NAME', -} - -TEST[[ -function f(,a) end -]] -{ - type = 'MISS_NAME', -} - -TEST[[ -function f(..., ) end -]] -{ - type = 'ARGS_AFTER_DOTS', -} - -TEST[[ -local x = ]] -{ - type = 'MISS_EXP', -} - -TEST[[ -x = ]] -{ - type = 'MISS_EXP', -} - -TEST[[ -for in next do -end -]] -{ - type = 'MISS_NAME', -} - -TEST[[ -for k, v do -end -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = 'in', - } -} - -TEST[[ -for k, v in do -end -]] -{ - type = 'MISS_EXP', -} - -TEST[[ -for k, v in next -end -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = 'do', - } -} - -TEST[[ -for k, v in next do -]] -{ - multi = 1, - type = 'MISS_SYMBOL', - info = { - symbol = 'end', - related = { - { - start = 0, - finish = 3, - }, - } - } -} - -TEST[[ - k, v in next do -]] -{ - multi = 2, - type = 'MISS_END', -} - -TEST[[ -for i = 2 do -end -]] -{ - multi = 1, - type = 'UNEXPECT_SYMBOL', - info = { - symbol = ',', - } -} - -TEST[[ -for = 1, 2 do -end -]] -{ - type = 'MISS_NAME', -} - -TEST[[ -for i = 1 do -end -]] -{ - type = 'MISS_LOOP_MAX', -} - -TEST[[ -for i = 1, do -end -]] -{ - --multi = 2, - type = 'MISS_EXP' -} - -TEST[[ -for i = do -end -]] -{ - type = 'MISS_LOOP_MIN' -} - -TEST[[ -for i = 1, do -end -]] -{ - --multi = 1, - type = 'MISS_EXP', -} - -TEST[[ -for i = 1, 2, do -end -]] -{ - type = 'MISS_EXP', -} - -TEST[[ -for i = 1, 23 do -end -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = ',', - } -} - -TEST[[ -for i = 1, 2 -end -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = 'do', - } -} - -TEST[[ -for i = 1, 2 do -]] -{ - multi = 1, - type = 'MISS_SYMBOL', - info = { - symbol = 'end', - related = { - { - start = 0, - finish = 3, - }, - } - } -} - -TEST[[ - i = 1, 2 do -]] -{ - multi = 2, - type = 'MISS_END', -} - -TEST[[ -while do -end -]] -{ - type = 'MISS_EXP', -} - -TEST[[ -while true -end -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = 'do', - } -} - -TEST[[ -while true do -]] -{ - multi = 1, - type = 'MISS_SYMBOL', - info = { - symbol = 'end', - related = { - { - start = 0, - finish = 5, - }, - } - } -} - -TEST[[ - true do -]] -{ - multi = 2, - type = 'MISS_END', -} - -TEST[[ -repeat -until -]] -{ - type = 'MISS_EXP', -} - -TEST[[ -repeat -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = 'until', - } -} - -TEST[[ -if then -end -]] -{ - type = 'MISS_EXP', -} - -TEST[[ -if true -end -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = 'then', - } -} - -TEST[[ -if true then -]] -{ - multi = 1, - type = 'MISS_SYMBOL', - info = { - symbol = 'end', - related = { - { - start = 0, - finish = 2, - }, - } - } -} - -TEST[[ - true then -]] -{ - multi = 2, - type = 'MISS_END', -} - -TEST[[ -if true then -else -]] -{ - multi = 1, - type = 'MISS_SYMBOL', - info = { - symbol = 'end', - related = { - { - start = 0, - finish = 2, - }, - } - } -} - -TEST[[ -if true then -elseif -end -]] -{ - type = 'MISS_EXP', -} - -TEST[[ -if true then -elseif true -end -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = 'then', - } -} - -TEST[[ - -]] -{ - type = 'EXP_IN_ACTION', -} - -TEST[[ - -]] -{ - type = 'EXP_IN_ACTION', -} - -TEST[[ - -]] -{ - type = 'EXP_IN_ACTION', -} - -TEST[[ - -]] -{ - type = 'EXP_IN_ACTION', -} - -TEST[[ -else -end -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = 'if', - } -} - -TEST[[ -elseif true then -end -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = 'if', - } -} - -TEST[[ -if true then -else -end -]] -{ - type = 'BLOCK_AFTER_ELSE', -} - -TEST[[ -xxxx -]] -{ - type = 'ERR_COMMENT_PREFIX', - fix = EXISTS, -} - -TEST[[ - -]] -{ - type = 'ERR_C_LONG_COMMENT', - fix = EXISTS, -} - -TEST[[ -a b -]] -{ - type = 'ERR_ASSIGN_AS_EQ', - fix = EXISTS, -} - -TEST[[ -_VERSION 1 -]] -{ - type = 'ERR_ASSIGN_AS_EQ', - fix = EXISTS, -} - -TEST[[ -return a b -]] -{ - type = 'ERR_EQ_AS_ASSIGN', - fix = EXISTS, -} - -TEST[[ -return a b -]] -{ - type = 'ERR_NONSTANDARD_SYMBOL', - fix = EXISTS, - info = { - symbol = '~=', - }, -} - -TEST[[ -if a end -]] -{ - type = 'ERR_THEN_AS_DO', - fix = EXISTS, -} - -TEST[[ -while true end -]] -{ - type = 'ERR_DO_AS_THEN', - fix = EXISTS, -} - -TEST[[ -return { - _VERSION = '', -} -]] -(nil) - -TEST[[ -return { - _VERSION == '', -} -]] -(nil) - --- 以下测试来自 https://github.com/andremm/lua-parser/blob/master/test.lua -TEST[[ -f = 9e -]] -{ - type = 'MISS_EXPONENT', -} - -TEST[[ -f = 5.e -]] -{ - type = 'MISS_EXPONENT', -} - -TEST[[ -f = .9e- -]] -{ - type = 'MISS_EXPONENT', -} - -TEST[[ -f = 5.9e+ -]] -{ - type = 'MISS_EXPONENT', -} - -TEST[[ -hex = 0xG -]] -{ - type = 'MUST_X16', - multi = 1, -} - -TEST[=============[ ---[==[ -testing long string3 begin -]==] - -ls3 = [===[ -testing -unfinised -long string -]==] - ---[==[ -[[ testing long string3 end ]] -]==] -]=============] -{ - type = 'MISS_SYMBOL', - info = { - symbol = ']===]', - } -} - -TEST[[ --- short string test begin - -ss6 = "testing unfinished string - --- short string test end -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = '"' - } -} - -TEST[[ --- short string test begin - -ss7 = 'testing \\ -unfinished \\ -string' - --- short string test end -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = "'", - }, - multi = 1, -} - -TEST[[ ---[[ testing -unfinished -comment -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = ']]', - } -} - -TEST[[ ---[=[xxx]==] -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = ']=]', - }, - fix = EXISTS, -} - -TEST[[ -a = function (a,b,) end -]] -{ - type = 'MISS_NAME', -} - -TEST[[ -a = function (...,) end -]] -{ - type = 'ARGS_AFTER_DOTS', -} - -TEST[[ -local a = function () end -]] -{ - type = 'UNKNOWN_SYMBOL', - info = { - symbol = '1', - }, -} - -TEST[[ -local test = function ( a , b , c , ... ) -]] -{ - multi = 1, - type = 'MISS_SYMBOL', - info = { - symbol = 'end', - related = { - { - start = 13, - finish = 21, - }, - } - } -} - -TEST[[ -local test = ( a , b , c , ... ) -]] -{ - multi = 2, - type = 'MISS_END', -} - -TEST[[ -a = 3 / 2 -]] -{ - type = 'UNKNOWN_SYMBOL', - info = { - symbol = '/' - } -} - -TEST[[ -b = 1 1 -]] -{ - type = 'ERR_NONSTANDARD_SYMBOL', - info = { - symbol = 'and', - } -} - -TEST[[ -b = 1 <!> 0 -]] -{ - type = 'UNKNOWN_SYMBOL', - info = { - symbol = '>', - } -} - -TEST[[ -b = 1 < 0 -]] -{ - type = 'UNKNOWN_SYMBOL', - info = { - symbol = '<', - } -} - -TEST[[ -concat2 = 2^3. -]] -{ - type = 'MALFORMED_NUMBER', -} - -TEST[[ -for k;v in pairs(t) do end -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = 'in', - }, - multi = 1, -} - -TEST[[ -for k,v in pairs(t:any) do end -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = '(', - }, -} - -TEST[[ -for i=1,10, do end -]] -{ - type = 'MISS_EXP', -} - -TEST[[ -for i=1,n:number do end -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = '(', - }, -} - -TEST[[ -function func(a,b,c,) end -]] -{ - type = 'MISS_NAME', -} - -TEST[[ -function func(...,) end -]] -{ - type = 'ARGS_AFTER_DOTS' -} - -TEST[[ -function a.b:c:d () end -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = '(', - } -} - -TEST[[ -if a then -]] -{ - multi = 1, - type = 'MISS_SYMBOL', - info = { - symbol = 'end', - related = { - { - start = 0, - finish = 2, - }, - } - } -} - -TEST[[ - a then -]] -{ - multi = 2, - type = 'MISS_END', -} - -TEST[[ -if a then else -]] -{ - multi = 1, - type = 'MISS_SYMBOL', - info = { - symbol = 'end', - related = { - { - start = 0, - finish = 2, - }, - } - } -} - -TEST[[ - a then else -]] -{ - multi = 2, - type = 'MISS_END', -} - -TEST[[ -if a then - return a -elseif b then - return b -elseif - -end -]] -{ - type = 'MISS_EXP', -} - -TEST[[ -if a:any then else end -]] -{ - type = 'MISS_SYMBOL', - info = { - symbol = '(', - } -} - -TEST[[ -:: blah :: -:: :: -]] -{ - type = 'KEYWORD', -} - -TEST[[ -local function () -end -]] -{ - type = 'UNEXPECT_LFUNC_NAME' -} - -TEST [[ -f() 1 -]] -{ - multi = 1, - type = 'UNKNOWN_SYMBOL', - info = { - symbol = '=', - } -} - -Version = 'Lua 5.1' -TEST[[ - -]] -{ - type = 'UNSUPPORT_SYMBOL', - version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, - info = { - version = 'Lua 5.1', - } -} - -TEST[[ -local goto = 1 -]] -(nil) - -TEST[[ -local x = '' -]] -{ - type = 'ERR_ESC', - version = {'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, - info = { - version ='Lua 5.1', - } -} - -TEST[[ -local x = '' -]] -{ - type = 'ERR_ESC', - version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, - info = { - version = 'Lua 5.1', - } -} - -Version = 'Lua 5.2' -TEST[[ -local x = 1 2 -]] -{ - type = 'UNSUPPORT_SYMBOL', - version = {'Lua 5.3', 'Lua 5.4'}, - info = { - version = 'Lua 5.2', - } -} - -TEST[[ -local x = 1 2 -]] -{ - type = 'UNSUPPORT_SYMBOL', - version = {'Lua 5.3', 'Lua 5.4'}, - info = { - version = 'Lua 5.2', - } -} - -TEST[[ -local x = '' -]] -{ - type = 'ERR_ESC', - version = {'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, - info = { - version = 'Lua 5.2', - } -} - -TEST[[ -while true do - break - x = 1 -end -]] -(nil) - -Version = 'Lua 5.3' - -TEST[[ -local x !> = 1 -]] -{ - type = 'UNSUPPORT_SYMBOL', - version = 'Lua 5.4', - info = { - version = 'Lua 5.3', - } -} - - -Version = 'Lua 5.4' - -TEST[[ -local x <> = 1 -]] -(nil) - -TEST[[ -s = '' -]] -(nil) - - -TEST[[ -s = '\u' -]] -{ - type = 'UTF8_MAX', - info = { - min = '00000000', - max = '7FFFFFFF', - } -} - -TEST[[ -x = 42 -]] -{ - type = 'UNSUPPORT_SYMBOL', - version = 'LuaJIT', - info = { - version = 'Lua 5.4', - } -} - -TEST[[ -x = -]] -{ - type = 'UNSUPPORT_SYMBOL', - version = 'LuaJIT', - info = { - version = 'Lua 5.4', - } -} - -TEST[[ -x = 12.5 -]] -{ - type = 'UNSUPPORT_SYMBOL', - version = 'LuaJIT', - info = { - version = 'Lua 5.4', - } -} - -TEST[[ -x = 1.23 -]] -{ - type = 'MALFORMED_NUMBER', -} - -Version = 'LuaJIT' - -TEST[[ -x = 42LL -x = 42ULl -x = 0x2aLL -x = 0x2All -x = 12.5i -x = 1I -x = 18446744073709551615ULL -x = 0b11011 -]] -(nil) - -TEST[[ -x = 1.23 -]] -{ - type = 'MALFORMED_NUMBER', -} - -TEST[[ -x = 0b1 -]] -{ - type = 'MALFORMED_NUMBER', -} diff --git a/test/main.lua b/test/main.lua index a42565f..a8d1787 100644 --- a/test/main.lua +++ b/test/main.lua @@ -18,7 +18,6 @@ end local function main() unitTest 'ast' unitTest 'grammar' - unitTest 'grammar_check' unitTest 'syntax_check' unitTest 'perform' diff --git a/test/syntax_check.lua b/test/syntax_check.lua index abad379..7480a08 100644 --- a/test/syntax_check.lua +++ b/test/syntax_check.lua @@ -1,18 +1,19 @@ local parser = require 'parser' local catch = require 'test.catch' +local util = require 'utility' ---@param script string ----@param version? LuaParser.LuaVersion ----@param optional? LuaParser.CompileOptions ---@return fun(expect: table|nil) -local function TEST(script, version, optional) +local function TEST(script) return function (expect) + local version = expect and expect.version + local optional = expect and expect.optional local newScript, list = catch(script, '!') local ast = parser.compile(newScript, version, optional) assert(ast) local errs = ast.errors local first = errs[1] - local target = list[1] + local target = list['!'][1] if not expect then assert(#errs == 0) return @@ -24,12 +25,1572 @@ local function TEST(script, version, optional) assert(#errs == 1) end assert(first) - assert(first.type == expect.type) + assert(first.code == expect.type) assert(first.start == target[1]) assert(first.finish == target[2]) + assert(util.equal(first.extra, expect.extra)) end end + +TEST[[ +local +]] +{ + type = 'MISS_NAME', +} + +TEST[[ + +]] +{ + type = 'UNICODE_NAME', +} + +TEST[[ +n = 1 +]] +{ + type = 'MALFORMED_NUMBER', +} + +TEST[[ +s = 'a +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = "'", + } +} + +TEST[[ +s = "a +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = '"', + } +} + +TEST[======[ +s = [[a]======] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = ']]', + } +} + +TEST[======[ +s = [===[a]======] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = ']===]', + } +} + +TEST[======[ +s = [===[a]=]]======] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = ']===]', + } +} + +TEST[[ +s = '\xzzzzz' +]] +{ + type = 'MISS_ESC_X', +} + +TEST[[ +s = '\u' +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = '{' + } +} + +TEST[[ +s = '\u{}' +]] +{ + type = 'UTF8_SMALL', +} + +TEST[[ +s = '\u{}' +]] +{ + type = 'UTF8_MAX', + version = 'Lua 5.3', + extra = { + min = '000000', + max = '10FFFF', + needVer = 'Lua 5.4' + } +} + +TEST[[ +s = '\u{}' +]] +{ + type = 'UTF8_MAX', + extra = { + min = '00000000', + max = '7FFFFFFF', + } +} + +TEST[[ +s = '\u{aaa' +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = '}', + } +} + +TEST[[ +s = '\u{}' +]] +{ + type = 'MUST_X16', +} + +TEST[[ +s = '' +]] +{ + type = 'ERR_ESC', +} + +TEST[[ +s = '' +]] +{ + type = 'ERR_ESC', +} + +TEST[[ +s = '\' +]] +{ + type = 'ERR_ESC_DEC', + extra = { + min = '000', + max = '255', + } +} + +TEST[[ +n = 0x +]] +{ + type = 'MUST_X16', +} + +TEST[[ +n = 0xzzzz +]] +{ + type = 'MUST_X16', + multi = 1, +} + + +TEST[[ +n = 0x.zzzz +]] +{ + type = 'MUST_X16', + multi = 1, +} + +TEST[[ +t = { +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = '}', + } +} + +TEST[[ +t = {1 +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = '}', + } +} + +TEST[[ +t = {1, +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = '}', + } +} + +TEST[[ +t = {name =} +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +t = {['name'] =} +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +t = {['name']} +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = '=', + } +} + +TEST[[ +t = {[]=1} +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +t = {,} +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +t = {12} +]] +{ + type = 'MISS_SEP_IN_TABLE', +} + +TEST[[ +f( +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = ')', + }, +} + +TEST[[ +f(1) +]] +{ + type = 'UNEXPECT_SYMBOL', + extra = { + symbol = ',', + } +} + +TEST[[ +f(1,) +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +f(11) +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = ',', + }, +} + +TEST[[ +().x() +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +print(x +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = ')', + } +} + +TEST[[ +x.() +]] +{ + type = 'MISS_FIELD', +} + +TEST[[ +x:() +]] +{ + type = 'MISS_METHOD', +} + +TEST[[ +x[] = 1 +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +y = x[1 +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = ']', + } +} + +TEST[[ +x:m +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = '(', + } +} + +TEST[[ +x = 1 and +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +x = # +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +local x = 1, +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +local x, = 1, 2 +]] +{ + type = 'MISS_NAME', +} + +TEST[[ +:: +]] +{ + type = 'MISS_NAME', +} + +TEST[[ +::LABEL +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = '::', + } +} + +TEST[[ +goto +]] +{ + type = 'MISS_NAME', +} + +TEST[[ +return 1, +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +local function() end +]] +{ + type = 'MISS_NAME', +} + +TEST[[ +function() end +]] +{ + type = 'MISS_NAME', +} + +TEST[[ +function f( +]] +{ + multi = 1, + type = 'MISS_SYMBOL', + extra = { + symbol = ')', + } +} + +TEST[[ +function f end +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = '(', + } +} + +TEST[[ +f = function ( +]] +{ + multi = 1, + type = 'MISS_SYMBOL', + extra = { + symbol = ')', + } +} + +TEST[[ +f = function end +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = '(', + } +} + +TEST[[ +f = function () end +]] +{ + type = 'UNEXPECT_EFUNC_NAME', +} + +TEST[[ +function f() +]] +{ + multi = 1, + type = 'MISS_SYMBOL', + extra = { + symbol = 'end', + related = { + { + start = 0, + finish = 8, + } + }, + } +} + +TEST[[ + f() +]] +{ + multi = 2, + type = 'MISS_END', +} + +TEST[[ +function f:() end +]] +{ + type = 'MISS_METHOD', +} + +TEST[[ +function f:x.y() end +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = '(', + } +} + +TEST[[ +function f(a,) end +]] +{ + type = 'MISS_NAME', +} + +TEST[[ +function f(,a) end +]] +{ + type = 'MISS_NAME', +} + +TEST[[ +function f(..., ) end +]] +{ + type = 'ARGS_AFTER_DOTS', +} + +TEST[[ +local x = ]] +{ + type = 'MISS_EXP', +} + +TEST[[ +x = ]] +{ + type = 'MISS_EXP', +} + +TEST[[ +for in next do +end +]] +{ + type = 'MISS_NAME', +} + +TEST[[ +for k, v do +end +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = 'in', + } +} + +TEST[[ +for k, v in do +end +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +for k, v in next +end +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = 'do', + } +} + +TEST[[ +for k, v in next do +]] +{ + multi = 1, + type = 'MISS_SYMBOL', + extra = { + symbol = 'end', + related = { + { + start = 0, + finish = 3, + }, + } + } +} + +TEST[[ + k, v in next do +]] +{ + multi = 2, + type = 'MISS_END', +} + +TEST[[ +for i = 2 do +end +]] +{ + multi = 1, + type = 'UNEXPECT_SYMBOL', + extra = { + symbol = ',', + } +} + +TEST[[ +for = 1, 2 do +end +]] +{ + type = 'MISS_NAME', +} + +TEST[[ +for i = 1 do +end +]] +{ + type = 'MISS_LOOP_MAX', +} + +TEST[[ +for i = 1, do +end +]] +{ + --multi = 2, + type = 'MISS_EXP' +} + +TEST[[ +for i = do +end +]] +{ + type = 'MISS_LOOP_MIN' +} + +TEST[[ +for i = 1, do +end +]] +{ + --multi = 1, + type = 'MISS_EXP', +} + +TEST[[ +for i = 1, 2, do +end +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +for i = 1, 23 do +end +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = ',', + } +} + +TEST[[ +for i = 1, 2 +end +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = 'do', + } +} + +TEST[[ +for i = 1, 2 do +]] +{ + multi = 1, + type = 'MISS_SYMBOL', + extra = { + symbol = 'end', + related = { + { + start = 0, + finish = 3, + }, + } + } +} + +TEST[[ + i = 1, 2 do +]] +{ + multi = 2, + type = 'MISS_END', +} + +TEST[[ +while do +end +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +while true +end +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = 'do', + } +} + +TEST[[ +while true do +]] +{ + multi = 1, + type = 'MISS_SYMBOL', + extra = { + symbol = 'end', + related = { + { + start = 0, + finish = 5, + }, + } + } +} + +TEST[[ + true do +]] +{ + multi = 2, + type = 'MISS_END', +} + +TEST[[ +repeat +until +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +repeat +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = 'until', + } +} + +TEST[[ +if then +end +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +if true +end +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = 'then', + } +} + +TEST[[ +if true then +]] +{ + multi = 1, + type = 'MISS_SYMBOL', + extra = { + symbol = 'end', + related = { + { + start = 0, + finish = 2, + }, + } + } +} + +TEST[[ + true then +]] +{ + multi = 2, + type = 'MISS_END', +} + +TEST[[ +if true then +else +]] +{ + multi = 1, + type = 'MISS_SYMBOL', + extra = { + symbol = 'end', + related = { + { + start = 0, + finish = 2, + }, + } + } +} + +TEST[[ +if true then +elseif +end +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +if true then +elseif true +end +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = 'then', + } +} + +TEST[[ + +]] +{ + type = 'EXP_IN_ACTION', +} + +TEST[[ + +]] +{ + type = 'EXP_IN_ACTION', +} + +TEST[[ + +]] +{ + type = 'EXP_IN_ACTION', +} + +TEST[[ + +]] +{ + type = 'EXP_IN_ACTION', +} + +TEST[[ +else +end +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = 'if', + } +} + +TEST[[ +elseif true then +end +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = 'if', + } +} + +TEST[[ +if true then +else +end +]] +{ + type = 'BLOCK_AFTER_ELSE', +} + +TEST[[ +xxxx +]] +{ + type = 'ERR_COMMENT_PREFIX', +} + +TEST[[ + +]] +{ + type = 'ERR_C_LONG_COMMENT', +} + +TEST[[ +a b +]] +{ + type = 'ERR_ASSIGN_AS_EQ', +} + +TEST[[ +_VERSION 1 +]] +{ + type = 'ERR_ASSIGN_AS_EQ', +} + +TEST[[ +return a b +]] +{ + type = 'ERR_EQ_AS_ASSIGN', +} + +TEST[[ +return a b +]] +{ + type = 'ERR_NONSTANDARD_SYMBOL', + extra = { + symbol = '~=', + }, +} + +TEST[[ +if a end +]] +{ + type = 'ERR_THEN_AS_DO' +} + +TEST[[ +while true end +]] +{ + type = 'ERR_DO_AS_THEN', +} + +TEST[[ +return { + _VERSION = '', +} +]] +(nil) + +TEST[[ +return { + _VERSION == '', +} +]] +(nil) + +-- 以下测试来自 https://github.com/andremm/lua-parser/blob/master/test.lua +TEST[[ +f = 9e +]] +{ + type = 'MISS_EXPONENT', +} + +TEST[[ +f = 5.e +]] +{ + type = 'MISS_EXPONENT', +} + +TEST[[ +f = .9e- +]] +{ + type = 'MISS_EXPONENT', +} + +TEST[[ +f = 5.9e+ +]] +{ + type = 'MISS_EXPONENT', +} + +TEST[[ +hex = 0xG +]] +{ + type = 'MUST_X16', + multi = 1, +} + +TEST[=============[ +--[==[ +testing long string3 begin +]==] + +ls3 = [===[ +testing +unfinised +long string +]==] + +--[==[ +[[ testing long string3 end ]] +]==] +]=============] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = ']===]', + } +} + +TEST[[ +-- short string test begin + +ss6 = "testing unfinished string + +-- short string test end +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = '"' + } +} + +TEST[[ +-- short string test begin + +ss7 = 'testing \\ +unfinished \\ +string' + +-- short string test end +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = "'", + }, + multi = 1, +} + +TEST[[ +--[[ testing +unfinished +comment +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = ']]', + } +} + +TEST[[ +--[=[xxx]==] +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = ']=]', + }, +} + +TEST[[ +a = function (a,b,) end +]] +{ + type = 'MISS_NAME', +} + +TEST[[ +a = function (...,) end +]] +{ + type = 'ARGS_AFTER_DOTS', +} + +TEST[[ +local a = function () end +]] +{ + type = 'UNKNOWN_SYMBOL', + extra = { + symbol = '1', + }, +} + +TEST[[ +local test = function ( a , b , c , ... ) +]] +{ + multi = 1, + type = 'MISS_SYMBOL', + extra = { + symbol = 'end', + related = { + { + start = 13, + finish = 21, + }, + } + } +} + +TEST[[ +local test = ( a , b , c , ... ) +]] +{ + multi = 2, + type = 'MISS_END', +} + +TEST[[ +a = 3 / 2 +]] +{ + type = 'UNKNOWN_SYMBOL', + extra = { + symbol = '/' + } +} + +TEST[[ +b = 1 1 +]] +{ + type = 'ERR_NONSTANDARD_SYMBOL', + extra = { + symbol = 'and', + } +} + +TEST[[ +b = 1 <!> 0 +]] +{ + type = 'UNKNOWN_SYMBOL', + extra = { + symbol = '>', + } +} + +TEST[[ +b = 1 < 0 +]] +{ + type = 'UNKNOWN_SYMBOL', + extra = { + symbol = '<', + } +} + +TEST[[ +concat2 = 2^3. +]] +{ + type = 'MALFORMED_NUMBER', +} + +TEST[[ +for k;v in pairs(t) do end +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = 'in', + }, + multi = 1, +} + +TEST[[ +for k,v in pairs(t:any) do end +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = '(', + }, +} + +TEST[[ +for i=1,10, do end +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +for i=1,n:number do end +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = '(', + }, +} + +TEST[[ +function func(a,b,c,) end +]] +{ + type = 'MISS_NAME', +} + +TEST[[ +function func(...,) end +]] +{ + type = 'ARGS_AFTER_DOTS' +} + +TEST[[ +function a.b:c:d () end +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = '(', + } +} + +TEST[[ +if a then +]] +{ + multi = 1, + type = 'MISS_SYMBOL', + extra = { + symbol = 'end', + related = { + { + start = 0, + finish = 2, + }, + } + } +} + +TEST[[ + a then +]] +{ + multi = 2, + type = 'MISS_END', +} + +TEST[[ +if a then else +]] +{ + multi = 1, + type = 'MISS_SYMBOL', + extra = { + symbol = 'end', + related = { + { + start = 0, + finish = 2, + }, + } + } +} + +TEST[[ + a then else +]] +{ + multi = 2, + type = 'MISS_END', +} + +TEST[[ +if a then + return a +elseif b then + return b +elseif + +end +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +if a:any then else end +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = '(', + } +} + +TEST[[ +:: blah :: +:: :: +]] +{ + type = 'KEYWORD', +} + +TEST[[ +local function () +end +]] +{ + type = 'UNEXPECT_LFUNC_NAME' +} + +TEST [[ +f() 1 +]] +{ + multi = 1, + type = 'UNKNOWN_SYMBOL', + extra = { + symbol = '=', + } +} + +Version = 'Lua 5.1' +TEST[[ + +]] +{ + type = 'UNSUPPORT_SYMBOL', + version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, + extra = { + version = 'Lua 5.1', + } +} + +TEST[[ +local goto = 1 +]] +(nil) + +TEST[[ +local x = '' +]] +{ + type = 'ERR_ESC', + version = {'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, + extra = { + version ='Lua 5.1', + } +} + +TEST[[ +local x = '' +]] +{ + type = 'ERR_ESC', + version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, + extra = { + version = 'Lua 5.1', + } +} + +Version = 'Lua 5.2' +TEST[[ +local x = 1 2 +]] +{ + type = 'UNSUPPORT_SYMBOL', + version = {'Lua 5.3', 'Lua 5.4'}, + extra = { + version = 'Lua 5.2', + } +} + +TEST[[ +local x = 1 2 +]] +{ + type = 'UNSUPPORT_SYMBOL', + version = {'Lua 5.3', 'Lua 5.4'}, + extra = { + version = 'Lua 5.2', + } +} + +TEST[[ +local x = '' +]] +{ + type = 'ERR_ESC', + version = {'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, + extra = { + version = 'Lua 5.2', + } +} + +TEST[[ +while true do + break + x = 1 +end +]] +(nil) + +Version = 'Lua 5.3' + +TEST[[ +local x !> = 1 +]] +{ + type = 'UNSUPPORT_SYMBOL', + version = 'Lua 5.4', + extra = { + version = 'Lua 5.3', + } +} + + +Version = 'Lua 5.4' + +TEST[[ +local x <> = 1 +]] +(nil) + +TEST[[ +s = '' +]] +(nil) + + +TEST[[ +s = '\u' +]] +{ + type = 'UTF8_MAX', + extra = { + min = '00000000', + max = '7FFFFFFF', + } +} + +TEST[[ +x = 42 +]] +{ + type = 'UNSUPPORT_SYMBOL', + version = 'LuaJIT', + extra = { + version = 'Lua 5.4', + } +} + +TEST[[ +x = +]] +{ + type = 'UNSUPPORT_SYMBOL', + version = 'LuaJIT', + extra = { + version = 'Lua 5.4', + } +} + +TEST[[ +x = 12.5 +]] +{ + type = 'UNSUPPORT_SYMBOL', + version = 'LuaJIT', + extra = { + version = 'Lua 5.4', + } +} + +TEST[[ +x = 1.23 +]] +{ + type = 'MALFORMED_NUMBER', +} + +Version = 'LuaJIT' + +TEST[[ +x = 42LL +x = 42ULl +x = 0x2aLL +x = 0x2All +x = 12.5i +x = 1I +x = 18446744073709551615ULL +x = 0b11011 +]] +(nil) + +TEST[[ +x = 1.23 +]] +{ + type = 'MALFORMED_NUMBER', +} + +TEST[[ +x = 0b1 +]] +{ + type = 'MALFORMED_NUMBER', +} + TEST [[ local = 1 ]] @@ -203,7 +1764,7 @@ goto ]] { type = 'NO_VISIBLE_LABEL', - info = { + extra = { label = 'label', } } @@ -214,7 +1775,7 @@ do do do goto end end end ]] { type = 'NO_VISIBLE_LABEL', - info = { + extra = { label = 'label', } } @@ -227,7 +1788,7 @@ end ]] { type = 'NO_VISIBLE_LABEL', - info = { + extra = { label = 'label', } } @@ -240,7 +1801,7 @@ x = 2 ]] { type = 'JUMP_LOCAL_SCOPE', - info = { + extra = { loc = 'x', }, relative = { @@ -263,7 +1824,7 @@ return x ]] { type = 'JUMP_LOCAL_SCOPE', - info = { + extra = { loc = 'x', }, relative = { From 74dc0d5f2416351935cc9853f5fd30cf5f197035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 27 Sep 2023 18:44:27 +0800 Subject: [PATCH 59/94] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/error.lua | 24 ++++++++++++ src/parser/ast/exp.lua | 24 +++++++++++- src/parser/ast/function.lua | 12 ++---- src/parser/ast/state.lua | 15 ++++++- src/parser/ast/table.lua | 7 ++-- test/syntax_check.lua | 78 ++++++++++++++----------------------- 6 files changed, 98 insertions(+), 62 deletions(-) diff --git a/src/parser/ast/error.lua b/src/parser/ast/error.lua index 5826d93..65cb904 100644 --- a/src/parser/ast/error.lua +++ b/src/parser/ast/error.lua @@ -50,3 +50,27 @@ function Ast:assertSymbol(symbol) end return pos end + +-- 断言下个符号是 `end`,如果成功则消耗,否则报错 +---@private +---@param relatedStart integer +---@param relatedFinish integer +---@return integer? pos +function Ast:assertSymbolEnd(relatedStart, relatedFinish) + local pos = self.lexer:consume 'end' + if not pos then + local lastPos = self:getLastPos() + self:throw('MISS_SYMBOL', lastPos, lastPos, { + symbol = 'end', + related = { + start = relatedStart, + finish = relatedFinish, + } + }) + self:throw('MISS_END', relatedStart, relatedFinish, { + start = lastPos, + finish = lastPos, + }) + end + return pos +end diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index 9d8fd01..a6e3fc1 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -91,16 +91,24 @@ function Ast:parseExpList(atLeastOne, greedy) local list = {} local first = self:parseExp(atLeastOne) list[#list+1] = first + local wantSep = first ~= nil while true do self:skipSpace() - local token, tp = self.lexer:peek() + local token, tp, pos = self.lexer:peek() if not token then break end + ---@cast pos -? if tp == 'Symbol' then if token == ',' then + if not wantSep then + self:throw('UNEXPECT_SYMBOL', pos, pos + 1, { + symbol = ',', + }) + end self.lexer:next() self:skipSpace() + wantSep = false else break end @@ -117,6 +125,7 @@ function Ast:parseExpList(atLeastOne, greedy) if exp then list[#list+1] = exp end + wantSep = true end return list end @@ -159,6 +168,12 @@ function Ast:parseTerm() return nil end + if head.type == 'Function' then + if head.name then + self:throw('UNEXPECT_EFUNC_NAME', head.name.start, head.name.finish) + end + end + ---@type LuaParser.Node.Term local current = head @@ -168,6 +183,13 @@ function Ast:parseTerm() local chain = self:parseField(current) or self:parseCall(current) + if current.type == 'Field' + and current.subtype == 'method' then + if not chain or chain.type ~= 'Call' then + self:throwMissSymbol(current.finish, '(') + end + end + if chain then current = chain else diff --git a/src/parser/ast/function.lua b/src/parser/ast/function.lua index f715355..72ede08 100644 --- a/src/parser/ast/function.lua +++ b/src/parser/ast/function.lua @@ -43,21 +43,17 @@ function Ast:parseFunction(isLocal) self:skipSpace() local symbolPos1 = self.lexer:consume '(' - if not name and not symbolPos1 then - return nil - end - local params + local params, symbolPos2 if symbolPos1 then self:skipSpace() params = self:parseParamList() + self:skipSpace() + symbolPos2 = self:assertSymbol ')' else self:throwMissSymbol(self:getLastPos(), '(') end - self:skipSpace() - local symbolPos2 = self:assertSymbol ')' - local func = self:createNode('LuaParser.Node.Function', { start = pos, name = name, @@ -81,7 +77,7 @@ function Ast:parseFunction(isLocal) end self:skipSpace() - local symbolPos3 = self:assertSymbol 'end' + local symbolPos3 = self:assertSymbolEnd(pos, pos + #'function') func.symbolPos3 = symbolPos3 func.finish = self:getLastPos() diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua index 953f9d6..cf1180e 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state.lua @@ -35,7 +35,7 @@ local Ast = class.declare 'LuaParser.Ast' ---@private ---@return LuaParser.Node.State? function Ast:parseState() - local token = self.lexer:peek() + local token, _, pos = self.lexer:peek() if token == 'local' then return self:parseLocal() @@ -68,7 +68,15 @@ function Ast:parseState() end if token == 'function' then - return self:parseFunction() + local func = self:parseFunction() + if not func then + self:throw('MISS_NAME', pos + #token) + return nil + end + if not func.name then + self:throw('MISS_NAME', func.symbolPos1) + end + return func end if token == 'continue' then @@ -192,6 +200,9 @@ function Ast:extendsAssignValues(values, varCount) return end local lastValue = values[#values] + if not lastValue then + return + end if lastValue.type ~= 'Call' and lastValue.type ~= 'Varargs' then return end diff --git a/src/parser/ast/table.lua b/src/parser/ast/table.lua index db0e707..d202bf5 100644 --- a/src/parser/ast/table.lua +++ b/src/parser/ast/table.lua @@ -51,20 +51,21 @@ function Ast:parseTableFields() local fields = {} local wantSep = false while true do - local token = self.lexer:peek() + local token, _, pos = self.lexer:peek() if not token or token == '}' then break end if token == ',' or token == ';' then - self.lexer:next() if not wantSep then self:throwMissExp(self:getLastPos()) end wantSep = false + self.lexer:next() else if wantSep then - self:throw('MISS_SEP_IN_TABLE', self:getLastPos()) + local lastField = fields[#fields] + self:throw('MISS_SEP_IN_TABLE', lastField.finish, pos) end wantSep = true local field = self:parseTableField() diff --git a/test/syntax_check.lua b/test/syntax_check.lua index 7480a08..8dd180a 100644 --- a/test/syntax_check.lua +++ b/test/syntax_check.lua @@ -318,7 +318,7 @@ f(1,) } TEST[[ -f(11) +f(1 1) ]] { type = 'MISS_SYMBOL', @@ -348,14 +348,14 @@ TEST[[ x.() ]] { - type = 'MISS_FIELD', + type = 'MISS_NAME', } TEST[[ x:() ]] { - type = 'MISS_METHOD', + type = 'MISS_NAME', } TEST[[ @@ -516,10 +516,8 @@ function f() extra = { symbol = 'end', related = { - { - start = 0, - finish = 8, - } + start = 0, + finish = 8, }, } } @@ -530,13 +528,17 @@ TEST[[ { multi = 2, type = 'MISS_END', + extra = { + start = 12, + finish = 12, + } } TEST[[ function f:() end ]] { - type = 'MISS_METHOD', + type = 'MISS_NAME', } TEST[[ @@ -629,10 +631,8 @@ for k, v in next do extra = { symbol = 'end', related = { - { - start = 0, - finish = 3, - }, + start = 0, + finish = 3, } } } @@ -738,10 +738,8 @@ for i = 1, 2 do extra = { symbol = 'end', related = { - { - start = 0, - finish = 3, - }, + start = 0, + finish = 3, } } } @@ -782,10 +780,8 @@ while true do extra = { symbol = 'end', related = { - { - start = 0, - finish = 5, - }, + start = 0, + finish = 5, } } } @@ -844,10 +840,8 @@ if true then extra = { symbol = 'end', related = { - { - start = 0, - finish = 2, - }, + start = 0, + finish = 2, } } } @@ -870,10 +864,8 @@ else extra = { symbol = 'end', related = { - { - start = 0, - finish = 2, - }, + start = 0, + finish = 2, } } } @@ -1179,10 +1171,8 @@ local test = function ( a , b , c , ... ) extra = { symbol = 'end', related = { - { - start = 13, - finish = 21, - }, + start = 13, + finish = 21, } } } @@ -1313,10 +1303,8 @@ if a then extra = { symbol = 'end', related = { - { - start = 0, - finish = 2, - }, + start = 0, + finish = 2, } } } @@ -1338,10 +1326,8 @@ if a then else extra = { symbol = 'end', related = { - { - start = 0, - finish = 2, - }, + start = 0, + finish = 2, } } } @@ -1847,10 +1833,8 @@ TEST[[ { type = 'REDEFINED_LABEL', related = { - { - start = 3, - finish = 7, - }, + start = 3, + finish = 7, } } @@ -1865,10 +1849,8 @@ end { type = 'REDEFINED_LABEL', related = { - { - start = 3, - finish = 7, - }, + start = 3, + finish = 7, } } From c1682cca414bcec3d26f7b87e8c21f6f8787146a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 27 Sep 2023 18:55:57 +0800 Subject: [PATCH 60/94] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/function.lua | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/parser/ast/function.lua b/src/parser/ast/function.lua index 72ede08..c46cc39 100644 --- a/src/parser/ast/function.lua +++ b/src/parser/ast/function.lua @@ -115,6 +115,13 @@ function Ast:parseFunctionName() local chain = self:parseField(current) + if current.type == 'Field' + and current.subtype == 'method' then + if chain then + self:throwMissSymbol(current.finish, '(') + end + end + if chain then current = chain else @@ -133,6 +140,7 @@ function Ast:parseParamList() local first = self:parseParam() list[#list+1] = first + local wantSep = first ~= nil while true do self:skipSpace() local token, tp = self.lexer:peek() @@ -141,26 +149,43 @@ function Ast:parseParamList() end if tp == 'Symbol' then if token == ',' then + if not wantSep then + self:throw('MISS_NAME', self:getLastPos()) + end self.lexer:next() self:skipSpace() + wantSep = false else break end else self:throwMissSymbol(self:getLastPos(), ',') end - local param = self:parseParam() + local param = self:parseParam(true) if param then list[#list+1] = param end + wantSep = true + end + + for i = 1, #list do + local param = list[i] + if param.id == '...' then + for j = i + 1, #list do + param = list[j] + self:throw('ARGS_AFTER_DOTS', param.start, param.finish) + end + break + end end return list end ---@private +---@param required? boolean ---@return LuaParser.Node.Param? -function Ast:parseParam() +function Ast:parseParam(required) local param = self:parseID('LuaParser.Node.Param') if param then return param @@ -173,5 +198,8 @@ function Ast:parseParam() id = '...', }) end + if required then + self:throw('MISS_NAME', self:getLastPos()) + end return nil end From 22457c577903e0a099821ad70f7dbece1fd58bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 28 Sep 2023 16:31:02 +0800 Subject: [PATCH 61/94] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/block.lua | 30 +++++++++++++----- src/parser/ast/exp.lua | 10 +++--- src/parser/ast/field.lua | 29 +++++++++--------- src/parser/ast/for.lua | 14 +++++++-- src/parser/ast/id.lua | 14 ++++++--- src/parser/ast/if.lua | 34 ++++++++++++--------- src/parser/ast/label.lua | 29 +++++++++--------- src/parser/ast/main.lua | 4 +-- src/parser/ast/state.lua | 12 +++++--- src/parser/ast/while.lua | 7 +++-- test/syntax_check.lua | 66 +++++++++++++++------------------------- 11 files changed, 134 insertions(+), 115 deletions(-) diff --git a/src/parser/ast/block.lua b/src/parser/ast/block.lua index e3488b4..430f958 100644 --- a/src/parser/ast/block.lua +++ b/src/parser/ast/block.lua @@ -3,10 +3,12 @@ local class = require 'class' ---@class LuaParser.Node.Block: LuaParser.Node.Base ---@field childs LuaParser.Node.State[] ---@field locals LuaParser.Node.Local[] +---@field isMain boolean ---@field private localMap table local Block = class.declare('LuaParser.Node.Block', 'LuaParser.Node.Base') Block.isBlock = true +Block.isMain = false Block.__getter.childs = function () return {}, true @@ -63,17 +65,29 @@ end ---@param block LuaParser.Node.Block function Ast:blockParseChilds(block) while true do - local token = self.lexer:peek() - if FinishMap[token] then + while self.lexer:consume ';' do + self:skipSpace() + end + local token, _, pos = self.lexer:peek() + if not token then break end - while self.lexer:consume ';' do end - local state = self:parseState() - if not state then + ---@cast pos -? + if FinishMap[token] and not block.isMain then break end - state.parent = block - block.childs[#block.childs+1] = state - self:skipSpace() + local state = self:parseState() + if state then + state.parent = block + block.childs[#block.childs+1] = state + self:skipSpace() + else + if block.isMain then + self.lexer:next() + self:throw('UNKNOWN_SYMBOL', pos, pos + #token) + else + break + end + end end end diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index a6e3fc1..47d426c 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -1,7 +1,7 @@ local class = require 'class' ---@class LuaParser.Node.Paren: LuaParser.Node.Base ----@field exp LuaParser.Node.Exp +---@field exp? LuaParser.Node.Exp ---@field next? LuaParser.Node.Field local Paren = class.declare('LuaParser.Node.Paren', 'LuaParser.Node.Base') @@ -208,14 +208,14 @@ function Ast:parseParen() return nil end local exp = self:parseExp(true) - if not exp then - return nil - end local paren = self:createNode('LuaParser.Node.Paren', { start = pos, exp = exp, }) - exp.parent = paren + if exp then + exp.parent = paren + end + self:skipSpace() self:assertSymbol ')' paren.finish = self:getLastPos() diff --git a/src/parser/ast/field.lua b/src/parser/ast/field.lua index 0a33147..cf525ee 100644 --- a/src/parser/ast/field.lua +++ b/src/parser/ast/field.lua @@ -68,23 +68,24 @@ function Ast:parseField(last) self.lexer:next() self:skipSpace() local key = self:parseExp(true) + + self:skipSpace() + local symbolPos2 = self:assertSymbol(']') + local field = self:createNode('LuaParser.Node.Field', { + start = last.start, + finish = self:getLastPos(), + subtype = 'index', + key = key, + last = last, + symbolPos = pos, + symbolPos2 = symbolPos2, + }) + last.parent = field + last.next = field if key then - self:skipSpace() - local symbolPos2 = self:assertSymbol(']') - local field = self:createNode('LuaParser.Node.Field', { - start = last.start, - finish = self:getLastPos(), - subtype = 'index', - key = key, - last = last, - symbolPos = pos, - symbolPos2 = symbolPos2, - }) - last.parent = field - last.next = field key.parent = field - return field end + return field end return nil end diff --git a/src/parser/ast/for.lua b/src/parser/ast/for.lua index 288029d..52b9364 100644 --- a/src/parser/ast/for.lua +++ b/src/parser/ast/for.lua @@ -46,6 +46,7 @@ function Ast:parseFor() elseif token == 'in' then forNode.subtype = 'in' else + self:throwMissSymbol(self:getLastPos(), 'in') return forNode end self.lexer:next() @@ -59,6 +60,12 @@ function Ast:parseFor() exp.index = i end + if forNode.subtype == 'loop' then + if #exps == 1 then + self:throw('MISS_LOOP_MAX', self:getLastPos()) + end + end + self:skipSpace() local symbolPos2 = self:assertSymbol 'do' @@ -71,11 +78,12 @@ function Ast:parseFor() self:blockParseChilds(forNode) self:blockFinish(forNode) - self:skipSpace() - local symbolPos3 = self:assertSymbol 'end' - forNode.symbolPos3 = symbolPos3 end + self:skipSpace() + local symbolPos3 = self:assertSymbolEnd(pos, pos + #'for') + forNode.symbolPos3 = symbolPos3 + forNode.finish = self:getLastPos() return forNode diff --git a/src/parser/ast/id.lua b/src/parser/ast/id.lua index 38410d8..855d1ec 100644 --- a/src/parser/ast/id.lua +++ b/src/parser/ast/id.lua @@ -16,8 +16,9 @@ local Ast = class.declare 'LuaParser.Ast' ---@generic T: LuaParser.Node.ID ---@param nodeType `T` ---@param required? true +---@param canBeKeyword? true ---@return T? -function Ast:parseID(nodeType, required) +function Ast:parseID(nodeType, required, canBeKeyword) local token, tp, pos = self.lexer:peek() if tp ~= 'Word' then if required then @@ -27,16 +28,19 @@ function Ast:parseID(nodeType, required) end ---@cast token -? ---@cast pos -? - if not self.unicodeName and token:find '[\x80-\xff]' then - self:throw('UNICODE_NAME', pos, pos + #token) - end if self:isKeyWord(token) then - if required then + if canBeKeyword then self:throw('KEYWORD', pos, pos + #token) else + if required then + self:throw('MISS_NAME', self:getLastPos()) + end return nil end end + if not self.unicodeName and token:find '[\x80-\xff]' then + self:throw('UNICODE_NAME', pos, pos + #token) + end self.lexer:next() return self:createNode(nodeType or 'LuaParser.Node.Var', { id = token, diff --git a/src/parser/ast/if.lua b/src/parser/ast/if.lua index 3e1d942..700c58e 100644 --- a/src/parser/ast/if.lua +++ b/src/parser/ast/if.lua @@ -21,6 +21,8 @@ function Ast:parseIf() return nil end + ---@cast pos -? + local ifNode = self:createNode('LuaParser.Node.If', { start = pos, childs = {}, @@ -36,7 +38,7 @@ function Ast:parseIf() end self:skipSpace() - self:assertSymbol 'end' + self:assertSymbolEnd(pos, pos + #'if') ifNode.finish = self:getLastPos() @@ -60,7 +62,7 @@ function Ast:parseIfChildIf() end self:skipSpace() - local condition = self:parseExp() + local condition = self:parseExp(true) local node = self:createNode('LuaParser.Node.IfChild', { subtype = 'if', @@ -68,16 +70,17 @@ function Ast:parseIfChildIf() condition = condition, }) + self:skipSpace() if condition then - self:skipSpace() node.symbolPos = self:assertSymbol 'then' - - self:skipSpace() - self:blockStart(node) - self:blockParseChilds(node) - self:blockFinish(node) + else + node.symbolPos = self.lexer:consume 'then' end + self:skipSpace() + self:blockStart(node) + self:blockParseChilds(node) + self:blockFinish(node) node.finish = self:getLastPos() return node @@ -92,7 +95,7 @@ function Ast:parseIfChildElseIf() end self:skipSpace() - local condition = self:parseExp() + local condition = self:parseExp(true) local node = self:createNode('LuaParser.Node.IfChild', { subtype = 'elseif', @@ -100,16 +103,17 @@ function Ast:parseIfChildElseIf() condition = condition, }) + self:skipSpace() if condition then - self:skipSpace() node.symbolPos = self:assertSymbol 'then' - - self:skipSpace() - self:blockStart(node) - self:blockParseChilds(node) - self:blockFinish(node) + else + node.symbolPos = self.lexer:consume 'then' end + self:skipSpace() + self:blockStart(node) + self:blockParseChilds(node) + self:blockFinish(node) node.finish = self:getLastPos() return node diff --git a/src/parser/ast/label.lua b/src/parser/ast/label.lua index 11490d5..845aad4 100644 --- a/src/parser/ast/label.lua +++ b/src/parser/ast/label.lua @@ -1,7 +1,7 @@ local class = require 'class' ---@class LuaParser.Node.Label: LuaParser.Node.Base ----@field label LuaParser.Node.LabelName +---@field label? LuaParser.Node.LabelName ---@field symbolPos? integer # 右边标签符号的位置 local Label = class.declare('LuaParser.Node.Label', 'LuaParser.Node.Base') @@ -11,7 +11,7 @@ local Label = class.declare('LuaParser.Node.Label', 'LuaParser.Node.Base') local LabelName = class.declare('LuaParser.Node.LabelName', 'LuaParser.Node.Base') ---@class LuaParser.Node.Goto: LuaParser.Node.Base ----@field label LuaParser.Node.LabelName +---@field label? LuaParser.Node.LabelName local Goto = class.declare('LuaParser.Node.Goto', 'LuaParser.Node.Base') ---@class LuaParser.Ast @@ -27,12 +27,12 @@ function Ast:parseLabel() self:skipSpace() local labelName = self:parseID('LuaParser.Node.LabelName', true) - if not labelName then - return nil - end - self:skipSpace() - local symbolPos = self:assertSymbol '::' + local symbolPos + if labelName then + self:skipSpace() + symbolPos = self:assertSymbol '::' + end local label = self:createNode('LuaParser.Node.Label', { start = pos, @@ -40,8 +40,10 @@ function Ast:parseLabel() symbolPos = symbolPos, }) - label.label = labelName - labelName.parent = label + if labelName then + label.label = labelName + labelName.parent = label + end return label end @@ -59,17 +61,16 @@ function Ast:parseGoto() self:skipSpace() local labelName = self:parseID('LuaParser.Node.LabelName', true) - if not labelName then - return nil - end local gotoNode = self:createNode('LuaParser.Node.Goto', { start = pos, finish = self:getLastPos(), }) - gotoNode.label = labelName - labelName.parent = gotoNode + if labelName then + gotoNode.label = labelName + labelName.parent = gotoNode + end return gotoNode end diff --git a/src/parser/ast/main.lua b/src/parser/ast/main.lua index fe7ce00..561af9f 100644 --- a/src/parser/ast/main.lua +++ b/src/parser/ast/main.lua @@ -22,9 +22,9 @@ function Ast:skipShebang() if self.code:sub(1, 1) == '#' then local pos = self.code:find('\n', 2, true) if pos then - self.lexer:fastForward(pos + 1) + self.lexer:fastForward(pos) else - self.lexer:fastForward(#self.code + 1) + self.lexer:fastForward(#self.code) end end end diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua index cf1180e..f7c787f 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state.lua @@ -6,7 +6,7 @@ local class = require 'class' ---@field values LuaParser.Node.Var[] local Assign = class.declare('LuaParser.Node.Assign', 'LuaParser.Node.Base') ----@class LuaParser.Node.SingleExp +---@class LuaParser.Node.SingleExp: LuaParser.Node.Base ---@field exp LuaParser.Node.Exp local SingleExp = class.declare('LuaParser.Node.SingleExp', 'LuaParser.Node.Base') @@ -41,9 +41,7 @@ function Ast:parseState() return self:parseLocal() end - if token == 'if' - or token == 'elseif' - or token == 'else' then + if token == 'if' then return self:parseIf() end @@ -133,6 +131,12 @@ function Ast:parseStateStartWithExp() }) exp.parent = state + if exp.type == 'Field' and exp.subtype == 'method' then + -- 已经throw过"缺少 `(`"" + else + self:throw('EXP_IN_ACTION', state.start, state.finish) + end + return state end diff --git a/src/parser/ast/while.lua b/src/parser/ast/while.lua index 3a73ba3..cf3de55 100644 --- a/src/parser/ast/while.lua +++ b/src/parser/ast/while.lua @@ -39,11 +39,12 @@ function Ast:parseWhile() self:blockParseChilds(whileNode) self:blockFinish(whileNode) - self:skipSpace() - local symbolPos2 = self:assertSymbol 'end' - whileNode.symbolPos2 = symbolPos2 end + self:skipSpace() + local symbolPos2 = self:assertSymbolEnd(pos, pos + #'while') + whileNode.symbolPos2 = symbolPos2 + whileNode.finish = self:getLastPos() return whileNode diff --git a/test/syntax_check.lua b/test/syntax_check.lua index 8dd180a..88ee15c 100644 --- a/test/syntax_check.lua +++ b/test/syntax_check.lua @@ -26,8 +26,8 @@ local function TEST(script) end assert(first) assert(first.code == expect.type) - assert(first.start == target[1]) - assert(first.finish == target[2]) + assert(first.left == target[1]) + assert(first.right == target[2]) assert(util.equal(first.extra, expect.extra)) end end @@ -41,7 +41,7 @@ local } TEST[[ - +local ]] { type = 'UNICODE_NAME', @@ -643,18 +643,19 @@ TEST[[ { multi = 2, type = 'MISS_END', + extra = { + start = 19, + finish = 19, + } } TEST[[ -for i = 2 do +for i =, 2 do end ]] { multi = 1, - type = 'UNEXPECT_SYMBOL', - extra = { - symbol = ',', - } + type = 'MISS_EXP', } TEST[[ @@ -678,7 +679,7 @@ for i = 1, do end ]] { - --multi = 2, + multi = 1, type = 'MISS_EXP' } @@ -687,16 +688,7 @@ for i = do end ]] { - type = 'MISS_LOOP_MIN' -} - -TEST[[ -for i = 1, do -end -]] -{ - --multi = 1, - type = 'MISS_EXP', + type = 'MISS_EXP' } TEST[[ @@ -708,7 +700,7 @@ end } TEST[[ -for i = 1, 23 do +for i = 1, 2 3 do end ]] { @@ -750,6 +742,10 @@ TEST[[ { multi = 2, type = 'MISS_END', + extra = { + start = 15, + finish = 15, + } } TEST[[ @@ -792,6 +788,10 @@ TEST[[ { multi = 2, type = 'MISS_END', + extra = { + start = 13, + finish = 13, + } } TEST[[ @@ -852,6 +852,10 @@ TEST[[ { multi = 2, type = 'MISS_END', + extra = { + start = 12, + finish = 12, + } } TEST[[ @@ -919,28 +923,6 @@ TEST[[ type = 'EXP_IN_ACTION', } -TEST[[ -else -end -]] -{ - type = 'MISS_SYMBOL', - extra = { - symbol = 'if', - } -} - -TEST[[ -elseif true then -end -]] -{ - type = 'MISS_SYMBOL', - extra = { - symbol = 'if', - } -} - TEST[[ if true then else From ffe23ebc78390feb04ea6c3ba63857d73ddc9a10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 28 Sep 2023 17:46:40 +0800 Subject: [PATCH 62/94] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/binary.lua | 4 +++- src/parser/ast/comment.lua | 7 +++++-- src/parser/ast/error.lua | 38 ++++++++++++++++++++++++++++++++++++++ src/parser/ast/for.lua | 2 +- src/parser/ast/if.lua | 20 ++++++++++---------- src/parser/ast/string.lua | 4 +++- src/parser/ast/unary.lua | 4 +++- src/parser/ast/while.lua | 9 ++++----- test/syntax_check.lua | 22 ++++------------------ 9 files changed, 71 insertions(+), 39 deletions(-) diff --git a/src/parser/ast/binary.lua b/src/parser/ast/binary.lua index 969b7d3..13c55bd 100644 --- a/src/parser/ast/binary.lua +++ b/src/parser/ast/binary.lua @@ -90,7 +90,9 @@ function Ast:parseBinary(curExp, curLevel, asState) end if BinaryAlias[op] then if not self.nssymbolMap[op] then - self:throw('ERR_NONSTANDARD_SYMBOL', pos, pos + #op) + self:throw('ERR_NONSTANDARD_SYMBOL', pos, pos + #op, { + symbol = BinaryAlias[op] + }) end op = BinaryAlias[op] end diff --git a/src/parser/ast/comment.lua b/src/parser/ast/comment.lua index 0cc8eae..16572ba 100644 --- a/src/parser/ast/comment.lua +++ b/src/parser/ast/comment.lua @@ -76,7 +76,7 @@ function Ast:parseLongComment() elseif token == '/*' then finishQuo = '*/' if not self.nssymbolMap['/**/'] then - self:throw('ERR_C_LONG_COMMENT', pos, pos + 1) + self:throw('ERR_C_LONG_COMMENT', pos, pos + 2) end else return nil @@ -88,9 +88,12 @@ function Ast:parseLongComment() self:throwMissSymbol(#self.code, finishQuo) self.lexer:fastForward(#self.code) end + + local finish = offset and (offset + #finishQuo - 1) or #self.code + return self:createNode('LuaParser.Node.Comment', { subtype = 'long', start = pos, - finish = offset and (offset + #finishQuo - 1) or #self.code, + finish = finish, }) end diff --git a/src/parser/ast/error.lua b/src/parser/ast/error.lua index 65cb904..775fa33 100644 --- a/src/parser/ast/error.lua +++ b/src/parser/ast/error.lua @@ -74,3 +74,41 @@ function Ast:assertSymbolEnd(relatedStart, relatedFinish) end return pos end + +---@private +---@param needThrowMissSymbol? boolean +---@return integer? pos +function Ast:assertSymbolThen(needThrowMissSymbol) + local pos = self.lexer:consume 'then' + if pos then + return pos + end + pos = self.lexer:consume 'do' + if pos then + self:throw('ERR_THEN_AS_DO', pos, pos + #'do') + return pos + end + if needThrowMissSymbol then + self:throwMissSymbol(self:getLastPos(), 'then') + end + return nil +end + +---@private +---@param needThrowMissSymbol? boolean +---@return integer? pos +function Ast:assertSymbolDo(needThrowMissSymbol) + local pos = self.lexer:consume 'do' + if pos then + return pos + end + pos = self.lexer:consume 'then' + if pos then + self:throw('ERR_DO_AS_THEN', pos, pos + #'then') + return pos + end + if needThrowMissSymbol then + self:throwMissSymbol(self:getLastPos(), 'do') + end + return nil +end diff --git a/src/parser/ast/for.lua b/src/parser/ast/for.lua index 52b9364..bba2b09 100644 --- a/src/parser/ast/for.lua +++ b/src/parser/ast/for.lua @@ -67,7 +67,7 @@ function Ast:parseFor() end self:skipSpace() - local symbolPos2 = self:assertSymbol 'do' + local symbolPos2 = self:assertSymbolDo(true) if symbolPos2 then forNode.symbolPos2 = symbolPos2 diff --git a/src/parser/ast/if.lua b/src/parser/ast/if.lua index 700c58e..05f6dd8 100644 --- a/src/parser/ast/if.lua +++ b/src/parser/ast/if.lua @@ -28,6 +28,7 @@ function Ast:parseIf() childs = {}, }) + local hasElse while true do local child = self:parseIfChild() if not child then @@ -35,6 +36,13 @@ function Ast:parseIf() end child.parent = ifNode ifNode.childs[#ifNode.childs+1] = child + + if hasElse then + self:throw('BLOCK_AFTER_ELSE', child.start, child.start + #child.subtype) + end + if child.subtype == 'else' then + hasElse = true + end end self:skipSpace() @@ -71,11 +79,7 @@ function Ast:parseIfChildIf() }) self:skipSpace() - if condition then - node.symbolPos = self:assertSymbol 'then' - else - node.symbolPos = self.lexer:consume 'then' - end + node.symbolPos = self:assertSymbolThen(condition ~= nil) self:skipSpace() self:blockStart(node) @@ -104,11 +108,7 @@ function Ast:parseIfChildElseIf() }) self:skipSpace() - if condition then - node.symbolPos = self:assertSymbol 'then' - else - node.symbolPos = self.lexer:consume 'then' - end + node.symbolPos = self:assertSymbolThen(condition ~= nil) self:skipSpace() self:blockStart(node) diff --git a/src/parser/ast/string.lua b/src/parser/ast/string.lua index 39e212d..933a828 100644 --- a/src/parser/ast/string.lua +++ b/src/parser/ast/string.lua @@ -83,7 +83,9 @@ function Ast:parseShortString() ---@cast pos -? if quo == '`' and not self.nssymbolMap['`'] then - self:throw('ERR_NONSTANDARD_SYMBOL', pos, pos + 1) + self:throw('ERR_NONSTANDARD_SYMBOL', pos, pos + 1, { + symbol = '"' + }) end local pieces = {} diff --git a/src/parser/ast/unary.lua b/src/parser/ast/unary.lua index b572f8c..033efc1 100644 --- a/src/parser/ast/unary.lua +++ b/src/parser/ast/unary.lua @@ -51,7 +51,9 @@ function Ast:parseUnary() local op = token if UnaryAlias[op] then if not self.nssymbolMap[op] then - self:throw('ERR_NONSTANDARD_SYMBOL', pos, pos + #op) + self:throw('ERR_NONSTANDARD_SYMBOL', pos, pos + #op, { + symbol = UnaryAlias[op] + }) end op = UnaryAlias[op] end diff --git a/src/parser/ast/while.lua b/src/parser/ast/while.lua index cf3de55..5cdc45d 100644 --- a/src/parser/ast/while.lua +++ b/src/parser/ast/while.lua @@ -19,18 +19,17 @@ function Ast:parseWhile() self:skipSpace() local condition = self:parseExp(true) - if not condition then - return nil - end local whileNode = self:createNode('LuaParser.Node.While', { start = pos, condition = condition, }) - condition.parent = whileNode + if condition then + condition.parent = whileNode + end self:skipSpace() - local symbolPos1 = self:assertSymbol 'do' + local symbolPos1 = self:assertSymbolDo(true) if symbolPos1 then whileNode.symbolPos1 = symbolPos1 diff --git a/test/syntax_check.lua b/test/syntax_check.lua index 88ee15c..4258576 100644 --- a/test/syntax_check.lua +++ b/test/syntax_check.lua @@ -926,8 +926,8 @@ TEST[[ TEST[[ if true then else -end + true then +end ]] { type = 'BLOCK_AFTER_ELSE', @@ -941,28 +941,14 @@ TEST[[ } TEST[[ - adadasd -*/!> +*/ ]] { type = 'ERR_C_LONG_COMMENT', } -TEST[[ -a b -]] -{ - type = 'ERR_ASSIGN_AS_EQ', -} - -TEST[[ -_VERSION 1 -]] -{ - type = 'ERR_ASSIGN_AS_EQ', -} - TEST[[ return a b ]] From 71da863d311e0848d3eaa5da519b88aac14aaf43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 28 Sep 2023 18:18:55 +0800 Subject: [PATCH 63/94] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 4 +--- src/parser/ast/function.lua | 42 +++++++++++++++++++++---------------- test/syntax_check.lua | 7 ++++--- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 9db29b3..a109645 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -127,9 +127,7 @@ function M:skipUnknown() end ---@cast pos -? - self:throw('UNKNOWN_SYMBOL', pos, pos + #token, { - symbol = token, - }) + self:throw('UNKNOWN_SYMBOL', pos, pos + #token) return true end diff --git a/src/parser/ast/function.lua b/src/parser/ast/function.lua index c46cc39..d4cff30 100644 --- a/src/parser/ast/function.lua +++ b/src/parser/ast/function.lua @@ -138,34 +138,44 @@ function Ast:parseParamList() ---@type LuaParser.Node.Param[] local list = {} - local first = self:parseParam() - list[#list+1] = first - local wantSep = first ~= nil + local wantExp = true while true do self:skipSpace() - local token, tp = self.lexer:peek() + + local param = self:parseParam() + if param then + list[#list+1] = param + if not wantExp then + self:throwMissSymbol(self:getLastPos(), ',') + end + wantExp = false + goto continue + end + + local token, tp, pos = self.lexer:peek() if not token then break end + ---@cast pos -? if tp == 'Symbol' then if token == ',' then - if not wantSep then + if wantExp then self:throw('MISS_NAME', self:getLastPos()) end self.lexer:next() - self:skipSpace() - wantSep = false + wantExp = true else break end else - self:throwMissSymbol(self:getLastPos(), ',') - end - local param = self:parseParam(true) - if param then - list[#list+1] = param + self:throw('UNKNOWN_SYMBOL', pos, pos + #token) + self.lexer:next() end - wantSep = true + ::continue:: + end + + if wantExp and #list > 0 then + self:throw('MISS_NAME', self:getLastPos()) end for i = 1, #list do @@ -183,9 +193,8 @@ function Ast:parseParamList() end ---@private ----@param required? boolean ---@return LuaParser.Node.Param? -function Ast:parseParam(required) +function Ast:parseParam() local param = self:parseID('LuaParser.Node.Param') if param then return param @@ -198,8 +207,5 @@ function Ast:parseParam(required) id = '...', }) end - if required then - self:throw('MISS_NAME', self:getLastPos()) - end return nil end diff --git a/test/syntax_check.lua b/test/syntax_check.lua index 4258576..8905237 100644 --- a/test/syntax_check.lua +++ b/test/syntax_check.lua @@ -1125,9 +1125,6 @@ local a = function () end ]] { type = 'UNKNOWN_SYMBOL', - extra = { - symbol = '1', - }, } TEST[[ @@ -1151,6 +1148,10 @@ local test = ( a , b , c , ... ) { multi = 2, type = 'MISS_END', + extra = { + start = 41, + finish = 41, + } } TEST[[ From c2738dc9420a73cc95457af9f3d22902abacfdb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Sat, 7 Oct 2023 17:01:59 +0800 Subject: [PATCH 64/94] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/function.lua | 7 +++++++ src/parser/ast/label.lua | 2 +- test/syntax_check.lua | 31 +++++++++++++++---------------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/parser/ast/function.lua b/src/parser/ast/function.lua index d4cff30..4eb5265 100644 --- a/src/parser/ast/function.lua +++ b/src/parser/ast/function.lua @@ -37,6 +37,13 @@ function Ast:parseFunction(isLocal) local name if isLocal then name = self:parseID('LuaParser.Node.Local', true) + local nextToken, _, nextPos = self.lexer:peek() + if nextToken == '.' or nextToken == ':' then + ---@cast nextPos -? + local endPos = self.code:match('[%s%w_%.%:]+()', pos + 1) + self.lexer:fastForward(endPos - 1) + self:throw('UNEXPECT_LFUNC_NAME', nextPos, endPos - 1) + end else name = self:parseFunctionName() end diff --git a/src/parser/ast/label.lua b/src/parser/ast/label.lua index 845aad4..807257c 100644 --- a/src/parser/ast/label.lua +++ b/src/parser/ast/label.lua @@ -26,7 +26,7 @@ function Ast:parseLabel() end self:skipSpace() - local labelName = self:parseID('LuaParser.Node.LabelName', true) + local labelName = self:parseID('LuaParser.Node.LabelName', true, true) local symbolPos if labelName then diff --git a/test/syntax_check.lua b/test/syntax_check.lua index 8905237..7a09dab 100644 --- a/test/syntax_check.lua +++ b/test/syntax_check.lua @@ -1155,13 +1155,10 @@ local test = ( a , b , c , ... ) } TEST[[ -a = 3 / 2 +a = 3 / / 2 ]] { - type = 'UNKNOWN_SYMBOL', - extra = { - symbol = '/' - } + type = 'MISS_EXP', } TEST[[ @@ -1175,23 +1172,17 @@ b = 1 1 } TEST[[ -b = 1 <!> 0 +b = 1 <> 0 ]] { - type = 'UNKNOWN_SYMBOL', - extra = { - symbol = '>', - } + type = 'MISS_EXP', } TEST[[ -b = 1 < 0 +b = 1 < < 0 ]] { - type = 'UNKNOWN_SYMBOL', - extra = { - symbol = '<', - } + type = 'MISS_EXP', } TEST[[ @@ -1284,6 +1275,10 @@ TEST[[ { multi = 2, type = 'MISS_END', + extra = { + start = 9, + finish = 9, + } } TEST[[ @@ -1307,6 +1302,10 @@ TEST[[ { multi = 2, type = 'MISS_END', + extra = { + start = 14, + finish = 14, + } } TEST[[ @@ -1341,7 +1340,7 @@ TEST[[ } TEST[[ -local function () +local function a() end ]] { From 9cbd684dd3aef0ae51d95dfa6f7494eb326ea1d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Sat, 7 Oct 2023 18:36:54 +0800 Subject: [PATCH 65/94] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 1 + src/parser/ast/ast.lua | 15 +++++ src/parser/ast/base.lua | 5 +- src/parser/ast/break.lua | 31 +++++++++ src/parser/ast/function.lua | 28 ++++---- src/parser/ast/id.lua | 4 +- src/parser/ast/label.lua | 25 ++++++-- src/parser/ast/local.lua | 4 ++ src/parser/ast/number.lua | 28 +++++--- src/parser/ast/string.lua | 42 ++++++------ src/parser/ast/var.lua | 24 +++++++ test/grammar.lua | 13 +++- test/syntax_check.lua | 124 +++++++++++++----------------------- 13 files changed, 213 insertions(+), 131 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index e990ed8..08fe051 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -20,6 +20,7 @@ "print" ], "internalConsoleOptions": "openOnSessionStart", + "console": "internalConsole", "sourceCoding": "utf8", "luaVersion": "5.4", "luaArch": "x86_64", diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index a109645..d92636c 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -169,4 +169,19 @@ function M:createNode(type, data) return class.new(type, data) end +-- 获取当前函数 +---@return LuaParser.Node.Function | LuaParser.Node.Main | nil +function M:getCurrentFunction() + local blocks = self.blocks + for i = #blocks, 1, -1 do + local block = blocks[i] + if block.type == 'Function' + or block.type == 'Main' then + ---@cast block LuaParser.Node.Function | LuaParser.Node.Main + return block + end + end + return nil +end + return M diff --git a/src/parser/ast/base.lua b/src/parser/ast/base.lua index c006462..bdd85af 100644 --- a/src/parser/ast/base.lua +++ b/src/parser/ast/base.lua @@ -119,10 +119,13 @@ Base.__getter.parentBlock = function (self) end ---@param self LuaParser.Node.Base ----@return LuaParser.Node.Function +---@return LuaParser.Node.Function | false ---@return true Base.__getter.parentFunction = function (self) local parent = self.parent + if not parent then + return false, true + end if parent.isFunction then return parent, true end diff --git a/src/parser/ast/break.lua b/src/parser/ast/break.lua index dfcc4fd..3c4abb3 100644 --- a/src/parser/ast/break.lua +++ b/src/parser/ast/break.lua @@ -1,6 +1,7 @@ local class = require 'class' ---@class LuaParser.Node.Break: LuaParser.Node.Base +---@field breeakBlock? LuaParser.Node.Block local Break = class.declare('LuaParser.Node.Break', 'LuaParser.Node.Base') ---@class LuaParser.Node.Continue: LuaParser.Node.Base @@ -9,6 +10,24 @@ local Continue = class.declare('LuaParser.Node.Continue', 'LuaParser.Node.Base') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' +---@return LuaParser.Node.Block? +function Ast:findBreakBlock() + local blocks = self.blocks + for i = #blocks, 1, -1 do + local block = blocks[i] + if block.type == 'For' + or block.type == 'While' + or block.type == 'Repeat' then + return block + end + if block.type == 'Function' + or block.type == 'Main' then + return nil + end + end + return nil +end + ---@private ---@return LuaParser.Node.Break? function Ast:parseBreak() @@ -17,9 +36,15 @@ function Ast:parseBreak() return end + local block = self:findBreakBlock() + if not block then + self:throw('BREAK_OUTSIDE', pos, pos + #'break') + end + return self:createNode('LuaParser.Node.Break', { start = pos, finish = self:getLastPos(), + breakBlock = block, }) end @@ -34,8 +59,14 @@ function Ast:parseContinue() return end + local block = self:findBreakBlock() + if not block then + self:throw('BREAK_OUTSIDE', pos, pos + #'break') + end + return self:createNode('LuaParser.Node.Continue', { start = pos, finish = self:getLastPos(), + breakBlock = block, }) end diff --git a/src/parser/ast/function.lua b/src/parser/ast/function.lua index 4eb5265..7ec9ea9 100644 --- a/src/parser/ast/function.lua +++ b/src/parser/ast/function.lua @@ -69,6 +69,19 @@ function Ast:parseFunction(isLocal) symbolPos2 = symbolPos2, }) + if name then + name.parent = func + name.value = func + end + + if params then + for i = 1, #params do + local param = params[i] + param.parent = func + param.index = i + end + end + if symbolPos2 then self:skipSpace() self:blockStart(func) @@ -89,19 +102,6 @@ function Ast:parseFunction(isLocal) func.symbolPos3 = symbolPos3 func.finish = self:getLastPos() - if name then - name.parent = func - name.value = func - end - - if params then - for i = 1, #params do - local param = params[i] - param.parent = func - param.index = i - end - end - return func end @@ -202,7 +202,7 @@ end ---@private ---@return LuaParser.Node.Param? function Ast:parseParam() - local param = self:parseID('LuaParser.Node.Param') + local param = self:parseID('LuaParser.Node.Param', false, true) if param then return param end diff --git a/src/parser/ast/id.lua b/src/parser/ast/id.lua index 855d1ec..f33378f 100644 --- a/src/parser/ast/id.lua +++ b/src/parser/ast/id.lua @@ -15,8 +15,8 @@ local Ast = class.declare 'LuaParser.Ast' ---@private ---@generic T: LuaParser.Node.ID ---@param nodeType `T` ----@param required? true ----@param canBeKeyword? true +---@param required? boolean +---@param canBeKeyword? boolean ---@return T? function Ast:parseID(nodeType, required, canBeKeyword) local token, tp, pos = self.lexer:peek() diff --git a/src/parser/ast/label.lua b/src/parser/ast/label.lua index 807257c..879c787 100644 --- a/src/parser/ast/label.lua +++ b/src/parser/ast/label.lua @@ -45,19 +45,32 @@ function Ast:parseLabel() labelName.parent = label end + if self.versionNum <= 51 then + self:throw('UNSUPPORT_SYMBOL', pos, pos + 2) + end + return label end ---@private ---@return LuaParser.Node.Goto? function Ast:parseGoto() - if not self:isKeyWord 'goto' then + local token, _, pos = self.lexer:peek() + if token ~= 'goto' then return nil end - local pos = self.lexer:consume 'goto' - if not pos then - return nil + ---@cast pos -? + if self:isKeyWord 'goto' then + -- OK + else + local _, nextType = self.lexer:peek(1) + if nextType == 'Word' then + -- OK + else + return nil + end end + self.lexer:next() self:skipSpace() local labelName = self:parseID('LuaParser.Node.LabelName', true) @@ -72,5 +85,9 @@ function Ast:parseGoto() labelName.parent = gotoNode end + if self.versionNum <= 51 then + self:throw('UNSUPPORT_SYMBOL', pos, pos + #'goto') + end + return gotoNode end diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index e804277..6a11437 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -253,5 +253,9 @@ function Ast:parseLocalAttr() self:throw('MISS_SPACE_BETWEEN', ltPos, ltPos + 2) end + if self.versionNum <= 53 then + self:throw('UNSUPPORT_SYMBOL', attrNode.start, attrNode.finish) + end + return attrNode end diff --git a/src/parser/ast/number.lua b/src/parser/ast/number.lua index e8766df..970fb9c 100644 --- a/src/parser/ast/number.lua +++ b/src/parser/ast/number.lua @@ -166,12 +166,6 @@ end ---@return boolean function Ast:parseNumberI(curOffset) if self.code:find('^[iI]', curOffset) then - if not self.jit then - self:throw('UNSUPPORT_SYMBOL', curOffset, curOffset, { - version = self.version, - needVer = 'LuaJIT', - }) - end return true end return false @@ -188,6 +182,13 @@ function Ast:buildFloat(value, startPos, curOffset) curOffset = curOffset + 1 valuei = value end + + if not self.jit then + if valuei then + self:throw('UNSUPPORT_SYMBOL', curOffset - 2, curOffset - 1) + end + end + self:fastForwardNumber(curOffset) return self:createNode('LuaParser.Node.Float', { start = startPos, @@ -217,6 +218,16 @@ function Ast:buildInteger(value, startPos, curOffset) intTail = 'LL' end end + + if not self.jit then + if valuei then + self:throw('UNSUPPORT_SYMBOL', curOffset - 2, curOffset - 1) + end + if intTail then + self:throw('UNSUPPORT_SYMBOL', curOffset - #intTail - 1, curOffset - 1) + end + end + self:fastForwardNumber(curOffset) return self:createNode('LuaParser.Node.Integer', { start = startPos, @@ -298,10 +309,7 @@ function Ast:parseNumber2() local value = tonumber(bins, 2) if not self.jit then - self:throw('UNSUPPORT_SYMBOL', pos + 2, curOffset - 1, { - version = self.version, - needVer = 'LuaJIT', - }) + self:throw('UNSUPPORT_SYMBOL', pos, curOffset - 1) end return self:buildInteger(value, pos, curOffset) diff --git a/src/parser/ast/string.lua b/src/parser/ast/string.lua index 933a828..d46e1c8 100644 --- a/src/parser/ast/string.lua +++ b/src/parser/ast/string.lua @@ -180,7 +180,7 @@ function Ast:parseShortString() curOffset = offset + 1 end if self.versionNum <= 51 then - self:throw('ERR_ESC', curPos, curPos + 3) + self:throw('ERR_ESC', curPos - 1, curPos + 3) end goto continue end @@ -193,30 +193,34 @@ function Ast:parseShortString() goto continue end pushEsc('unicode', offset - 2, tailOffset - 1) - if #word == 0 then - self:throw('UTF8_SMALL', offset + 1, offset + 1) + if self.versionNum <= 52 then + self:throw('ERR_ESC', offset - 2, tailOffset - 1) else - local num = tonumber(word, 16) - if num then - if self.versionNum >= 54 then - if num < 0 or num > 0x7FFFFFFF then + if #word == 0 then + self:throw('UTF8_SMALL', offset + 1, offset + 1) + else + local num = tonumber(word, 16) + if num then + if self.versionNum >= 54 then + if num < 0 or num > 0x7FFFFFFF then + self:throw('UTF8_MAX', offset + 1, offset + #word + 1, { + min = '00000000', + max = '7FFFFFFF', + }) + end + else self:throw('UTF8_MAX', offset + 1, offset + #word + 1, { - min = '00000000', - max = '7FFFFFFF', + min = '000000', + max = '10FFFF', + needVer = num <= 0x7FFFFFFF and 'Lua 5.4' or nil, }) end + if num >= 0 and num <= 0x7FFFFFFF then + pieces[#pieces+1] = utf8.char(num) + end else - self:throw('UTF8_MAX', offset + 1, offset + #word + 1, { - min = '000000', - max = '10FFFF', - needVer = num <= 0x7FFFFFFF and 'Lua 5.4' or nil, - }) + self:throw('MUST_X16', offset + 1, offset + #word + 1) end - if num >= 0 and num <= 0x7FFFFFFF then - pieces[#pieces+1] = utf8.char(num) - end - else - self:throw('MUST_X16', offset + 1, offset + #word + 1) end end if rightP == '' then diff --git a/src/parser/ast/var.lua b/src/parser/ast/var.lua index b1c2630..e9a76af 100644 --- a/src/parser/ast/var.lua +++ b/src/parser/ast/var.lua @@ -10,6 +10,7 @@ local class = require 'class' local Var = class.declare('LuaParser.Node.Var', 'LuaParser.Node.Base') ---@class LuaParser.Node.Varargs: LuaParser.Node.Base +---@field loc? LuaParser.Node.Local local Varargs = class.declare('LuaParser.Node.Varargs', 'LuaParser.Node.Base') ---@class LuaParser.Ast @@ -38,6 +39,22 @@ function Ast:parseVar() return var end +---@private +---@return LuaParser.Node.Local? +function Ast:bindVarargs() + local loc = self:getLocal('...') + + if not loc then + return nil + end + + if loc.parentFunction ~= self:getCurrentFunction() then + return nil + end + + return loc +end + ---@private ---@return LuaParser.Node.Varargs? function Ast:parseVarargs() @@ -45,8 +62,15 @@ function Ast:parseVarargs() if not pos then return nil end + + local loc = self:bindVarargs() + if not loc then + self:throw('UNEXPECT_DOTS', pos, pos + #'...') + end + return self:createNode('LuaParser.Node.Varargs', { start = pos, finish = pos + 3, + loc = loc, }) end diff --git a/test/grammar.lua b/test/grammar.lua index 4eaeef4..d7a771f 100644 --- a/test/grammar.lua +++ b/test/grammar.lua @@ -1,6 +1,14 @@ local class = require 'class' -local function check_str(code, name, mode) +local function removeErrors(errors, code) + for i = #errors, 1, -1 do + if errors[i].code == code then + table.remove(errors, i) + end + end +end + +local function checkStr(code, name, mode) ---@class LuaParser.Ast local ast = class.new 'LuaParser.Ast' (code) local parser = 'parse' .. mode @@ -9,6 +17,7 @@ local function check_str(code, name, mode) end local node = ast[parser](ast) assert(node) + removeErrors(ast.errors, 'UNEXPECT_DOTS') if #ast.errors > 0 and mode ~= 'Dirty' then error(([[ [%s]测试失败: @@ -27,7 +36,7 @@ end local function check(mode) return function (list) for i, str in ipairs(list) do - check_str(str, mode .. '-' .. i, mode) + checkStr(str, mode .. '-' .. i, mode) end end end diff --git a/test/syntax_check.lua b/test/syntax_check.lua index 7a09dab..e3f8821 100644 --- a/test/syntax_check.lua +++ b/test/syntax_check.lua @@ -3,7 +3,7 @@ local catch = require 'test.catch' local util = require 'utility' ---@param script string ----@return fun(expect: table|nil) +---@return fun(expect: table) local function TEST(script) return function (expect) local version = expect and expect.version @@ -14,7 +14,7 @@ local function TEST(script) local errs = ast.errors local first = errs[1] local target = list['!'][1] - if not expect then + if not expect.type then assert(#errs == 0) return end @@ -985,14 +985,14 @@ return { _VERSION = '', } ]] -(nil) +{} TEST[[ return { _VERSION == '', } ]] -(nil) +{} -- 以下测试来自 https://github.com/andremm/lua-parser/blob/master/test.lua TEST[[ @@ -1353,37 +1353,37 @@ f() 1 { multi = 1, type = 'UNKNOWN_SYMBOL', - extra = { - symbol = '=', - } } -Version = 'Lua 5.1' TEST[[ - +xx:: ]] { type = 'UNSUPPORT_SYMBOL', - version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, - extra = { - version = 'Lua 5.1', - } + version = 'Lua 5.1', +} + +TEST [[ + X +]] +{ + type = 'UNSUPPORT_SYMBOL', + version = 'Lua 5.1', } TEST[[ local goto = 1 ]] -(nil) +{ + version = 'Lua 5.1', +} TEST[[ local x = '' ]] { type = 'ERR_ESC', - version = {'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, - extra = { - version ='Lua 5.1', - } + version ='Lua 5.1', } TEST[[ @@ -1391,33 +1391,23 @@ local x = '' ]] { type = 'ERR_ESC', - version = {'Lua 5.2', 'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, - extra = { - version = 'Lua 5.1', - } + version = 'Lua 5.1', } -Version = 'Lua 5.2' TEST[[ local x = 1 2 ]] { type = 'UNSUPPORT_SYMBOL', - version = {'Lua 5.3', 'Lua 5.4'}, - extra = { - version = 'Lua 5.2', - } + version = 'Lua 5.2', } TEST[[ -local x = 1 2 +local x = 1 >!> 2 ]] { type = 'UNSUPPORT_SYMBOL', - version = {'Lua 5.3', 'Lua 5.4'}, - extra = { - version = 'Lua 5.2', - } + version = 'Lua 5.2', } TEST[[ @@ -1425,10 +1415,7 @@ local x = '' ]] { type = 'ERR_ESC', - version = {'Lua 5.3', 'Lua 5.4', 'LuaJIT'}, - extra = { - version = 'Lua 5.2', - } + version = 'Lua 5.2', } TEST[[ @@ -1437,37 +1424,30 @@ while true do x = 1 end ]] -(nil) - -Version = 'Lua 5.3' +{ + version = 'Lua 5.1', +} TEST[[ local x !> = 1 ]] { type = 'UNSUPPORT_SYMBOL', - version = 'Lua 5.4', - extra = { - version = 'Lua 5.3', - } + version = 'Lua 5.3' } - -Version = 'Lua 5.4' - TEST[[ -local x <> = 1 +local x = 1 ]] -(nil) +{} TEST[[ -s = '' +s = '\u{1FFFFF}' ]] -(nil) - +{} TEST[[ -s = '\u' +s = '\u{}' ]] { type = 'UTF8_MAX', @@ -1482,10 +1462,6 @@ x = 42 ]] { type = 'UNSUPPORT_SYMBOL', - version = 'LuaJIT', - extra = { - version = 'Lua 5.4', - } } TEST[[ @@ -1493,10 +1469,6 @@ x = ]] { type = 'UNSUPPORT_SYMBOL', - version = 'LuaJIT', - extra = { - version = 'Lua 5.4', - } } TEST[[ @@ -1504,10 +1476,6 @@ x = 12.5 ]] { type = 'UNSUPPORT_SYMBOL', - version = 'LuaJIT', - extra = { - version = 'Lua 5.4', - } } TEST[[ @@ -1517,8 +1485,6 @@ x = 1.23 type = 'MALFORMED_NUMBER', } -Version = 'LuaJIT' - TEST[[ x = 42LL x = 42ULl @@ -1529,7 +1495,11 @@ x = 1I x = 18446744073709551615ULL x = 0b11011 ]] -(nil) +{ + optional = { + jit = true, + } +} TEST[[ x = 1.23 @@ -1543,13 +1513,9 @@ x = 0b1 ]] { type = 'MALFORMED_NUMBER', -} - -TEST [[ -local = 1 -]] -{ - type = 'KEYWORD' + optional = { + jit = true, + } } TEST [[ @@ -1585,35 +1551,35 @@ function f(...) return ... end ]] -(nil) +{} TEST[[ for i = 1, 10 do break end ]] -(nil) +{} TEST[[ for k, v in pairs(t) do break end ]] -(nil) +{} TEST[[ while true do break end ]] -(nil) +{} TEST[[ repeat break until true ]] -(nil) +{} TEST[[ From fb24175872f65d6e220ef45c392c7e992c331f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Sat, 7 Oct 2023 18:46:16 +0800 Subject: [PATCH 66/94] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/block.lua | 6 ++++++ test/syntax_check.lua | 14 +++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/parser/ast/block.lua b/src/parser/ast/block.lua index 430f958..2d2929d 100644 --- a/src/parser/ast/block.lua +++ b/src/parser/ast/block.lua @@ -64,6 +64,7 @@ end ---@private ---@param block LuaParser.Node.Block function Ast:blockParseChilds(block) + local lastState while true do while self.lexer:consume ';' do self:skipSpace() @@ -80,6 +81,11 @@ function Ast:blockParseChilds(block) if state then state.parent = block block.childs[#block.childs+1] = state + if lastState and lastState.type == 'Return' then + ---@cast lastState LuaParser.Node.Return + self:throw('ACTION_AFTER_RETURN', lastState.start, lastState.start + #'return') + end + lastState = state self:skipSpace() else if block.isMain then diff --git a/test/syntax_check.lua b/test/syntax_check.lua index e3f8821..9f1654a 100644 --- a/test/syntax_check.lua +++ b/test/syntax_check.lua @@ -1629,13 +1629,13 @@ TEST[[ ::label:: goto label ]] -(nil) +{} TEST[[ goto label ::label:: ]] -(nil) +{} TEST[[ do @@ -1643,7 +1643,7 @@ do end ::label:: ]] -(nil) +{} TEST[[ ::label:: @@ -1651,7 +1651,7 @@ do goto label end ]] -(nil) +{} TEST[[ goto label @@ -1659,7 +1659,7 @@ local x = 1 x = 2 ::label:: ]] -(nil) +{} TEST[[ local x = 1 @@ -1668,7 +1668,7 @@ x = 2 ::label:: print(x) ]] -(nil) +{} TEST[[ local x = 1 @@ -1677,7 +1677,7 @@ print(x) local x goto label ]] -(nil) +{} TEST[[ goto From 278e088687bdcb183727e50708e25706a1483bbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Sun, 8 Oct 2023 16:18:58 +0800 Subject: [PATCH 67/94] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 24 +++++++++++- src/parser/ast/base.lua | 23 ++++++++++- src/parser/ast/block.lua | 39 +++++++++++++++++++ src/parser/ast/function.lua | 5 +++ src/parser/ast/label.lua | 77 +++++++++++++++++++++++++++++++++++-- src/parser/compile.lua | 5 ++- test/ast/state.lua | 4 +- test/syntax_check.lua | 10 +---- 8 files changed, 167 insertions(+), 20 deletions(-) diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index d92636c..8ef5b94 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -1,5 +1,6 @@ local class = require 'class' local lexer = require 'parser.lexer' +local util = require 'utility' require 'parser.ast.base' require 'parser.ast.error' @@ -33,7 +34,6 @@ require 'parser.ast.main' ---@class LuaParser.Ast ---@field envMode 'fenv' | '_ENV' ---@field main LuaParser.Node.Main ----@field fullCompile? boolean # 是否是完整编译 ---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast local M = class.declare 'LuaParser.Ast' @@ -59,11 +59,21 @@ function M:__init(code, version, options) ---@type LuaParser.Node.Comment[] self.comments = {} -- 代码块 + ---@private ---@type LuaParser.Node.Block[] self.blocks = {} -- 当前代码块 + ---@private ---@type LuaParser.Node.Block? self.curBlock = nil + -- 按类型存放的节点 + ---@private + ---@type table + self.nodesMap = util.multiTable(2) + -- 存放所有的block + ---@private + ---@type LuaParser.Node.Block[] + self.blockList = {} local major, minor = self.version:match 'Lua (%d+)%.(%d+)' ---@type integer @@ -166,10 +176,20 @@ end ---@return T function M:createNode(type, data) data.ast = self - return class.new(type, data) + local node = class.new(type, data) + + local nodeMap = self.nodesMap[node.type] + nodeMap[#nodeMap+1] = node + + if node.isBlock then + self.blockList[#self.blockList+1] = node + end + + return node end -- 获取当前函数 +---@private ---@return LuaParser.Node.Function | LuaParser.Node.Main | nil function M:getCurrentFunction() local blocks = self.blocks diff --git a/src/parser/ast/base.lua b/src/parser/ast/base.lua index bdd85af..f2797df 100644 --- a/src/parser/ast/base.lua +++ b/src/parser/ast/base.lua @@ -13,8 +13,10 @@ local class = require 'class' ---@field finishCol integer # 结束列号 ---@field code string # 对应的代码 ---@field parent unknown ----@field parentBlock LuaParser.Node.Block | false ----@field parentFunction LuaParser.Node.Function +---@field parentBlock LuaParser.Node.Block | false # 向上搜索一个block +---@field parentFunction LuaParser.Node.Function | false # 向上搜索一个function +---@field referBlock LuaParser.Node.Block | false # 如果自己是block,则是自己;否则向上搜索一个block +---@field referFunction LuaParser.Node.Function | false # 如果自己是function,则是自己;否则向上搜索一个function ---@field asNumber? number ---@field asString? string ---@field asBoolean? boolean @@ -27,6 +29,9 @@ local class = require 'class' ---@field index? integer local Base = class.declare 'LuaParser.Node.Base' +---@type boolean +Base.isBlock = false + local rowcolMulti = 10000 ---@param self LuaParser.Node.Base @@ -118,6 +123,13 @@ Base.__getter.parentBlock = function (self) return parent.parentBlock, true end +---@param self LuaParser.Node.Base +---@return LuaParser.Node.Block | false +---@return true +Base.__getter.referBlock = function (self) + return self.parentBlock, true +end + ---@param self LuaParser.Node.Base ---@return LuaParser.Node.Function | false ---@return true @@ -132,6 +144,13 @@ Base.__getter.parentFunction = function (self) return parent.parentFunction, true end +---@param self LuaParser.Node.Base +---@return LuaParser.Node.Function | false +---@return true +function Base.__getter.referFunction(self) + return self.parentFunction, true +end + ---@class LuaParser.Node.Literal: LuaParser.Node.Base ---@field value? nil|boolean|number|string|integer local Literal = class.declare('LuaParser.Node.Literal', 'LuaParser.Node.Base') diff --git a/src/parser/ast/block.lua b/src/parser/ast/block.lua index 2d2929d..3566516 100644 --- a/src/parser/ast/block.lua +++ b/src/parser/ast/block.lua @@ -18,6 +18,10 @@ Block.__getter.locals = function () return {}, true end +Block.__getter.referBlock = function (self) + return self, true +end + ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' @@ -97,3 +101,38 @@ function Ast:blockParseChilds(block) end end end + +---@package +Ast.needSortBlock = true + +-- 获取最近的block +---@public +---@param pos integer +---@return LuaParser.Node.Block? +function Ast:getRecentBlock(pos) + if self.needSortBlock then + self.needSortBlock = false + table.sort(self.blocks, function (a, b) + return a.start < b.start + end) + end + + local blocks = self.blockList + -- 使用二分法找到最近的block + local low = 1 + local high = #blocks + while low <= high do + local mid = (low + high) // 2 + if pos < blocks[mid].start then + high = mid - 1 + elseif not blocks[mid+1] then + return blocks[mid] + elseif pos >= blocks[mid+1].start then + low = mid + 1 + else + return blocks[mid] + end + end + + return nil +end diff --git a/src/parser/ast/function.lua b/src/parser/ast/function.lua index 7ec9ea9..1bec202 100644 --- a/src/parser/ast/function.lua +++ b/src/parser/ast/function.lua @@ -21,6 +21,11 @@ local Function = class.declare('LuaParser.Node.Function', 'LuaParser.Node.Block' Function.isFunction = true +function Function.__getter.referFunction(self) + return self, true +end + + ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' diff --git a/src/parser/ast/label.lua b/src/parser/ast/label.lua index 879c787..ffeb75b 100644 --- a/src/parser/ast/label.lua +++ b/src/parser/ast/label.lua @@ -1,17 +1,23 @@ local class = require 'class' ---@class LuaParser.Node.Label: LuaParser.Node.Base ----@field label? LuaParser.Node.LabelName +---@field name? LuaParser.Node.LabelName ---@field symbolPos? integer # 右边标签符号的位置 +---@field gotos LuaParser.Node.Goto[] # 关联的Goto local Label = class.declare('LuaParser.Node.Label', 'LuaParser.Node.Base') +Label.__getter.gotos = function (self) + return {}, true +end + ---@class LuaParser.Node.LabelName: LuaParser.Node.Base ---@field parent LuaParser.Node.Label | LuaParser.Node.Goto ---@field id string local LabelName = class.declare('LuaParser.Node.LabelName', 'LuaParser.Node.Base') ---@class LuaParser.Node.Goto: LuaParser.Node.Base ----@field label? LuaParser.Node.LabelName +---@field name? LuaParser.Node.LabelName +---@field label? LuaParser.Node.Label # 关联的Label local Goto = class.declare('LuaParser.Node.Goto', 'LuaParser.Node.Base') ---@class LuaParser.Ast @@ -41,7 +47,7 @@ function Ast:parseLabel() }) if labelName then - label.label = labelName + label.name = labelName labelName.parent = label end @@ -81,7 +87,7 @@ function Ast:parseGoto() }) if labelName then - gotoNode.label = labelName + gotoNode.name = labelName labelName.parent = gotoNode end @@ -91,3 +97,66 @@ function Ast:parseGoto() return gotoNode end + +---@private +function Ast:resolveAllGoto() + for _, gotoNode in ipairs(self.nodesMap['Goto']) do + ---@cast gotoNode LuaParser.Node.Goto + self:resolveGoto(gotoNode) + end +end + +---@private +---@param gotoNode LuaParser.Node.Goto +function Ast:resolveGoto(gotoNode) + if not gotoNode.name then + return + end + local myName = gotoNode.name.id + + ---@type LuaParser.Node.Label? + local labelNode + local labels = self:findVisibleLabels(gotoNode.start) + for _, label in ipairs(labels) do + if label.name.id == myName then + labelNode = label + break + end + end + + if not labelNode then + self:throw('NO_VISIBLE_LABEL', gotoNode.name.start, gotoNode.name.finish) + return + end + + gotoNode.label = labelNode + labelNode.gotos[#labelNode.gotos+1] = gotoNode +end + +-- 获取在指定位置可见的所有标签 +---@public +---@param pos integer +---@return LuaParser.Node.Label[] +function Ast:findVisibleLabels(pos) + local results = {} + local myBlock = self:getRecentBlock(pos) + if not myBlock then + return results + end + local myFunction = myBlock.referFunction + if not myFunction then + return results + end + for _, labelNode in ipairs(self.nodesMap['Label']) do + ---@cast labelNode LuaParser.Node.Label + local block = labelNode.parentBlock + if block + and labelNode.name + and block.start <= myBlock.start + and block.finish >= myBlock.finish + and block.start >= myFunction.start then + results[#results+1] = labelNode + end + end + return results +end diff --git a/src/parser/compile.lua b/src/parser/compile.lua index 4c7f3d9..0b75b06 100644 --- a/src/parser/compile.lua +++ b/src/parser/compile.lua @@ -32,7 +32,10 @@ local LuaParser = class.get 'LuaParser' ---@return LuaParser.Ast function LuaParser.compile(code, version, options) local ast = class.new 'LuaParser.Ast' (code, version, options) - ast.fullCompile = true ast.main = ast:parseMain() + + ---@diagnostic disable-next-line: invisible + ast:resolveAllGoto() + return ast end diff --git a/test/ast/state.lua b/test/ast/state.lua index 12f3b55..a409573 100644 --- a/test/ast/state.lua +++ b/test/ast/state.lua @@ -236,7 +236,7 @@ TEST [[:: continue ::]] type = 'Label', start = 0, finish = 14, - label = { + name = { start = 3, finish = 11, id = 'continue', @@ -248,7 +248,7 @@ TEST [[goto continue]] type = 'Goto', start = 0, finish = 13, - label = { + name = { start = 5, finish = 13, id = 'continue', diff --git a/test/syntax_check.lua b/test/syntax_check.lua index 9f1654a..9222e0a 100644 --- a/test/syntax_check.lua +++ b/test/syntax_check.lua @@ -1367,6 +1367,7 @@ TEST [[ X ]] { + multi = 1, type = 'UNSUPPORT_SYMBOL', version = 'Lua 5.1', } @@ -1684,9 +1685,6 @@ goto ]] { type = 'NO_VISIBLE_LABEL', - extra = { - label = 'label', - } } TEST[[ @@ -1695,9 +1693,6 @@ do do do goto end end end ]] { type = 'NO_VISIBLE_LABEL', - extra = { - label = 'label', - } } TEST[[ @@ -1708,9 +1703,6 @@ end ]] { type = 'NO_VISIBLE_LABEL', - extra = { - label = 'label', - } } TEST[[ From daf422dd159bed883a6d0e3d8be2499e417645e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Sun, 8 Oct 2023 16:48:46 +0800 Subject: [PATCH 68/94] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/label.lua | 16 ++++++++ test/syntax_check.lua | 88 +++++++++++----------------------------- 2 files changed, 40 insertions(+), 64 deletions(-) diff --git a/src/parser/ast/label.lua b/src/parser/ast/label.lua index ffeb75b..a0c2f96 100644 --- a/src/parser/ast/label.lua +++ b/src/parser/ast/label.lua @@ -131,6 +131,22 @@ function Ast:resolveGoto(gotoNode) gotoNode.label = labelNode labelNode.gotos[#labelNode.gotos+1] = gotoNode + + -- 检查是否进入局部变量的作用域 + local labelBlock = labelNode.parentBlock + if labelBlock then + for _, loc in ipairs(labelBlock.locals) do + if loc.start > gotoNode.start + and loc.start < labelNode.start then + self:throw('JUMP_LOCAL_SCOPE', gotoNode.name.start, gotoNode.name.finish, { + loc = loc.id, + start = loc.start, + finish = loc.finish, + }) + break + end + end + end end -- 获取在指定位置可见的所有标签 diff --git a/test/syntax_check.lua b/test/syntax_check.lua index 9222e0a..1755932 100644 --- a/test/syntax_check.lua +++ b/test/syntax_check.lua @@ -1654,32 +1654,6 @@ end ]] {} -TEST[[ -goto label -local x = 1 -x = 2 -::label:: -]] -{} - -TEST[[ -local x = 1 -goto label -x = 2 -::label:: -print(x) -]] -{} - -TEST[[ -local x = 1 -::label:: -print(x) -local x -goto label -]] -{} - TEST[[ goto ]] @@ -1709,47 +1683,32 @@ TEST[[ goto local x = 1 ::label:: -x = 2 ]] { type = 'JUMP_LOCAL_SCOPE', extra = { loc = 'x', + start = 17, + finish = 18, }, - relative = { - { - start = 26, - finish = 30, - }, - { - start = 18, - finish = 18, - }, - } } TEST[[ -goto -local x = 1 +local x +goto label ::label:: -return x +print(x) ]] -{ - type = 'JUMP_LOCAL_SCOPE', - extra = { - loc = 'x', - }, - relative = { - { - start = 26, - finish = 30, - }, - { - start = 18, - finish = 18, - }, - } -} +{} + +TEST[[ +local x +::label:: +print(x) +local x +goto label +]] +{} TEST[[ ::label:: @@ -1758,13 +1717,12 @@ TEST[[ ]] { type = 'REDEFINED_LABEL', - related = { + extra = { start = 3, finish = 7, } } -Version = 'Lua 5.4' TEST[[ ::label:: ::other_label:: @@ -1774,7 +1732,7 @@ end ]] { type = 'REDEFINED_LABEL', - related = { + extra = { start = 3, finish = 7, } @@ -1786,9 +1744,8 @@ if true then end ::label:: ]] -(nil) +{} -Version = 'Lua 5.3' TEST[[ ::label:: ::other_label:: @@ -1796,7 +1753,9 @@ if true then ::label:: end ]] -(nil) +{ + version = 'Lua 5.3', +} TEST[[ if true then @@ -1804,9 +1763,10 @@ if true then end ::label:: ]] -(nil) +{ + version = 'Lua 5.3', +} -Version = 'Lua 5.4' TEST[[ local x = 1 = 2 From 7e82bcc6f4123f566eaca917c4abbca27c81ac79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Sun, 8 Oct 2023 17:25:34 +0800 Subject: [PATCH 69/94] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/base.lua | 15 +++------- src/parser/ast/block.lua | 63 ++++++++++++++++++++++++++++++---------- src/parser/ast/label.lua | 47 ++++++++++++++---------------- src/parser/ast/local.lua | 4 +-- test/syntax_check.lua | 4 +-- 5 files changed, 76 insertions(+), 57 deletions(-) diff --git a/src/parser/ast/base.lua b/src/parser/ast/base.lua index f2797df..5dcaa23 100644 --- a/src/parser/ast/base.lua +++ b/src/parser/ast/base.lua @@ -12,7 +12,7 @@ local class = require 'class' ---@field finishRow integer # 结束行号 ---@field finishCol integer # 结束列号 ---@field code string # 对应的代码 ----@field parent unknown +---@field parent? unknown ---@field parentBlock LuaParser.Node.Block | false # 向上搜索一个block ---@field parentFunction LuaParser.Node.Function | false # 向上搜索一个function ---@field referBlock LuaParser.Node.Block | false # 如果自己是block,则是自己;否则向上搜索一个block @@ -32,6 +32,9 @@ local Base = class.declare 'LuaParser.Node.Base' ---@type boolean Base.isBlock = false +---@type boolean +Base.isFunction = false + local rowcolMulti = 10000 ---@param self LuaParser.Node.Base @@ -99,16 +102,6 @@ Base.__getter.code = function (self) return code, true end ----@param self LuaParser.Node.Base ----@return any ----@return true -Base.__getter.parent = function (self) - if self.start == 0 then - return false, true - end - error('未设置父节点:' .. self.type) -end - ---@param self LuaParser.Node.Base ---@return LuaParser.Node.Block | false ---@return true diff --git a/src/parser/ast/block.lua b/src/parser/ast/block.lua index 3566516..2b124de 100644 --- a/src/parser/ast/block.lua +++ b/src/parser/ast/block.lua @@ -3,8 +3,10 @@ local class = require 'class' ---@class LuaParser.Node.Block: LuaParser.Node.Base ---@field childs LuaParser.Node.State[] ---@field locals LuaParser.Node.Local[] +---@field labels LuaParser.Node.Label[] ---@field isMain boolean ----@field private localMap table +---@field localMap table +---@field labelMap table local Block = class.declare('LuaParser.Node.Block', 'LuaParser.Node.Base') Block.isBlock = true @@ -18,6 +20,49 @@ Block.__getter.locals = function () return {}, true end +---@param self LuaParser.Node.Block +---@return table +---@return true +Block.__getter.localMap = function (self) + local parentBlock = self.parentBlock + if not parentBlock then + return {}, true + end + local parentLocalMap = parentBlock.localMap + return setmetatable({}, { + __index = function (t, k) + local v = parentLocalMap[k] or false + t[k] = v + return v + end + }), true +end + +Block.__getter.labels = function () + return {}, true +end + +---@param self LuaParser.Node.Block +---@return table +---@return true +Block.__getter.labelMap = function (self) + if self.isFunction then + return {}, true + end + local parentBlock = self.parentBlock + if not parentBlock then + return {}, true + end + local parentLabelMap = parentBlock.labelMap + return setmetatable({}, { + __index = function (t, k) + local v = parentLabelMap[k] or false + t[k] = v + return v + end + }), true +end + Block.__getter.referBlock = function (self) return self, true end @@ -40,21 +85,7 @@ function Ast:blockStart(block) local parentBlock = self.blocks[#self.blocks] self.blocks[#self.blocks+1] = block self.curBlock = block - - ---@diagnostic disable: invisible - if parentBlock then - local parentLocalMap = parentBlock.localMap - block.localMap = setmetatable({}, { - __index = function (t, k) - local v = parentLocalMap[k] or false - t[k] = v - return v - end - }) - else - block.localMap = {} - end - ---@diagnostic enable: invisible + block.parentBlock = parentBlock end ---@private diff --git a/src/parser/ast/label.lua b/src/parser/ast/label.lua index a0c2f96..d5802aa 100644 --- a/src/parser/ast/label.lua +++ b/src/parser/ast/label.lua @@ -49,6 +49,23 @@ function Ast:parseLabel() if labelName then label.name = labelName labelName.parent = label + local curBlock = self.curBlock + if curBlock then + curBlock.labels[#curBlock.labels+1] = label + + local existLabel = curBlock.labelMap[labelName.id] + if existLabel then + if self.versionNum >= 54 + or curBlock == existLabel.parentBlock then + self:throw('REDEFINED_LABEL', labelName.start, labelName.finish, { + start = existLabel.name.start, + finish = existLabel.name.finish, + }) + end + end + + curBlock.labelMap[labelName.id] = label + end end if self.versionNum <= 51 then @@ -114,15 +131,8 @@ function Ast:resolveGoto(gotoNode) end local myName = gotoNode.name.id - ---@type LuaParser.Node.Label? - local labelNode - local labels = self:findVisibleLabels(gotoNode.start) - for _, label in ipairs(labels) do - if label.name.id == myName then - labelNode = label - break - end - end + local labelMap = self:findVisibleLabels(gotoNode.start) + local labelNode = labelMap[myName] if not labelNode then self:throw('NO_VISIBLE_LABEL', gotoNode.name.start, gotoNode.name.finish) @@ -152,27 +162,12 @@ end -- 获取在指定位置可见的所有标签 ---@public ---@param pos integer ----@return LuaParser.Node.Label[] +---@return table function Ast:findVisibleLabels(pos) local results = {} local myBlock = self:getRecentBlock(pos) if not myBlock then return results end - local myFunction = myBlock.referFunction - if not myFunction then - return results - end - for _, labelNode in ipairs(self.nodesMap['Label']) do - ---@cast labelNode LuaParser.Node.Label - local block = labelNode.parentBlock - if block - and labelNode.name - and block.start <= myBlock.start - and block.finish >= myBlock.finish - and block.start >= myFunction.start then - results[#results+1] = labelNode - end - end - return results + return myBlock.labelMap end diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index 6a11437..06d3e45 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -31,7 +31,7 @@ Local.__getter.sets = function (self) local sets = {} for _, ref in ipairs(self.refs) do local parent = ref.parent - if parent.type == 'Assign' then + if parent and parent.type == 'Assign' then sets[#sets+1] = ref end end @@ -46,7 +46,7 @@ Local.__getter.gets = function (self) local gets = {} for _, ref in ipairs(self.refs) do local parent = ref.parent - if parent.type ~= 'Assign' then + if parent and parent.type ~= 'Assign' then gets[#gets+1] = ref end end diff --git a/test/syntax_check.lua b/test/syntax_check.lua index 1755932..7f9fa61 100644 --- a/test/syntax_check.lua +++ b/test/syntax_check.lua @@ -1718,7 +1718,7 @@ TEST[[ { type = 'REDEFINED_LABEL', extra = { - start = 3, + start = 2, finish = 7, } } @@ -1733,7 +1733,7 @@ end { type = 'REDEFINED_LABEL', extra = { - start = 3, + start = 2, finish = 7, } } From 150ef3cb0eb9323dd432259e1fe08c8b5cbb6ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Sun, 8 Oct 2023 17:50:23 +0800 Subject: [PATCH 70/94] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/local.lua | 21 ++++++- src/parser/compile.lua | 2 + test/syntax_check.lua | 117 +++++++++++++++++++++++++-------------- 3 files changed, 96 insertions(+), 44 deletions(-) diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index 06d3e45..0fcc831 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -30,9 +30,13 @@ end Local.__getter.sets = function (self) local sets = {} for _, ref in ipairs(self.refs) do - local parent = ref.parent - if parent and parent.type == 'Assign' then + if ref.value then sets[#sets+1] = ref + else + local parent = ref.parent + if parent and parent.type == 'Assign' then + sets[#sets+1] = ref + end end end return sets, true @@ -259,3 +263,16 @@ function Ast:parseLocalAttr() return attrNode end + +---@private +function Ast:checkAssignConst() + for _, loc in ipairs(self.nodesMap['Local']) do + ---@cast loc LuaParser.Node.Local + local attr = loc.attr and loc.attr.name and loc.attr.name.id + if attr == 'const' or attr == 'close' then + for _, set in ipairs(loc.sets) do + self:throw('SET_CONST', set.start, set.finish) + end + end + end +end diff --git a/src/parser/compile.lua b/src/parser/compile.lua index 0b75b06..896cd30 100644 --- a/src/parser/compile.lua +++ b/src/parser/compile.lua @@ -36,6 +36,8 @@ function LuaParser.compile(code, version, options) ---@diagnostic disable-next-line: invisible ast:resolveAllGoto() + ---@diagnostic disable-next-line: invisible + ast:checkAssignConst() return ast end diff --git a/test/syntax_check.lua b/test/syntax_check.lua index 7f9fa61..509e6e1 100644 --- a/test/syntax_check.lua +++ b/test/syntax_check.lua @@ -1858,7 +1858,7 @@ local l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17 for i = 1, 10 do end ]] -(nil) +{} TEST [[ local l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20, l21, l22, l23, l24, l25, l26, l27, l28, l29, l30, l31, l32, l33, l34, l35, l36, l37, l38, l39, l40, l41, l42, l43, l44, l45, l46, l47, l48, l49, l50, l51, l52, l53, l54, l55, l56, l57, l58, l59, l60, l61, l62, l63, l64, l65, l66, l67, l68, l69, l70, l71, l72, l73, l74, l75, l76, l77, l78, l79, l80, l81, l82, l83, l84, l85, l86, l87, l88, l89, l90, l91, l92, l93, l94, l95, l96, l97, l98, l99, l100, l101, l102, l103, l104, l105, l106, l107, l108, l109, l110, l111, l112, l113, l114, l115, l116, l117, l118, l119, l120, l121, l122, l123, l124, l125, l126, l127, l128, l129, l130, l131, l132, l133, l134, l135, l136, l137, l138, l139, l140, l141, l142, l143, l144, l145, l146, l147, l148, l149, l150, l151, l152, l153, l154, l155, l156, l157, l158, l159, l160, l161, l162, l163, l164, l165, l166, l167, l168, l169, l170, l171, l172, l173, l174, l175, l176, l177, l178, l179, l180, l181, l182, l183, l184, l185, l186, l187, l188, l189, l190, l191, l192, l193, l194, l195, l196 @@ -1874,9 +1874,7 @@ local l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17 for x in _ do end ]] -(nil) - -Version = 'Lua 5.1' +{} TEST [[ local l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20, l21, l22, l23, l24, l25, l26, l27, l28, l29, l30, l31, l32, l33, l34, l35, l36, l37, l38, l39, l40, l41, l42, l43, l44, l45, l46, l47, l48, l49, l50, l51, l52, l53, l54, l55, l56, l57, l58, l59, l60, l61, l62, l63, l64, l65, l66, l67, l68, l69, l70, l71, l72, l73, l74, l75, l76, l77, l78, l79, l80, l81, l82, l83, l84, l85, l86, l87, l88, l89, l90, l91, l92, l93, l94, l95, l96, l97, l98, l99, l100, l101, l102, l103, l104, l105, l106, l107, l108, l109, l110, l111, l112, l113, l114, l115, l116, l117, l118, l119, l120, l121, l122, l123, l124, l125, l126, l127, l128, l129, l130, l131, l132, l133, l134, l135, l136, l137, l138, l139, l140, l141, l142, l143, l144, l145, l146, l147, l148, l149, l150, l151, l152, l153, l154, l155, l156, l157, l158, l159, l160, l161, l162, l163, l164, l165, l166, l167, l168, l169, l170, l171, l172, l173, l174, l175, l176, l177, l178, l179, l180, l181, l182, l183, l184, l185, l186, l187, l188, l189, l190, l191, l192, l193, l194, l195, l196, l197 @@ -1884,6 +1882,7 @@ local l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17 for in _ do end ]] { + version = 'Lua 5.1', type = 'LOCAL_LIMIT', } @@ -1892,14 +1891,16 @@ local l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17 for x in _ do end ]] -(nil) +{ + version = 'Lua 5.1', +} TEST [[ local l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20, l21, l22, l23, l24, l25, l26, l27, l28, l29, l30, l31, l32, l33, l34, l35, l36, l37, l38, l39, l40, l41, l42, l43, l44, l45, l46, l47, l48, l49, l50, l51, l52, l53, l54, l55, l56, l57, l58, l59, l60, l61, l62, l63, l64, l65, l66, l67, l68, l69, l70, l71, l72, l73, l74, l75, l76, l77, l78, l79, l80, l81, l82, l83, l84, l85, l86, l87, l88, l89, l90, l91, l92, l93, l94, l95, l96, l97, l98, l99, l100, l101, l102, l103, l104, l105, l106, l107, l108, l109, l110, l111, l112, l113, l114, l115, l116, l117, l118, l119, l120, l121, l122, l123, l124, l125, l126, l127, l128, l129, l130, l131, l132, l133, l134, l135, l136, l137, l138, l139, l140, l141, l142, l143, l144, l145, l146, l147, l148, l149, l150, l151, l152, l153, l154, l155, l156, l157, l158, l159, l160, l161, l162, l163, l164, l165, l166, l167, l168, l169, l170, l171, l172, l173, l174, l175, l176, l177, l178, l179, l180, l181, l182, l183, l184, l185, l186, l187, l188, l189, l190, l191, l192, l193, l194, l195, l196, l197, l198, l199, l200 _ENV = nil ]] -(nil) +{} TEST [[ local l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20, l21, l22, l23, l24, l25, l26, l27, l28, l29, l30, l31, l32, l33, l34, l35, l36, l37, l38, l39, l40, l41, l42, l43, l44, l45, l46, l47, l48, l49, l50, l51, l52, l53, l54, l55, l56, l57, l58, l59, l60, l61, l62, l63, l64, l65, l66, l67, l68, l69, l70, l71, l72, l73, l74, l75, l76, l77, l78, l79, l80, l81, l82, l83, l84, l85, l86, l87, l88, l89, l90, l91, l92, l93, l94, l95, l96, l97, l98, l99, l100, l101, l102, l103, l104, l105, l106, l107, l108, l109, l110, l111, l112, l113, l114, l115, l116, l117, l118, l119, l120, l121, l122, l123, l124, l125, l126, l127, l128, l129, l130, l131, l132, l133, l134, l135, l136, l137, l138, l139, l140, l141, l142, l143, l144, l145, l146, l147, l148, l149, l150, l151, l152, l153, l154, l155, l156, l157, l158, l159, l160, l161, l162, l163, l164, l165, l166, l167, l168, l169, l170, l171, l172, l173, l174, l175, l176, l177, l178, l179, l180, l181, l182, l183, l184, l185, l186, l187, l188, l189, l190, l191, l192, l193, l194, l195, l196, l197, l198, l199, l200 @@ -1910,8 +1911,6 @@ local = nil type = 'LOCAL_LIMIT', } -Version = 'Lua 5.4' - TEST [[ local x =!> 1 ]] @@ -1935,27 +1934,28 @@ function mt() end type = 'INDEX_IN_FUNC_NAME' } -Version = 'Lua 5.4' TEST [[ goto = 1 ]] { multi = 1, + version = 'Lua 5.4', type = 'MISS_NAME' } -Version = 'Lua 5.1' TEST [[ goto = 1 ]] -(nil) +{ + version = 'Lua 5.1', +} TEST [[ return { function () end } ]] -(nil) +{} TEST [[ @@ -1978,6 +1978,7 @@ f ]] { + version = 'Lua 5.1', type = 'AMBIGUOUS_SYNTAX', } @@ -1986,6 +1987,7 @@ f:xx ]] { + version = 'Lua 5.1', type = 'AMBIGUOUS_SYNTAX', } @@ -1995,6 +1997,7 @@ f .x = 1 ]] { + version = 'Lua 5.1', type = 'AMBIGUOUS_SYNTAX', } @@ -2004,6 +2007,7 @@ print [[ ]] ]===] { + version = 'Lua 5.1', type = 'NESTING_LONG_MARK', } @@ -2013,6 +2017,7 @@ print [[ ]] ]===] { + version = 'Lua 5.1', type = 'NESTING_LONG_MARK', } @@ -2021,52 +2026,55 @@ print [=[ [=[ ]=] ]===] -(nil) +{ + version = 'Lua 5.1', +} TEST [===[ print [=[ [=[ ]=] ]===] -(nil) +{ + version = 'Lua 5.1', +} TEST [===[ print [[]] print [[]] ]===] -(nil) +{ + version = 'Lua 5.1', +} -Version = 'Lua 5.4' TEST [===[ print [[ [[ ]] ]===] -(nil) +{} TEST [===[ print [[ [[ ]] ]===] -(nil) - -Version = 'Lua 5.4' +{} TEST [[ f '' ]] -(nil) +{} TEST [[ f {} ]] -(nil) +{} TEST '\v\f' -(nil) +{} TEST [=[ print(:gsub()) @@ -2100,47 +2108,72 @@ TEST [[ local t = '' (function () end)() ]] -(nil) +{} TEST [[ local t = "" (function () end)() ]] -(nil) +{} TEST [[ local t = {} (function () end)() ]] -(nil) +{} TEST [=[ local t = [[]] (function () end)() ]=] -(nil) +{} -TEST ([[ +TEST [[ goto LABEL ::LABEL:: -]], 'Lua 5.1', { jit = true }) -(nil) +]] +{ + version = 'Lua 5.1', + optional = { + jit = true, + } +} -TEST ([[ +TEST [[ local goto = 1 -]], 'Lua 5.1', { jit = true }) -(nil) +]] +{ + version = 'Lua 5.1', + optional = { + jit = true, + } +} -TEST ([[ -local goto]], 'Lua 5.1', { jit = true }) -(nil) +TEST [[ +local goto]] +{ + version = 'Lua 5.1', + optional = { + jit = true, + } +} -TEST ([[ +TEST [[ f(1, goto, 2) -]], 'Lua 5.1', { jit = true }) -(nil) +]] +{ + version = 'Lua 5.1', + optional = { + jit = true, + } +} -TEST ([[ +TEST [[ local function f(x, goto, y) end -]], 'Lua 5.1', { jit = true }) -(nil) +]] +{ + version = 'Lua 5.1', + optional = { + jit = true, + } +} From 290b1faa11e8ad8cbdaa4f4bfbae1a8318dac5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Sun, 8 Oct 2023 18:16:52 +0800 Subject: [PATCH 71/94] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 3 +- src/parser/ast/block.lua | 2 + src/parser/ast/for.lua | 4 +- src/parser/ast/function.lua | 6 ++- src/parser/ast/local.lua | 19 ++------- src/parser/ast/main.lua | 2 + test/ast/block.lua | 70 ------------------------------- test/ast/main.lua | 82 +++++++++++++++++++++++++++++++++++++ 8 files changed, 98 insertions(+), 90 deletions(-) diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 8ef5b94..dde4b2d 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -51,7 +51,6 @@ function M:__init(code, version, options) self.nssymbolMap = {} -- 词法分析结果 self.lexer = lexer.parseLua(code) - self.index = 1 -- 错误信息 ---@type LuaParser.Node.Error[] self.errors = {} @@ -74,6 +73,8 @@ function M:__init(code, version, options) ---@private ---@type LuaParser.Node.Block[] self.blockList = {} + -- 当前的局部变量计数(最大只能存在200个局部变量) + self.localCount = 0 local major, minor = self.version:match 'Lua (%d+)%.(%d+)' ---@type integer diff --git a/src/parser/ast/block.lua b/src/parser/ast/block.lua index 2b124de..1c2a9a7 100644 --- a/src/parser/ast/block.lua +++ b/src/parser/ast/block.lua @@ -94,6 +94,8 @@ function Ast:blockFinish(block) assert(self.curBlock == block) self.blocks[#self.blocks] = nil self.curBlock = self.blocks[#self.blocks] + + self.localCount = self.localCount - #block.locals end ---@private diff --git a/src/parser/ast/for.lua b/src/parser/ast/for.lua index bba2b09..228f18f 100644 --- a/src/parser/ast/for.lua +++ b/src/parser/ast/for.lua @@ -74,7 +74,9 @@ function Ast:parseFor() self:skipSpace() self:blockStart(forNode) - self:initLocals(vars) + for i = 1, #vars do + self:initLocal(vars[i]) + end self:blockParseChilds(forNode) self:blockFinish(forNode) diff --git a/src/parser/ast/function.lua b/src/parser/ast/function.lua index 1bec202..86fee63 100644 --- a/src/parser/ast/function.lua +++ b/src/parser/ast/function.lua @@ -89,13 +89,15 @@ function Ast:parseFunction(isLocal) if symbolPos2 then self:skipSpace() - self:blockStart(func) if isLocal and name then ---@cast name LuaParser.Node.Local self:initLocal(name) end + self:blockStart(func) if params then - self:initLocals(params) + for i = 1, #params do + self:initLocal(params[i]) + end end self:blockParseChilds(func) self:blockFinish(func) diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index 0fcc831..48ec027 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -140,24 +140,11 @@ function Ast:initLocal(loc) local name = loc.id block.localMap[name] = loc -end - ----@private ----@param locs LuaParser.Node.Local[] -function Ast:initLocals(locs) - ---@class LuaParser.Node.Block - local block = self.curBlock - if not block then - return - end - - for i = 1, #locs do - local loc = locs[i] - block.locals[#block.locals+1] = loc - local name = loc.id - block.localMap[name] = loc + if self.localCount == 200 then + self:throw('LOCAL_LIMIT', loc.start, loc.finish) end + self.localCount = self.localCount + 1 end ---@private diff --git a/src/parser/ast/main.lua b/src/parser/ast/main.lua index 561af9f..4604d45 100644 --- a/src/parser/ast/main.lua +++ b/src/parser/ast/main.lua @@ -56,6 +56,8 @@ function Ast:parseMain() parent = main, }) self:initLocal(env) + -- 虽然 _ENV 是上值,但是不计入200个的数量限制 + self.localCount = 0 end self:skipSpace() diff --git a/test/ast/block.lua b/test/ast/block.lua index 67afdc0..d2f5a24 100644 --- a/test/ast/block.lua +++ b/test/ast/block.lua @@ -438,73 +438,3 @@ end } } } - -TEST [[ -local function f() - print(f) -end -]] -{ - childs = { - [1] = { - type = 'Call', - args = { - [1] = { - id = 'f', - loc = { - left = 15, - } - }, - } - } - } -} - -TEST [[ -local function a() - return -end]] -{ - type = 'Function', - childs = { - [1] = { - type = 'Return', - exps = { - [1] = NIL - } - } - } -} - -TEST [[ -local function f() - return f -end -]] -{ - type = 'Function', - locals = { - [1] = { - id = 'f', - gets = { - [1] = { - left = 10011, - } - } - } - }, - childs = { - [1] = { - type = 'Return', - exps = { - [1] = { - type = 'Var', - id = 'f', - loc = { - left = 15, - } - } - } - } - } -} diff --git a/test/ast/main.lua b/test/ast/main.lua index 5aa5511..0bc5493 100644 --- a/test/ast/main.lua +++ b/test/ast/main.lua @@ -217,3 +217,85 @@ x = 1 } } } + +TEST [[ +local function f() + print(f) +end +]] +{ + childs = { + [1] = { + childs = { + [1] = { + type = 'Call', + args = { + [1] = { + id = 'f', + loc = { + left = 15, + } + }, + } + } + } + } + } +} + +TEST [[ +local function a() + return +end]] +{ + childs = { + [1] = { + type = 'Function', + childs = { + [1] = { + type = 'Return', + exps = { + [1] = NIL + } + } + } + } + } +} + +TEST [[ +local function f() + return f +end +]] +{ + locals = { + [3] = { + id = 'f', + gets = { + [1] = { + left = 10011, + } + } + } + }, + childs = { + [1] = { + type = 'Function', + childs = { + [1] = { + type = 'Return', + exps = { + [1] = { + type = 'Var', + id = 'f', + loc = { + left = 15, + } + } + } + } + } + } + } +} From 1091d31c8038f33fb3305846ec97768f4df4210c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Sun, 8 Oct 2023 18:33:03 +0800 Subject: [PATCH 72/94] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/block.lua | 5 ++++- src/parser/ast/for.lua | 17 +++++++++++++++++ src/parser/ast/local.lua | 13 ++++++++++--- test/syntax_check.lua | 6 +++--- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/parser/ast/block.lua b/src/parser/ast/block.lua index 1c2a9a7..d5f79bd 100644 --- a/src/parser/ast/block.lua +++ b/src/parser/ast/block.lua @@ -12,6 +12,9 @@ local Block = class.declare('LuaParser.Node.Block', 'LuaParser.Node.Base') Block.isBlock = true Block.isMain = false +-- 局部变量和上值的数量 +Block.localCount = 0 + Block.__getter.childs = function () return {}, true end @@ -95,7 +98,7 @@ function Ast:blockFinish(block) self.blocks[#self.blocks] = nil self.curBlock = self.blocks[#self.blocks] - self.localCount = self.localCount - #block.locals + self.localCount = self.localCount - block.localCount end ---@private diff --git a/src/parser/ast/for.lua b/src/parser/ast/for.lua index 228f18f..0b9ac53 100644 --- a/src/parser/ast/for.lua +++ b/src/parser/ast/for.lua @@ -41,14 +41,31 @@ function Ast:parseFor() local token, _, symbolPos = self.lexer:peek() forNode.symbolPos1 = symbolPos + + local extraLocalCount if token == '=' then forNode.subtype = 'loop' + extraLocalCount = 3 elseif token == 'in' then forNode.subtype = 'in' + if self.versionNum >= 54 then + extraLocalCount = 4 + else + extraLocalCount = 3 + end else self:throwMissSymbol(self:getLastPos(), 'in') return forNode end + + -- 循环要使用额外的局部变量 + local block = self.curBlock + self.localCount = self.localCount + extraLocalCount + if block then + block.localCount = block.localCount + extraLocalCount + end + + self.lexer:next() self:skipSpace() diff --git a/src/parser/ast/local.lua b/src/parser/ast/local.lua index 48ec027..362a719 100644 --- a/src/parser/ast/local.lua +++ b/src/parser/ast/local.lua @@ -127,6 +127,9 @@ function Ast:parseLocal() return localdef end +---@package +Ast.hasThrowedLocalLimit = false + ---@private ---@param loc LuaParser.Node.Local function Ast:initLocal(loc) @@ -141,10 +144,14 @@ function Ast:initLocal(loc) local name = loc.id block.localMap[name] = loc - if self.localCount == 200 then - self:throw('LOCAL_LIMIT', loc.start, loc.finish) + if name ~= '...' then + if self.localCount >= 200 and not self.hasThrowedLocalLimit then + self.hasThrowedLocalLimit = true + self:throw('LOCAL_LIMIT', loc.start, loc.finish) + end + self.localCount = self.localCount + 1 + block.localCount = block.localCount + 1 end - self.localCount = self.localCount + 1 end ---@private diff --git a/test/syntax_check.lua b/test/syntax_check.lua index 509e6e1..3d4ca8f 100644 --- a/test/syntax_check.lua +++ b/test/syntax_check.lua @@ -1847,7 +1847,7 @@ end TEST [[ local l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20, l21, l22, l23, l24, l25, l26, l27, l28, l29, l30, l31, l32, l33, l34, l35, l36, l37, l38, l39, l40, l41, l42, l43, l44, l45, l46, l47, l48, l49, l50, l51, l52, l53, l54, l55, l56, l57, l58, l59, l60, l61, l62, l63, l64, l65, l66, l67, l68, l69, l70, l71, l72, l73, l74, l75, l76, l77, l78, l79, l80, l81, l82, l83, l84, l85, l86, l87, l88, l89, l90, l91, l92, l93, l94, l95, l96, l97, l98, l99, l100, l101, l102, l103, l104, l105, l106, l107, l108, l109, l110, l111, l112, l113, l114, l115, l116, l117, l118, l119, l120, l121, l122, l123, l124, l125, l126, l127, l128, l129, l130, l131, l132, l133, l134, l135, l136, l137, l138, l139, l140, l141, l142, l143, l144, l145, l146, l147, l148, l149, l150, l151, l152, l153, l154, l155, l156, l157, l158, l159, l160, l161, l162, l163, l164, l165, l166, l167, l168, l169, l170, l171, l172, l173, l174, l175, l176, l177, l178, l179, l180, l181, l182, l183, l184, l185, l186, l187, l188, l189, l190, l191, l192, l193, l194, l195, l196, l197 -for = 1, 10 do end -- use 4 local variables +for = 1, 10 do end -- use 3 + 1 local variables ]] { type = 'LOCAL_LIMIT', @@ -1863,7 +1863,7 @@ for i = 1, 10 do end TEST [[ local l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20, l21, l22, l23, l24, l25, l26, l27, l28, l29, l30, l31, l32, l33, l34, l35, l36, l37, l38, l39, l40, l41, l42, l43, l44, l45, l46, l47, l48, l49, l50, l51, l52, l53, l54, l55, l56, l57, l58, l59, l60, l61, l62, l63, l64, l65, l66, l67, l68, l69, l70, l71, l72, l73, l74, l75, l76, l77, l78, l79, l80, l81, l82, l83, l84, l85, l86, l87, l88, l89, l90, l91, l92, l93, l94, l95, l96, l97, l98, l99, l100, l101, l102, l103, l104, l105, l106, l107, l108, l109, l110, l111, l112, l113, l114, l115, l116, l117, l118, l119, l120, l121, l122, l123, l124, l125, l126, l127, l128, l129, l130, l131, l132, l133, l134, l135, l136, l137, l138, l139, l140, l141, l142, l143, l144, l145, l146, l147, l148, l149, l150, l151, l152, l153, l154, l155, l156, l157, l158, l159, l160, l161, l162, l163, l164, l165, l166, l167, l168, l169, l170, l171, l172, l173, l174, l175, l176, l177, l178, l179, l180, l181, l182, l183, l184, l185, l186, l187, l188, l189, l190, l191, l192, l193, l194, l195, l196 -for in _ do end +for in _ do end -- use 4 + X local variables ]] { type = 'LOCAL_LIMIT', @@ -1879,7 +1879,7 @@ for x in _ do end TEST [[ local l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20, l21, l22, l23, l24, l25, l26, l27, l28, l29, l30, l31, l32, l33, l34, l35, l36, l37, l38, l39, l40, l41, l42, l43, l44, l45, l46, l47, l48, l49, l50, l51, l52, l53, l54, l55, l56, l57, l58, l59, l60, l61, l62, l63, l64, l65, l66, l67, l68, l69, l70, l71, l72, l73, l74, l75, l76, l77, l78, l79, l80, l81, l82, l83, l84, l85, l86, l87, l88, l89, l90, l91, l92, l93, l94, l95, l96, l97, l98, l99, l100, l101, l102, l103, l104, l105, l106, l107, l108, l109, l110, l111, l112, l113, l114, l115, l116, l117, l118, l119, l120, l121, l122, l123, l124, l125, l126, l127, l128, l129, l130, l131, l132, l133, l134, l135, l136, l137, l138, l139, l140, l141, l142, l143, l144, l145, l146, l147, l148, l149, l150, l151, l152, l153, l154, l155, l156, l157, l158, l159, l160, l161, l162, l163, l164, l165, l166, l167, l168, l169, l170, l171, l172, l173, l174, l175, l176, l177, l178, l179, l180, l181, l182, l183, l184, l185, l186, l187, l188, l189, l190, l191, l192, l193, l194, l195, l196, l197 -for in _ do end +for in _ do end -- use 3 + X local variables ]] { version = 'Lua 5.1', From d9f3c4889953d7402cdc2fe9ad03ea1a5bdbb481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Mon, 9 Oct 2023 11:01:24 +0800 Subject: [PATCH 73/94] =?UTF-8?q?=E9=80=9A=E8=BF=87=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/base.lua | 3 +++ src/parser/ast/block.lua | 2 +- src/parser/ast/boolean.lua | 2 ++ src/parser/ast/call.lua | 8 ++++++-- src/parser/ast/exp.lua | 10 ++++++++++ src/parser/ast/function.lua | 13 ++++++++----- src/parser/ast/label.lua | 4 ++-- src/parser/ast/nil.lua | 2 ++ src/parser/ast/number.lua | 2 ++ src/parser/ast/state.lua | 5 ++++- src/parser/ast/string.lua | 2 ++ src/parser/ast/table.lua | 2 ++ 12 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/parser/ast/base.lua b/src/parser/ast/base.lua index 5dcaa23..69fdc23 100644 --- a/src/parser/ast/base.lua +++ b/src/parser/ast/base.lua @@ -35,6 +35,9 @@ Base.isBlock = false ---@type boolean Base.isFunction = false +---@type boolean +Base.isLiteral = false + local rowcolMulti = 10000 ---@param self LuaParser.Node.Base diff --git a/src/parser/ast/block.lua b/src/parser/ast/block.lua index d5f79bd..7187a38 100644 --- a/src/parser/ast/block.lua +++ b/src/parser/ast/block.lua @@ -123,7 +123,7 @@ function Ast:blockParseChilds(block) block.childs[#block.childs+1] = state if lastState and lastState.type == 'Return' then ---@cast lastState LuaParser.Node.Return - self:throw('ACTION_AFTER_RETURN', lastState.start, lastState.start + #'return') + self:throw('ACTION_AFTER_RETURN', lastState.start, lastState.finish) end lastState = state self:skipSpace() diff --git a/src/parser/ast/boolean.lua b/src/parser/ast/boolean.lua index 1029c8e..628dda3 100644 --- a/src/parser/ast/boolean.lua +++ b/src/parser/ast/boolean.lua @@ -4,6 +4,8 @@ local class = require 'class' ---@field value boolean local Boolean = class.declare('LuaParser.Node.Boolean', 'LuaParser.Node.Literal') +Boolean.isLiteral = true + ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' diff --git a/src/parser/ast/call.lua b/src/parser/ast/call.lua index 0ca0141..37315fd 100644 --- a/src/parser/ast/call.lua +++ b/src/parser/ast/call.lua @@ -13,8 +13,12 @@ local Ast = class.declare 'LuaParser.Ast' ---@param last LuaParser.Node.Term ---@return LuaParser.Node.Call? function Ast:parseCall(last) - local pos = self.lexer:consume '(' - if pos then + local token, _, pos = self.lexer:peek() + if token == '(' then + if last.isLiteral then + return nil + end + self.lexer:next() local exps = self:parseExpList(false, true) self:assertSymbol ')' local call = self:createNode('LuaParser.Node.Call', { diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index 47d426c..57594d7 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -191,6 +191,16 @@ function Ast:parseTerm() end if chain then + if chain.type == 'Call' and self.versionNum <= 51 then + if current.finishRow ~= self.lexer:rowcol(chain.argPos) then + self:throw('AMBIGUOUS_SYNTAX', chain.argPos, chain.finish) + end + end + + if current.isLiteral then + self:throw('NEED_PAREN', current.start, current.finish) + end + current = chain else break diff --git a/src/parser/ast/function.lua b/src/parser/ast/function.lua index 86fee63..5489e94 100644 --- a/src/parser/ast/function.lua +++ b/src/parser/ast/function.lua @@ -19,6 +19,7 @@ local Param = class.declare('LuaParser.Node.Param', 'LuaParser.Node.Local') ---@field symbolPos3? integer # `end` local Function = class.declare('LuaParser.Node.Function', 'LuaParser.Node.Block') +Function.isLiteral = true Function.isFunction = true function Function.__getter.referFunction(self) @@ -129,14 +130,16 @@ function Ast:parseFunctionName() local chain = self:parseField(current) - if current.type == 'Field' - and current.subtype == 'method' then - if chain then + if chain then + if current.type == 'Field' + and current.subtype == 'method' then self:throwMissSymbol(current.finish, '(') end - end - if chain then + if chain.subtype == 'index' then + self:throw('INDEX_IN_FUNC_NAME', chain.symbolPos, chain.finish) + end + current = chain else break diff --git a/src/parser/ast/label.lua b/src/parser/ast/label.lua index d5802aa..2a3957e 100644 --- a/src/parser/ast/label.lua +++ b/src/parser/ast/label.lua @@ -68,7 +68,7 @@ function Ast:parseLabel() end end - if self.versionNum <= 51 then + if self.versionNum <= 51 and not self.jit then self:throw('UNSUPPORT_SYMBOL', pos, pos + 2) end @@ -108,7 +108,7 @@ function Ast:parseGoto() labelName.parent = gotoNode end - if self.versionNum <= 51 then + if self.versionNum <= 51 and not self.jit then self:throw('UNSUPPORT_SYMBOL', pos, pos + #'goto') end diff --git a/src/parser/ast/nil.lua b/src/parser/ast/nil.lua index 2605e42..34fb709 100644 --- a/src/parser/ast/nil.lua +++ b/src/parser/ast/nil.lua @@ -3,9 +3,11 @@ local class = require 'class' ---@class LuaParser.Node.Nil: LuaParser.Node.Literal local Nil = class.declare('LuaParser.Node.Nil', 'LuaParser.Node.Literal') +Nil.isLiteral = true Nil.toString = 'nil' Nil.isTruly = false + ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' diff --git a/src/parser/ast/number.lua b/src/parser/ast/number.lua index 970fb9c..a3179c9 100644 --- a/src/parser/ast/number.lua +++ b/src/parser/ast/number.lua @@ -9,6 +9,7 @@ local class = require 'class' ---@field view string local Float = class.declare('LuaParser.Node.Float', 'LuaParser.Node.Literal') +Float.isLiteral = true Float.value = 0.0 ---@param self LuaParser.Node.Float @@ -62,6 +63,7 @@ end ---@field view string local Integer = class.declare('LuaParser.Node.Integer', 'LuaParser.Node.Literal') +Integer.isLiteral = true Integer.value = 0 ---@param self LuaParser.Node.Integer diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua index f7c787f..5e37603 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state.lua @@ -86,7 +86,10 @@ function Ast:parseState() end if token == 'goto' then - return self:parseGoto() + local state = self:parseGoto() + if state then + return state + end end if token == 'repeat' then diff --git a/src/parser/ast/string.lua b/src/parser/ast/string.lua index d46e1c8..d39422c 100644 --- a/src/parser/ast/string.lua +++ b/src/parser/ast/string.lua @@ -10,6 +10,8 @@ local class = require 'class' ---@field missQuo? true local String = class.declare('LuaParser.Node.String', 'LuaParser.Node.Literal') +String.isLiteral = true + local escMap = { ['a'] = '\a', ['b'] = '\b', diff --git a/src/parser/ast/table.lua b/src/parser/ast/table.lua index d202bf5..ffbbff9 100644 --- a/src/parser/ast/table.lua +++ b/src/parser/ast/table.lua @@ -4,6 +4,8 @@ local class = require 'class' ---@field fields LuaParser.Node.Field[] local Table = class.declare('LuaParser.Node.Table', 'LuaParser.Node.Base') +Table.isLiteral = true + ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' From 7e91887a354b943feb2b463ca0aae537162aba27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Fri, 13 Oct 2023 15:42:58 +0800 Subject: [PATCH 74/94] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 3 -- src/parser/ast/ast.lua | 20 +++++++- src/parser/ast/cats/attr.lua | 12 +++++ src/parser/ast/cats/base.lua | 57 +++++++++++++++++++++ src/parser/ast/state.lua | 99 +++++++++++++++++------------------- test/ast/cats.lua | 14 +++++ 6 files changed, 148 insertions(+), 57 deletions(-) create mode 100644 src/parser/ast/cats/attr.lua create mode 100644 src/parser/ast/cats/base.lua create mode 100644 test/ast/cats.lua diff --git a/.vscode/launch.json b/.vscode/launch.json index 08fe051..f4c7bb6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -20,9 +20,7 @@ "print" ], "internalConsoleOptions": "openOnSessionStart", - "console": "internalConsole", "sourceCoding": "utf8", - "luaVersion": "5.4", "luaArch": "x86_64", }, { @@ -45,7 +43,6 @@ ], "console": "internalConsole", "sourceCoding": "utf8", - "luaVersion": "5.4", "luaArch": "x86_64", }, ] diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index dde4b2d..585fd94 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -10,7 +10,6 @@ require 'parser.ast.number' require 'parser.ast.string' require 'parser.ast.comment' require 'parser.ast.exp' -require 'parser.ast.state' require 'parser.ast.id' require 'parser.ast.local' require 'parser.ast.var' @@ -29,7 +28,9 @@ require 'parser.ast.return' require 'parser.ast.for' require 'parser.ast.while' require 'parser.ast.repeat' +require 'parser.ast.state' require 'parser.ast.main' +require 'parser.ast.cats.base' ---@class LuaParser.Ast ---@field envMode 'fenv' | '_ENV' @@ -37,6 +38,14 @@ require 'parser.ast.main' ---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast local M = class.declare 'LuaParser.Ast' +---@alias LuaParser.Status +---| 'Lua' # Lua代码 +---| 'ShortCats' # 单行的注解 +---| 'LongCats' # 多行的注解 +---| 'InLineCats' # 内联的注解 +---| 'ShortLua' # 单行注解内的Lua代码 +---| 'LongLua' # 多行注释内的Lua代码 + ---@param code string # lua代码 ---@param version? LuaParser.LuaVersion ---@param options? LuaParser.CompileOptions @@ -74,7 +83,15 @@ function M:__init(code, version, options) ---@type LuaParser.Node.Block[] self.blockList = {} -- 当前的局部变量计数(最大只能存在200个局部变量) + ---@private self.localCount = 0 + -- 当前解析状态 + ---@private + ---@type LuaParser.Status + self.status = 'Lua' + -- 未绑定的注解 + ---@private + self.cats = {} local major, minor = self.version:match 'Lua (%d+)%.(%d+)' ---@type integer @@ -151,6 +168,7 @@ function M:skipSpace(inExp) self.lastRightCI = self.lexer.ci end repeat until not self:skipNL() + and not self:skipCats() and not self:skipComment(inExp) and not self:skipUnknown() self.lastSpaceCI = self.lexer.ci diff --git a/src/parser/ast/cats/attr.lua b/src/parser/ast/cats/attr.lua new file mode 100644 index 0000000..ad41257 --- /dev/null +++ b/src/parser/ast/cats/attr.lua @@ -0,0 +1,12 @@ +local class = require 'class' + +---@class LuaParser.Node.CatsAttr: LuaParser.Node.Base +local CatsAttr = class.declare('LuaParser.Node.CatsAttr', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@return LuaParser.Node.CatsAttr[]? +function Ast:parseCatsAttrs() + +end diff --git a/src/parser/ast/cats/base.lua b/src/parser/ast/cats/base.lua new file mode 100644 index 0000000..a4b1eb9 --- /dev/null +++ b/src/parser/ast/cats/base.lua @@ -0,0 +1,57 @@ +local class = require 'class' + +---@class LuaParser.Node.Cats: LuaParser.Node.Base +---@field tailComments? string +---@field catType string +---@field symbolPos1 integer # @的位置 +---@field attrs? LuaParser.Node.CatsAttr[] +local CatsBase = class.declare('LuaParser.Node.Cats', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@return boolean +function Ast:skipCats() + if self:parseCats() + or self:parseCatsBlock() then + return true + else + return false + end +end + +-- 会将解析结果存放到 `Ast.cats` 中 +---@private +---@return LuaParser.Node.Cats? +function Ast:parseCats() + local token, _, pos = self.lexer:peek() + ---@cast pos -? + if token ~= '--' then + return nil + end + + -- 检查 `---@` 开头 + local symbolPos1, catType = self.code:match('%-[ \t]*()@(%a+)') + if not symbolPos1 then + return nil + end + + local cat = self:createNode('LuaParser.Node.Cats', { + start = pos, + catType = catType, + symbolPos1 = symbolPos1, + }) + + local attrs = self + if attrs then + cat.attrs = attrs + for _, attr in ipairs(attrs) do + attr.parent = cat + end + end +end + +-- 会将解析结果存放到 `Ast.cats` 中 +function Ast:parseCatsBlock() + +end diff --git a/src/parser/ast/state.lua b/src/parser/ast/state.lua index 5e37603..567ef8f 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state.lua @@ -33,69 +33,62 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.Function ---@private ----@return LuaParser.Node.State? -function Ast:parseState() - local token, _, pos = self.lexer:peek() - - if token == 'local' then - return self:parseLocal() - end - - if token == 'if' then - return self:parseIf() - end - - if token == 'do' then - return self:parseDo() - end - - if token == 'break' then - return self:parseBreak() - end - - if token == 'return' then - return self:parseReturn() - end - - if token == 'for' then - return self:parseFor() - end +Ast.stateParserMap = {} - if token == 'while' then - return self:parseWhile() - end +-- 注册语句解析 +---@private +---@param token string +---@param parser fun(self: LuaParser.Ast): LuaParser.Node.State?, boolean? +function Ast:registerStateParser(token, parser) + self.stateParserMap[token] = parser +end - if token == 'function' then - local func = self:parseFunction() - if not func then - self:throw('MISS_NAME', pos + #token) - return nil - end - if not func.name then - self:throw('MISS_NAME', func.symbolPos1) - end - return func +Ast:registerStateParser('local' , Ast.parseLocal) +Ast:registerStateParser('if' , Ast.parseIf) +Ast:registerStateParser('do' , Ast.parseDo) +Ast:registerStateParser('break' , Ast.parseBreak) +Ast:registerStateParser('return' , Ast.parseReturn) +Ast:registerStateParser('for' , Ast.parseFor) +Ast:registerStateParser('while' , Ast.parseWhile) +Ast:registerStateParser('repeat' , Ast.parseRepeat) +Ast:registerStateParser('continue', Ast.parseContinue) +Ast:registerStateParser('::' , Ast.parseLabel) +Ast:registerStateParser('goto' , function (ast) + ---@class LuaParser.Ast + local self = ast + local state = self:parseGoto() + if state then + return state + end + return nil, true +end) +Ast:registerStateParser('function', function (ast) + ---@class LuaParser.Ast + local self = ast + local func = self:parseFunction() + if not func then + self:throw('MISS_NAME', self:getLastPos()) + return nil end - - if token == 'continue' then - return self:parseContinue() + if not func.name then + self:throw('MISS_NAME', func.symbolPos1) end + return func +end) - if token == '::' then - return self:parseLabel() - end +---@private +---@return LuaParser.Node.State? +function Ast:parseState() + local token = self.lexer:peek() - if token == 'goto' then - local state = self:parseGoto() - if state then + local parser = self.stateParserMap[token] + if parser then + local state, dontConsumeToken = parser(self) + if not dontConsumeToken then return state end end - if token == 'repeat' then - return self:parseRepeat() - end - return self:parseStateStartWithExp() end diff --git a/test/ast/cats.lua b/test/ast/cats.lua new file mode 100644 index 0000000..4c7dce2 --- /dev/null +++ b/test/ast/cats.lua @@ -0,0 +1,14 @@ +local class = require 'class' + +local function TEST(code) + return function (expect) + ---@class LuaParser.Ast + local ast = class.new 'LuaParser.Ast' (code) + local cat = ast:parseCats() + Match(cat, expect) + end +end + +TEST [[ +---@class A +]] From 9105a13232559a073928a6600994610bbb342d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Fri, 13 Oct 2023 15:53:05 +0800 Subject: [PATCH 75/94] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 14 ++------------ src/parser/ast/cats/{base.lua => cats.lua} | 0 src/parser/ast/{ => state}/break.lua | 0 src/parser/ast/{ => state}/do.lua | 0 src/parser/ast/{ => state}/for.lua | 0 src/parser/ast/{ => state}/function.lua | 0 src/parser/ast/{ => state}/if.lua | 0 src/parser/ast/{ => state}/label.lua | 0 src/parser/ast/{ => state}/local.lua | 0 src/parser/ast/{ => state}/repeat.lua | 0 src/parser/ast/{ => state}/return.lua | 0 src/parser/ast/{ => state}/state.lua | 11 +++++++++++ src/parser/ast/{ => state}/while.lua | 0 13 files changed, 13 insertions(+), 12 deletions(-) rename src/parser/ast/cats/{base.lua => cats.lua} (100%) rename src/parser/ast/{ => state}/break.lua (100%) rename src/parser/ast/{ => state}/do.lua (100%) rename src/parser/ast/{ => state}/for.lua (100%) rename src/parser/ast/{ => state}/function.lua (100%) rename src/parser/ast/{ => state}/if.lua (100%) rename src/parser/ast/{ => state}/label.lua (100%) rename src/parser/ast/{ => state}/local.lua (100%) rename src/parser/ast/{ => state}/repeat.lua (100%) rename src/parser/ast/{ => state}/return.lua (100%) rename src/parser/ast/{ => state}/state.lua (94%) rename src/parser/ast/{ => state}/while.lua (100%) diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 585fd94..bfd01c8 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -11,26 +11,16 @@ require 'parser.ast.string' require 'parser.ast.comment' require 'parser.ast.exp' require 'parser.ast.id' -require 'parser.ast.local' require 'parser.ast.var' require 'parser.ast.call' require 'parser.ast.table' require 'parser.ast.block' -require 'parser.ast.function' require 'parser.ast.field' require 'parser.ast.unary' require 'parser.ast.binary' -require 'parser.ast.label' -require 'parser.ast.do' -require 'parser.ast.if' -require 'parser.ast.break' -require 'parser.ast.return' -require 'parser.ast.for' -require 'parser.ast.while' -require 'parser.ast.repeat' -require 'parser.ast.state' +require 'parser.ast.state.state' require 'parser.ast.main' -require 'parser.ast.cats.base' +require 'parser.ast.cats.cats' ---@class LuaParser.Ast ---@field envMode 'fenv' | '_ENV' diff --git a/src/parser/ast/cats/base.lua b/src/parser/ast/cats/cats.lua similarity index 100% rename from src/parser/ast/cats/base.lua rename to src/parser/ast/cats/cats.lua diff --git a/src/parser/ast/break.lua b/src/parser/ast/state/break.lua similarity index 100% rename from src/parser/ast/break.lua rename to src/parser/ast/state/break.lua diff --git a/src/parser/ast/do.lua b/src/parser/ast/state/do.lua similarity index 100% rename from src/parser/ast/do.lua rename to src/parser/ast/state/do.lua diff --git a/src/parser/ast/for.lua b/src/parser/ast/state/for.lua similarity index 100% rename from src/parser/ast/for.lua rename to src/parser/ast/state/for.lua diff --git a/src/parser/ast/function.lua b/src/parser/ast/state/function.lua similarity index 100% rename from src/parser/ast/function.lua rename to src/parser/ast/state/function.lua diff --git a/src/parser/ast/if.lua b/src/parser/ast/state/if.lua similarity index 100% rename from src/parser/ast/if.lua rename to src/parser/ast/state/if.lua diff --git a/src/parser/ast/label.lua b/src/parser/ast/state/label.lua similarity index 100% rename from src/parser/ast/label.lua rename to src/parser/ast/state/label.lua diff --git a/src/parser/ast/local.lua b/src/parser/ast/state/local.lua similarity index 100% rename from src/parser/ast/local.lua rename to src/parser/ast/state/local.lua diff --git a/src/parser/ast/repeat.lua b/src/parser/ast/state/repeat.lua similarity index 100% rename from src/parser/ast/repeat.lua rename to src/parser/ast/state/repeat.lua diff --git a/src/parser/ast/return.lua b/src/parser/ast/state/return.lua similarity index 100% rename from src/parser/ast/return.lua rename to src/parser/ast/state/return.lua diff --git a/src/parser/ast/state.lua b/src/parser/ast/state/state.lua similarity index 94% rename from src/parser/ast/state.lua rename to src/parser/ast/state/state.lua index 567ef8f..779686e 100644 --- a/src/parser/ast/state.lua +++ b/src/parser/ast/state/state.lua @@ -1,5 +1,16 @@ local class = require 'class' +require 'parser.ast.state.local' +require 'parser.ast.state.function' +require 'parser.ast.state.label' +require 'parser.ast.state.do' +require 'parser.ast.state.if' +require 'parser.ast.state.return' +require 'parser.ast.state.for' +require 'parser.ast.state.while' +require 'parser.ast.state.repeat' +require 'parser.ast.state.break' + ---@class LuaParser.Node.Assign: LuaParser.Node.Base ---@field symbolPos? integer # 等号的位置 ---@field exps LuaParser.Node.Exp[] diff --git a/src/parser/ast/while.lua b/src/parser/ast/state/while.lua similarity index 100% rename from src/parser/ast/while.lua rename to src/parser/ast/state/while.lua From 501cf36afa1078f0e10959eee8a0981abb2fa1d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Fri, 13 Oct 2023 18:16:38 +0800 Subject: [PATCH 76/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 4 +- src/parser/ast/cats/attr.lua | 8 +-- src/parser/ast/cats/cat.lua | 114 ++++++++++++++++++++++++++++++++++ src/parser/ast/cats/cats.lua | 57 ----------------- src/parser/ast/cats/class.lua | 25 ++++++++ src/parser/ast/cats/id.lua | 30 +++++++++ test/ast/cats.lua | 17 ++++- test/ast/init.lua | 1 + 8 files changed, 192 insertions(+), 64 deletions(-) create mode 100644 src/parser/ast/cats/cat.lua delete mode 100644 src/parser/ast/cats/cats.lua create mode 100644 src/parser/ast/cats/class.lua create mode 100644 src/parser/ast/cats/id.lua diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index bfd01c8..55390ed 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -20,7 +20,7 @@ require 'parser.ast.unary' require 'parser.ast.binary' require 'parser.ast.state.state' require 'parser.ast.main' -require 'parser.ast.cats.cats' +require 'parser.ast.cats.cat' ---@class LuaParser.Ast ---@field envMode 'fenv' | '_ENV' @@ -158,7 +158,7 @@ function M:skipSpace(inExp) self.lastRightCI = self.lexer.ci end repeat until not self:skipNL() - and not self:skipCats() + and not self:skipCat() and not self:skipComment(inExp) and not self:skipUnknown() self.lastSpaceCI = self.lexer.ci diff --git a/src/parser/ast/cats/attr.lua b/src/parser/ast/cats/attr.lua index ad41257..68a1d6c 100644 --- a/src/parser/ast/cats/attr.lua +++ b/src/parser/ast/cats/attr.lua @@ -1,12 +1,12 @@ local class = require 'class' ----@class LuaParser.Node.CatsAttr: LuaParser.Node.Base -local CatsAttr = class.declare('LuaParser.Node.CatsAttr', 'LuaParser.Node.Base') +---@class LuaParser.Node.CatAttr: LuaParser.Node.Base +local CatAttr = class.declare('LuaParser.Node.CatAttr', 'LuaParser.Node.Base') ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' ----@return LuaParser.Node.CatsAttr[]? -function Ast:parseCatsAttrs() +---@return LuaParser.Node.CatAttr[]? +function Ast:parseCatAttrs() end diff --git a/src/parser/ast/cats/cat.lua b/src/parser/ast/cats/cat.lua new file mode 100644 index 0000000..7a3e6df --- /dev/null +++ b/src/parser/ast/cats/cat.lua @@ -0,0 +1,114 @@ +local class = require 'class' + +require 'parser.ast.cats.id' +require 'parser.ast.cats.attr' +require 'parser.ast.cats.class' + +---@class LuaParser.Node.Cat: LuaParser.Node.Base +---@field subtype string +---@field symbolPos integer # @的位置 +---@field attrs? LuaParser.Node.CatAttr[] +---@field value? LuaParser.Node.CatValue +---@field tail? string +local Cat = class.declare('LuaParser.Node.Cat', 'LuaParser.Node.Base') + +---@alias LuaParser.Node.CatValue +---| LuaParser.Node.CatClass + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@private +Ast.catParserMap = {} + +---@private +---@param catType string +---@param parser fun(self: LuaParser.Ast, cat: LuaParser.Node.Cat) +function Ast:registerCatParser(catType, parser) + Ast.catParserMap[catType] = parser +end + +Ast:registerCatParser('class', Ast.parseCatClass) + +---@private +---@return boolean +function Ast:skipCat() + if self:parseCat() + or self:parseCatBlock() then + return true + else + return false + end +end + +-- 会将解析结果存放到 `Ast.cats` 中 +---@private +---@return LuaParser.Node.Cat? +function Ast:parseCat() + local token, _, pos = self.lexer:peek() + ---@cast pos -? + if token ~= '--' then + return nil + end + + -- 检查 `---@` 开头 + local symbolPos, subtype = self.code:match('%-[ \t]*()@(%a+)', pos + 3) + if not symbolPos then + return nil + end + + self.lexer:fastForward(symbolPos + #subtype) + + local cat = self:createNode('LuaParser.Node.Cat', { + start = pos, + subtype = subtype, + symbolPos = symbolPos - 1, + }) + + local attrs = self:parseCatAttrs() + if attrs then + cat.attrs = attrs + for _, attr in ipairs(attrs) do + attr.parent = cat + end + end + + local parser = Ast.catParserMap[cat.subtype] + if parser then + local value = parser(self, cat) + if value then + cat.value = value + value.parent = cat + end + end + + cat.finish = self:getLastPos() + + cat.tail = self:parseTail() + + return cat +end + +-- 会将解析结果存放到 `Ast.cats` 中 +function Ast:parseCatBlock() + +end + +---@return string? +function Ast:parseTail() + local startOffset = self:getLastPos() + 1 + local tail = self.code:match('[^\r\n]+', startOffset) + if not tail then + return nil + end + + self.lexer:fastForward(startOffset + #tail) + + tail = tail:gsub('^%s*[@#]?%s*', '') + + if tail == '' then + return nil + end + + return tail +end diff --git a/src/parser/ast/cats/cats.lua b/src/parser/ast/cats/cats.lua deleted file mode 100644 index a4b1eb9..0000000 --- a/src/parser/ast/cats/cats.lua +++ /dev/null @@ -1,57 +0,0 @@ -local class = require 'class' - ----@class LuaParser.Node.Cats: LuaParser.Node.Base ----@field tailComments? string ----@field catType string ----@field symbolPos1 integer # @的位置 ----@field attrs? LuaParser.Node.CatsAttr[] -local CatsBase = class.declare('LuaParser.Node.Cats', 'LuaParser.Node.Base') - ----@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' - ----@return boolean -function Ast:skipCats() - if self:parseCats() - or self:parseCatsBlock() then - return true - else - return false - end -end - --- 会将解析结果存放到 `Ast.cats` 中 ----@private ----@return LuaParser.Node.Cats? -function Ast:parseCats() - local token, _, pos = self.lexer:peek() - ---@cast pos -? - if token ~= '--' then - return nil - end - - -- 检查 `---@` 开头 - local symbolPos1, catType = self.code:match('%-[ \t]*()@(%a+)') - if not symbolPos1 then - return nil - end - - local cat = self:createNode('LuaParser.Node.Cats', { - start = pos, - catType = catType, - symbolPos1 = symbolPos1, - }) - - local attrs = self - if attrs then - cat.attrs = attrs - for _, attr in ipairs(attrs) do - attr.parent = cat - end - end -end - --- 会将解析结果存放到 `Ast.cats` 中 -function Ast:parseCatsBlock() - -end diff --git a/src/parser/ast/cats/class.lua b/src/parser/ast/cats/class.lua new file mode 100644 index 0000000..acd0f81 --- /dev/null +++ b/src/parser/ast/cats/class.lua @@ -0,0 +1,25 @@ +local class = require 'class' + +---@class LuaParser.Node.CatClass: LuaParser.Node.Base +---@field classID LuaParser.Node.CatID +local CatClass = class.declare('LuaParser.Node.CatClass', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare('LuaParser.Ast') + +---@return LuaParser.Node.CatClass? +function Ast:parseCatClass() + local classID = self:parseCatID() + if not classID then + return nil + end + + local catClass = self:createNode('LuaParser.Node.CatClass', { + classID = classID, + start = classID.start, + }) + + catClass.finish = self:getLastPos() + + return catClass +end diff --git a/src/parser/ast/cats/id.lua b/src/parser/ast/cats/id.lua new file mode 100644 index 0000000..cf37f66 --- /dev/null +++ b/src/parser/ast/cats/id.lua @@ -0,0 +1,30 @@ +local class = require 'class' + +---@class LuaParser.Node.CatID: LuaParser.Node.Base +---@field id string +local CatID = class.declare('LuaParser.Node.CatID', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare('LuaParser.Ast') + +---@return LuaParser.Node.CatID? +function Ast:parseCatID() + local _, _, pos = self.lexer:peek() + if not pos then + return nil + end + + local id = self.code:match('[%a\x80-\xff_][%w\x80-\xff_%.%*%-]*', pos + 1) + if not id then + return nil + end + + local finish = pos + #id + self.lexer:fastForward(finish) + + return self:createNode('LuaParser.Node.CatID', { + id = id, + start = pos, + finish = finish, + }) +end diff --git a/test/ast/cats.lua b/test/ast/cats.lua index 4c7dce2..0e8abb2 100644 --- a/test/ast/cats.lua +++ b/test/ast/cats.lua @@ -4,7 +4,7 @@ local function TEST(code) return function (expect) ---@class LuaParser.Ast local ast = class.new 'LuaParser.Ast' (code) - local cat = ast:parseCats() + local cat = ast:parseCat() Match(cat, expect) end end @@ -12,3 +12,18 @@ end TEST [[ ---@class A ]] +{ + subtype = 'class', + start = 0, + finish = 11, + symbolPos = 3, + value = { + start = 10, + finish = 11, + classID = { + start = 10, + finish = 11, + id = 'A', + }, + } +} diff --git a/test/ast/init.lua b/test/ast/init.lua index 41b8299..81433f3 100644 --- a/test/ast/init.lua +++ b/test/ast/init.lua @@ -29,3 +29,4 @@ require 'test.ast.exp' require 'test.ast.state' require 'test.ast.block' require 'test.ast.main' +require 'test.ast.cats' From 28cfe7a1c9776378e7149a291babe752938d732b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Mon, 16 Oct 2023 16:02:33 +0800 Subject: [PATCH 77/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/cats/attr.lua | 12 ------- src/parser/ast/cats/cat.lua | 19 ++++++++--- src/parser/ast/cats/class.lua | 14 ++++++++ src/parser/ast/cats/type.lua | 21 ++++++++++++ src/parser/ast/id.lua | 1 + test/ast/cats.lua | 63 +++++++++++++++++++++++++++++++++++ 6 files changed, 113 insertions(+), 17 deletions(-) delete mode 100644 src/parser/ast/cats/attr.lua create mode 100644 src/parser/ast/cats/type.lua diff --git a/src/parser/ast/cats/attr.lua b/src/parser/ast/cats/attr.lua deleted file mode 100644 index 68a1d6c..0000000 --- a/src/parser/ast/cats/attr.lua +++ /dev/null @@ -1,12 +0,0 @@ -local class = require 'class' - ----@class LuaParser.Node.CatAttr: LuaParser.Node.Base -local CatAttr = class.declare('LuaParser.Node.CatAttr', 'LuaParser.Node.Base') - ----@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' - ----@return LuaParser.Node.CatAttr[]? -function Ast:parseCatAttrs() - -end diff --git a/src/parser/ast/cats/cat.lua b/src/parser/ast/cats/cat.lua index 7a3e6df..06d4f3f 100644 --- a/src/parser/ast/cats/cat.lua +++ b/src/parser/ast/cats/cat.lua @@ -1,20 +1,27 @@ local class = require 'class' require 'parser.ast.cats.id' -require 'parser.ast.cats.attr' require 'parser.ast.cats.class' +require 'parser.ast.cats.type' ---@class LuaParser.Node.Cat: LuaParser.Node.Base ---@field subtype string ---@field symbolPos integer # @的位置 +---@field attrPos1? integer # 左括号的位置 +---@field attrPos2? integer # 右括号的位置 ---@field attrs? LuaParser.Node.CatAttr[] ---@field value? LuaParser.Node.CatValue +---@field extends? LuaParser.Node.CatType ---@field tail? string local Cat = class.declare('LuaParser.Node.Cat', 'LuaParser.Node.Base') ---@alias LuaParser.Node.CatValue ---| LuaParser.Node.CatClass +---@class LuaParser.Node.CatAttr: LuaParser.Node.Base +---@field id string +local CatAttr = class.declare('LuaParser.Node.CatAttr', 'LuaParser.Node.Base') + ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' @@ -65,12 +72,14 @@ function Ast:parseCat() symbolPos = symbolPos - 1, }) - local attrs = self:parseCatAttrs() - if attrs then - cat.attrs = attrs - for _, attr in ipairs(attrs) do + local attrPos1 = self.lexer:consume '(' + if attrPos1 then + cat.attrPos1 = attrPos1 + cat.attrs = self:parseIDList('LuaParser.Node.CatAttr', true, false) + for _, attr in ipairs(cat.attrs) do attr.parent = cat end + cat.attrPos2 = self.lexer:consume ')' end local parser = Ast.catParserMap[cat.subtype] diff --git a/src/parser/ast/cats/class.lua b/src/parser/ast/cats/class.lua index acd0f81..b7bcaf2 100644 --- a/src/parser/ast/cats/class.lua +++ b/src/parser/ast/cats/class.lua @@ -2,6 +2,8 @@ local class = require 'class' ---@class LuaParser.Node.CatClass: LuaParser.Node.Base ---@field classID LuaParser.Node.CatID +---@field symbolPos? integer # :的位置 +---@field extends? LuaParser.Node.CatType local CatClass = class.declare('LuaParser.Node.CatClass', 'LuaParser.Node.Base') ---@class LuaParser.Ast @@ -19,6 +21,18 @@ function Ast:parseCatClass() start = classID.start, }) + classID.parent = catClass + + local symbolPos = self.lexer:consume ':' + if symbolPos then + catClass.symbolPos = symbolPos + local extends = self:parseCatType(true) + if extends then + catClass.extends = extends + extends.parent = catClass + end + end + catClass.finish = self:getLastPos() return catClass diff --git a/src/parser/ast/cats/type.lua b/src/parser/ast/cats/type.lua new file mode 100644 index 0000000..d8b9351 --- /dev/null +++ b/src/parser/ast/cats/type.lua @@ -0,0 +1,21 @@ +local class = require 'class' + +---@alias LuaParser.Node.CatType +---| LuaParser.Node.CatID + +---@class LuaParser.Ast +local Ast = class.declare('LuaParser.Ast') + +---@param require? boolean +---@return LuaParser.Node.CatType? +function Ast:parseCatType(require) + local name = self:parseCatID() + if not name then + if require then + self:throw('MISS_CAT_NAME', self:getLastPos()) + end + return nil + end + + return name +end diff --git a/src/parser/ast/id.lua b/src/parser/ast/id.lua index f33378f..eaecf28 100644 --- a/src/parser/ast/id.lua +++ b/src/parser/ast/id.lua @@ -11,6 +11,7 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.Param ---| LuaParser.Node.LabelName ---| LuaParser.Node.AttrName +---| LuaParser.Node.CatAttr ---@private ---@generic T: LuaParser.Node.ID diff --git a/test/ast/cats.lua b/test/ast/cats.lua index 0e8abb2..64dfbc1 100644 --- a/test/ast/cats.lua +++ b/test/ast/cats.lua @@ -27,3 +27,66 @@ TEST [[ }, } } + +TEST [[ +---@class(exact, what1, what2) A +]] +{ + subtype = 'class', + start = 0, + finish = 32, + symbolPos = 3, + attrPos1 = 9, + attrPos2 = 29, + attrs = { + [1] = { + start = 10, + finish = 15, + id = 'exact', + }, + [2] = { + start = 17, + finish = 22, + id = 'what1', + }, + [3] = { + start = 24, + finish = 29, + id = 'what2', + }, + }, + value = { + start = 31, + finish = 32, + classID = { + start = 31, + finish = 32, + id = 'A', + }, + } +} + +TEST [[ +---@class A: B +]] +{ + subtype = 'class', + start = 0, + finish = 14, + symbolPos = 3, + value = { + start = 10, + finish = 14, + classID = { + start = 10, + finish = 11, + id = 'A', + }, + symbolPos = 11, + extends = { + id = 'B', + start = 13, + finish = 14, + } + } +} From 498dd9bd7dd50b7a72fcf922c3c05703141bd41b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Mon, 16 Oct 2023 17:48:50 +0800 Subject: [PATCH 78/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90array?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 9 +++- src/parser/ast/cats/cat.lua | 29 +++++++++--- src/parser/ast/cats/type.lua | 88 +++++++++++++++++++++++++++++++++--- test/ast/cats.lua | 58 ++++++++++++++++++++++++ 4 files changed, 171 insertions(+), 13 deletions(-) diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 55390ed..0db6cd5 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -119,7 +119,14 @@ end ---@private ---@return boolean # 是否成功跳过换行符 function M:skipNL() - return self.lexer:consumeType 'NL' ~= nil + local status = self.status + if status == 'Lua' + or status == 'LongCats' + or status == 'LongLua' then + return self.lexer:consumeType 'NL' ~= nil + else + return false + end end -- 跳过注释 diff --git a/src/parser/ast/cats/cat.lua b/src/parser/ast/cats/cat.lua index 06d4f3f..11e5567 100644 --- a/src/parser/ast/cats/cat.lua +++ b/src/parser/ast/cats/cat.lua @@ -17,6 +17,7 @@ local Cat = class.declare('LuaParser.Node.Cat', 'LuaParser.Node.Base') ---@alias LuaParser.Node.CatValue ---| LuaParser.Node.CatClass +---| LuaParser.Node.CatType ---@class LuaParser.Node.CatAttr: LuaParser.Node.Base ---@field id string @@ -30,12 +31,16 @@ Ast.catParserMap = {} ---@private ---@param catType string ----@param parser fun(self: LuaParser.Ast, cat: LuaParser.Node.Cat) +---@param parser fun(self: LuaParser.Ast) function Ast:registerCatParser(catType, parser) Ast.catParserMap[catType] = parser end Ast:registerCatParser('class', Ast.parseCatClass) +Ast:registerCatParser('type', function (self) + ---@diagnostic disable-next-line: invisible + return self:parseCatType(true) +end) ---@private ---@return boolean @@ -64,7 +69,17 @@ function Ast:parseCat() return nil end - self.lexer:fastForward(symbolPos + #subtype) + ---@type LuaParser.Status + local oldStatus = self.status + if oldStatus == 'Lua' then + self.status = 'ShortCats' + elseif oldStatus == 'LongCats' then + else + return nil + end + + local nextPos = symbolPos + #subtype + self.lexer:fastForward(nextPos) local cat = self:createNode('LuaParser.Node.Cat', { start = pos, @@ -72,9 +87,9 @@ function Ast:parseCat() symbolPos = symbolPos - 1, }) - local attrPos1 = self.lexer:consume '(' - if attrPos1 then - cat.attrPos1 = attrPos1 + if self.code:sub(nextPos + 1, nextPos + 1) == '(' then + cat.attrPos1 = nextPos + self.lexer:fastForward(nextPos + 1) cat.attrs = self:parseIDList('LuaParser.Node.CatAttr', true, false) for _, attr in ipairs(cat.attrs) do attr.parent = cat @@ -84,7 +99,7 @@ function Ast:parseCat() local parser = Ast.catParserMap[cat.subtype] if parser then - local value = parser(self, cat) + local value = parser(self) if value then cat.value = value value.parent = cat @@ -95,6 +110,8 @@ function Ast:parseCat() cat.tail = self:parseTail() + self.status = oldStatus + return cat end diff --git a/src/parser/ast/cats/type.lua b/src/parser/ast/cats/type.lua index d8b9351..ca82dde 100644 --- a/src/parser/ast/cats/type.lua +++ b/src/parser/ast/cats/type.lua @@ -2,20 +2,96 @@ local class = require 'class' ---@alias LuaParser.Node.CatType ---| LuaParser.Node.CatID +---| LuaParser.Node.CatParen +---| LuaParser.Node.CatArray + +---@class LuaParser.Node.CatParen: LuaParser.Node.Base +---@field value? LuaParser.Node.CatType +---@field symbolPos? integer # 右括号的位置 +local CatParen = class.declare('LuaParser.Node.CatParen', 'LuaParser.Node.Base') + +---@class LuaParser.Node.CatArray: LuaParser.Node.Base +---@field node? LuaParser.Node.CatType +---@field symbolPos1 integer # 左括号的位置 +---@field symbolPos2? integer # 右括号的位置 +local CatArray = class.declare('LuaParser.Node.CatArray', 'LuaParser.Node.Base') ---@class LuaParser.Ast local Ast = class.declare('LuaParser.Ast') ----@param require? boolean +---@private +---@param required? boolean ---@return LuaParser.Node.CatType? -function Ast:parseCatType(require) - local name = self:parseCatID() - if not name then - if require then +function Ast:parseCatType(required) + local head = self:parseCatParen() + or self:parseCatID() + + if not head then + if required then self:throw('MISS_CAT_NAME', self:getLastPos()) end return nil end - return name + ---@type LuaParser.Node.CatType + local current = head + while true do + self:skipSpace() + + local chain = self:parseCatArray(current) + + if chain then + current = chain + else + break + end + end + + return current +end + +---@private +---@return LuaParser.Node.CatParen? +function Ast:parseCatParen() + local plPos = self.lexer:consume '(' + if not plPos then + return nil + end + + local value = self:parseCatType(true) + local paren = self:createNode('LuaParser.Node.CatParen', { + start = plPos, + value = value, + }) + + if value then + value.parent = paren + end + + paren.symbolPos = self:assertSymbol ')' + paren.finish = self:getLastPos() + + return paren +end + +---@private +---@param head LuaParser.Node.CatType +---@return LuaParser.Node.CatArray? +function Ast:parseCatArray(head) + local pos1 = self.lexer:consume '[' + if not pos1 then + return nil + end + local array = self:createNode('LuaParser.Node.CatArray', { + start = head.start, + node = head, + symbolPos1 = pos1, + }) + + head.parent = array + + array.symbolPos2 = self:assertSymbol ']' + array.finish = self:getLastPos() + + return array end diff --git a/test/ast/cats.lua b/test/ast/cats.lua index 64dfbc1..9a1fa9e 100644 --- a/test/ast/cats.lua +++ b/test/ast/cats.lua @@ -90,3 +90,61 @@ TEST [[ } } } + +TEST [[ +---@type string +]] +{ + subtype = 'type', + start = 0, + finish = 15, + value = { + type = 'CatID', + id = 'string', + start = 9, + finish = 15, + } +} + +TEST [[ +---@type (string) +]] +{ + subtype = 'type', + start = 0, + finish = 17, + value = { + type = 'CatParen', + symbolPos = 16, + start = 9, + finish = 17, + value = { + type = 'CatID', + id = 'string', + start = 10, + finish = 16, + } + } +} + +TEST [[ +---@type string[] +]] +{ + subtype = 'type', + start = 0, + finish = 17, + value = { + type = 'CatArray', + start = 9, + finish = 17, + symbolPos1 = 15, + symbolPos2 = 16, + node = { + type = 'CatID', + id = 'string', + start = 9, + finish = 15, + } + } +} From a2e510306bf2c0c4596e608357ef8daf0472a5e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Mon, 16 Oct 2023 18:33:49 +0800 Subject: [PATCH 79/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=20CatCall?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/cats/type.lua | 91 +++++++++++++++++++++++++++++++++++- test/ast/cats.lua | 27 +++++++++++ 2 files changed, 117 insertions(+), 1 deletion(-) diff --git a/src/parser/ast/cats/type.lua b/src/parser/ast/cats/type.lua index ca82dde..c483828 100644 --- a/src/parser/ast/cats/type.lua +++ b/src/parser/ast/cats/type.lua @@ -4,6 +4,7 @@ local class = require 'class' ---| LuaParser.Node.CatID ---| LuaParser.Node.CatParen ---| LuaParser.Node.CatArray +---| LuaParser.Node.CatCall ---@class LuaParser.Node.CatParen: LuaParser.Node.Base ---@field value? LuaParser.Node.CatType @@ -11,11 +12,18 @@ local class = require 'class' local CatParen = class.declare('LuaParser.Node.CatParen', 'LuaParser.Node.Base') ---@class LuaParser.Node.CatArray: LuaParser.Node.Base ----@field node? LuaParser.Node.CatType +---@field node LuaParser.Node.CatType ---@field symbolPos1 integer # 左括号的位置 ---@field symbolPos2? integer # 右括号的位置 local CatArray = class.declare('LuaParser.Node.CatArray', 'LuaParser.Node.Base') +---@class LuaParser.Node.CatCall: LuaParser.Node.Base +---@field node LuaParser.Node.CatID +---@field args LuaParser.Node.CatType[] +---@field symbolPos1 integer # 左括号的位置 +---@field symbolPos2? integer # 右括号的位置 +local CatCall = class.declare('LuaParser.Node.CatCall', 'LuaParser.Node.Base') + ---@class LuaParser.Ast local Ast = class.declare('LuaParser.Ast') @@ -39,6 +47,7 @@ function Ast:parseCatType(required) self:skipSpace() local chain = self:parseCatArray(current) + or self:parseCatCall(current) if chain then current = chain @@ -50,6 +59,47 @@ function Ast:parseCatType(required) return current end +---@private +---@param atLeastOne? boolean +---@return LuaParser.Node.CatType[] +function Ast:parseCatTypeList(atLeastOne) + ---@type LuaParser.Node.CatType[] + local list = {} + local first = self:parseCatType(atLeastOne) + list[#list+1] = first + local wantSep = first ~= nil + while true do + self:skipSpace() + local token, tp, pos = self.lexer:peek() + if not token then + break + end + ---@cast pos -? + if tp == 'Symbol' then + if token == ',' then + if not wantSep then + self:throw('UNEXPECT_SYMBOL', pos, pos + 1, { + symbol = ',', + }) + end + self.lexer:next() + self:skipSpace() + wantSep = false + else + break + end + else + break + end + local catType = self:parseCatType(true) + if catType then + list[#list+1] = catType + end + wantSep = true + end + return list +end + ---@private ---@return LuaParser.Node.CatParen? function Ast:parseCatParen() @@ -58,6 +108,7 @@ function Ast:parseCatParen() return nil end + self:skipSpace() local value = self:parseCatType(true) local paren = self:createNode('LuaParser.Node.CatParen', { start = plPos, @@ -68,6 +119,7 @@ function Ast:parseCatParen() value.parent = paren end + self:skipSpace() paren.symbolPos = self:assertSymbol ')' paren.finish = self:getLastPos() @@ -90,8 +142,45 @@ function Ast:parseCatArray(head) head.parent = array + self:skipSpace() array.symbolPos2 = self:assertSymbol ']' array.finish = self:getLastPos() return array end + +---@private +---@param head LuaParser.Node.CatType +---@return LuaParser.Node.CatCall? +function Ast:parseCatCall(head) + local pos1 = self.lexer:consume '<' + if not pos1 then + return nil + end + + local call = self:createNode('LuaParser.Node.CatCall', { + start = head.start, + node = head, + symbolPos1 = pos1, + }) + head.parent = call + + self:skipSpace() + local args = self:parseCatTypeList(true) + call.args = args + + for i = 1, #args do + local arg = args[i] + arg.parent = call + end + + self:skipSpace() + call.symbolPos2 = self:assertSymbol '>' + call.finish = self:getLastPos() + + if head.type ~= 'CatID' then + self:throw('UNEXPECT_CAT_CALL', pos1, call.finish) + end + + return call +end diff --git a/test/ast/cats.lua b/test/ast/cats.lua index 9a1fa9e..fa1b48c 100644 --- a/test/ast/cats.lua +++ b/test/ast/cats.lua @@ -148,3 +148,30 @@ TEST [[ } } } + +TEST [[ +---@type A +]] +{ + subtype = 'type', + start = 0, + finish = 27, + value = { + type = 'CatCall', + start = 9, + finish = 27, + symbolPos1 = 10, + symbolPos2 = 26, + node = { + id = 'A', + }, + args = { + [1] = { + id = 'boolean', + }, + [2] = { + id = 'string', + } + }, + } +} From f3f55750d6e17f9a1325482d187f05cb0adf49fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Tue, 17 Oct 2023 11:42:07 +0800 Subject: [PATCH 80/94] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/cats/type.lua | 12 ++++++++++-- src/parser/ast/exp.lua | 27 +++++++++++++++------------ 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/parser/ast/cats/type.lua b/src/parser/ast/cats/type.lua index c483828..d1cbd03 100644 --- a/src/parser/ast/cats/type.lua +++ b/src/parser/ast/cats/type.lua @@ -6,10 +6,10 @@ local class = require 'class' ---| LuaParser.Node.CatArray ---| LuaParser.Node.CatCall ----@class LuaParser.Node.CatParen: LuaParser.Node.Base +---@class LuaParser.Node.CatParen: LuaParser.Node.ParenBase ---@field value? LuaParser.Node.CatType ---@field symbolPos? integer # 右括号的位置 -local CatParen = class.declare('LuaParser.Node.CatParen', 'LuaParser.Node.Base') +local CatParen = class.declare('LuaParser.Node.CatParen', 'LuaParser.Node.ParenBase') ---@class LuaParser.Node.CatArray: LuaParser.Node.Base ---@field node LuaParser.Node.CatType @@ -31,6 +31,14 @@ local Ast = class.declare('LuaParser.Ast') ---@param required? boolean ---@return LuaParser.Node.CatType? function Ast:parseCatType(required) + local first = self:parseCatTerm(required) + return first +end + +---@private +---@param required? boolean +---@return LuaParser.Node.CatType? +function Ast:parseCatTerm(required) local head = self:parseCatParen() or self:parseCatID() diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index 57594d7..64f442d 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -1,42 +1,45 @@ local class = require 'class' ----@class LuaParser.Node.Paren: LuaParser.Node.Base ----@field exp? LuaParser.Node.Exp ----@field next? LuaParser.Node.Field -local Paren = class.declare('LuaParser.Node.Paren', 'LuaParser.Node.Base') +---@class LuaParser.Node.ParenBase: LuaParser.Node.Base +local ParenBase = class.declare('LuaParser.Node.ParenBase', 'LuaParser.Node.Base') -function Paren.__getter.asNumber(self) +function ParenBase.__getter.asNumber(self) return self.exp.asNumber, true end -function Paren.__getter.asString(self) +function ParenBase.__getter.asString(self) return self.exp.asString, true end -function Paren.__getter.asBoolean(self) +function ParenBase.__getter.asBoolean(self) return self.exp.asBoolean, true end -function Paren.__getter.asInteger(self) +function ParenBase.__getter.asInteger(self) return self.exp.asInteger, true end -function Paren.__getter.toNumber(self) +function ParenBase.__getter.toNumber(self) return self.exp.toNumber, true end -function Paren.__getter.toString(self) +function ParenBase.__getter.toString(self) return self.exp.toString, true end -function Paren.__getter.toInteger(self) +function ParenBase.__getter.toInteger(self) return self.exp.toInteger, true end -function Paren.__getter.isTruly(self) +function ParenBase.__getter.isTruly(self) return self.exp.isTruly, true end +---@class LuaParser.Node.Paren: LuaParser.Node.ParenBase +---@field exp? LuaParser.Node.Exp +---@field next? LuaParser.Node.Field +local Paren = class.declare('LuaParser.Node.Paren', 'LuaParser.Node.ParenBase') + ---@class LuaParser.Ast local Ast = class.declare 'LuaParser.Ast' From 8c23298ab9592c2d1ccbf6acd4361b14c40d50f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Tue, 17 Oct 2023 18:08:17 +0800 Subject: [PATCH 81/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=20union?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/cats/cat.lua | 1 + src/parser/ast/cats/type.lua | 18 +++++++++++++-- src/parser/ast/cats/union.lua | 41 +++++++++++++++++++++++++++++++++++ test/ast/cats.lua | 26 ++++++++++++++++++++++ 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 src/parser/ast/cats/union.lua diff --git a/src/parser/ast/cats/cat.lua b/src/parser/ast/cats/cat.lua index 11e5567..613ee6e 100644 --- a/src/parser/ast/cats/cat.lua +++ b/src/parser/ast/cats/cat.lua @@ -3,6 +3,7 @@ local class = require 'class' require 'parser.ast.cats.id' require 'parser.ast.cats.class' require 'parser.ast.cats.type' +require 'parser.ast.cats.union' ---@class LuaParser.Node.Cat: LuaParser.Node.Base ---@field subtype string diff --git a/src/parser/ast/cats/type.lua b/src/parser/ast/cats/type.lua index d1cbd03..0034ba4 100644 --- a/src/parser/ast/cats/type.lua +++ b/src/parser/ast/cats/type.lua @@ -5,6 +5,7 @@ local class = require 'class' ---| LuaParser.Node.CatParen ---| LuaParser.Node.CatArray ---| LuaParser.Node.CatCall +---| LuaParser.Node.CatUnion ---@class LuaParser.Node.CatParen: LuaParser.Node.ParenBase ---@field value? LuaParser.Node.CatType @@ -31,8 +32,21 @@ local Ast = class.declare('LuaParser.Ast') ---@param required? boolean ---@return LuaParser.Node.CatType? function Ast:parseCatType(required) - local first = self:parseCatTerm(required) - return first + local curExp = self:parseCatTerm(required) + if not curExp then + return nil + end + self:skipSpace() + + while true do + local union = self:parseCatUnion(curExp) + if not union then + break + end + self:skipSpace() + curExp = union + end + return curExp end ---@private diff --git a/src/parser/ast/cats/union.lua b/src/parser/ast/cats/union.lua new file mode 100644 index 0000000..f902388 --- /dev/null +++ b/src/parser/ast/cats/union.lua @@ -0,0 +1,41 @@ +local class = require'class' + +---@class LuaParser.Node.CatUnion: LuaParser.Node.Base +---@field poses integer[] # 所有 | 的位置 +---@field exps LuaParser.Node.CatType[] # 所有的子表达式 +local Union = class.declare('LuaParser.Node.CatUnion', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@private +---@param exp LuaParser.Node.CatType +---@return LuaParser.Node.CatUnion? +function Ast:parseCatUnion(exp) + local pos = self.lexer:consume '|' + if not pos then + return nil + end + + ---@type LuaParser.Node.CatUnion + local union + + if exp.type == 'CatUnion' then + ---@cast exp LuaParser.Node.CatUnion + union = exp + union.poses[#union.poses+1] = pos + else + union = self:createNode('LuaParser.Node.CatUnion', { + start = exp.start, + poses = { pos }, + exps = { exp }, + }) + end + + local nextNode = self:parseCatTerm(true) + union.exps[#union.exps+1] = nextNode + + union.finish = self:getLastPos() + + return union +end diff --git a/test/ast/cats.lua b/test/ast/cats.lua index fa1b48c..eece60f 100644 --- a/test/ast/cats.lua +++ b/test/ast/cats.lua @@ -175,3 +175,29 @@ TEST [[ }, } } + +TEST [[ +---@type A | B | C +]] +{ + subtype = 'type', + start = 0, + finish = 18, + value = { + type = 'CatUnion', + start = 9, + finish = 18, + poses = {11, 15}, + exps = { + [1] = { + id = 'A', + }, + [2] = { + id = 'B', + }, + [3] = { + id = 'C', + } + } + } +} From d1e85347def722903f60a2244b6506bc8b623531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Tue, 17 Oct 2023 18:34:50 +0800 Subject: [PATCH 82/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=20cross?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/cats/cat.lua | 1 + src/parser/ast/cats/cross.lua | 48 +++++++++++++++++++++++++++++++++++ src/parser/ast/cats/type.lua | 18 +++---------- src/parser/ast/cats/union.lua | 47 ++++++++++++++++++++-------------- test/ast/cats.lua | 41 ++++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 34 deletions(-) create mode 100644 src/parser/ast/cats/cross.lua diff --git a/src/parser/ast/cats/cat.lua b/src/parser/ast/cats/cat.lua index 613ee6e..8d18bc3 100644 --- a/src/parser/ast/cats/cat.lua +++ b/src/parser/ast/cats/cat.lua @@ -4,6 +4,7 @@ require 'parser.ast.cats.id' require 'parser.ast.cats.class' require 'parser.ast.cats.type' require 'parser.ast.cats.union' +require 'parser.ast.cats.cross' ---@class LuaParser.Node.Cat: LuaParser.Node.Base ---@field subtype string diff --git a/src/parser/ast/cats/cross.lua b/src/parser/ast/cats/cross.lua new file mode 100644 index 0000000..6dfc2e4 --- /dev/null +++ b/src/parser/ast/cats/cross.lua @@ -0,0 +1,48 @@ +local class = require'class' + +---@class LuaParser.Node.CatCross: LuaParser.Node.Base +---@field poses integer[] # 所有 & 的位置 +---@field exps LuaParser.Node.CatType[] # 所有的子表达式 +local Cross = class.declare('LuaParser.Node.CatCross', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare 'LuaParser.Ast' + +---@private +---@param required? boolean +---@return LuaParser.Node.CatType? +function Ast:parseCatCross(required) + local first = self:parseCatTerm(required) + if not first then + return nil + end + + local pos = self.lexer:consume '&' + if not pos then + return first + end + + local cross = self:createNode('LuaParser.Node.CatCross', { + start = first.start, + poses = { pos }, + exps = { first }, + }) + + while true do + self:skipSpace() + local nextNode = self:parseCatTerm(true) + cross.exps[#cross.exps+1] = nextNode + + self:skipSpace() + local nextPos = self.lexer:consume '&' + if not nextPos then + break + end + + cross.poses[#cross.poses+1] = nextPos + end + + cross.finish = self:getLastPos() + + return cross +end diff --git a/src/parser/ast/cats/type.lua b/src/parser/ast/cats/type.lua index 0034ba4..ce58cfd 100644 --- a/src/parser/ast/cats/type.lua +++ b/src/parser/ast/cats/type.lua @@ -6,6 +6,7 @@ local class = require 'class' ---| LuaParser.Node.CatArray ---| LuaParser.Node.CatCall ---| LuaParser.Node.CatUnion +---| LuaParser.Node.CatCross ---@class LuaParser.Node.CatParen: LuaParser.Node.ParenBase ---@field value? LuaParser.Node.CatType @@ -32,21 +33,8 @@ local Ast = class.declare('LuaParser.Ast') ---@param required? boolean ---@return LuaParser.Node.CatType? function Ast:parseCatType(required) - local curExp = self:parseCatTerm(required) - if not curExp then - return nil - end - self:skipSpace() - - while true do - local union = self:parseCatUnion(curExp) - if not union then - break - end - self:skipSpace() - curExp = union - end - return curExp + local catType = self:parseCatUnion(required) + return catType end ---@private diff --git a/src/parser/ast/cats/union.lua b/src/parser/ast/cats/union.lua index f902388..b4ec6ee 100644 --- a/src/parser/ast/cats/union.lua +++ b/src/parser/ast/cats/union.lua @@ -9,31 +9,40 @@ local Union = class.declare('LuaParser.Node.CatUnion', 'LuaParser.Node.Base') local Ast = class.declare 'LuaParser.Ast' ---@private ----@param exp LuaParser.Node.CatType ----@return LuaParser.Node.CatUnion? -function Ast:parseCatUnion(exp) +---@param required? boolean +---@return LuaParser.Node.CatType? +function Ast:parseCatUnion(required) + local first = self:parseCatCross(required) + if not first then + return nil + end + local pos = self.lexer:consume '|' if not pos then - return nil + return first end ---@type LuaParser.Node.CatUnion - local union - - if exp.type == 'CatUnion' then - ---@cast exp LuaParser.Node.CatUnion - union = exp - union.poses[#union.poses+1] = pos - else - union = self:createNode('LuaParser.Node.CatUnion', { - start = exp.start, - poses = { pos }, - exps = { exp }, - }) - end + local union = self:createNode('LuaParser.Node.CatUnion', { + start = first.start, + poses = { pos }, + exps = { first }, + }) - local nextNode = self:parseCatTerm(true) - union.exps[#union.exps+1] = nextNode + + while true do + self:skipSpace() + local nextNode = self:parseCatCross(true) + union.exps[#union.exps+1] = nextNode + + self:skipSpace() + local nextPos = self.lexer:consume '|' + if not nextPos then + break + end + + union.poses[#union.poses+1] = nextPos + end union.finish = self:getLastPos() diff --git a/test/ast/cats.lua b/test/ast/cats.lua index eece60f..02814df 100644 --- a/test/ast/cats.lua +++ b/test/ast/cats.lua @@ -201,3 +201,44 @@ TEST [[ } } } + +TEST [[ +---@type A & B | C & D & E | F +]] +{ + subtype = 'type', + value = { + type = 'CatUnion', + exps = { + [1] = { + type = 'CatCross', + exps = { + [1] = { + id = 'A', + }, + [2] = { + id = 'B', + } + } + }, + [2] = { + type = 'CatCross', + exps = { + [1] = { + id = 'C', + }, + [2] = { + id = 'D', + }, + [3] = { + id = 'E', + }, + } + }, + [3] = { + type = 'CatID', + id = 'F', + } + } + } +} From 88cb3d4251fc97d7af212ae11fed703f06ebeebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 18 Oct 2023 18:47:05 +0800 Subject: [PATCH 83/94] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 1 + src/parser/ast/cats/cat.lua | 1 + src/parser/ast/cats/function.lua | 116 +++++++++++++++++++++++++++++++ src/parser/ast/cats/type.lua | 38 +--------- src/parser/ast/exp.lua | 42 +---------- src/parser/ast/id.lua | 36 ++-------- src/parser/ast/list.lua | 52 ++++++++++++++ test/ast/cats.lua | 11 +++ 8 files changed, 189 insertions(+), 108 deletions(-) create mode 100644 src/parser/ast/cats/function.lua create mode 100644 src/parser/ast/list.lua diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 0db6cd5..4932564 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -4,6 +4,7 @@ local util = require 'utility' require 'parser.ast.base' require 'parser.ast.error' +require 'parser.ast.list' require 'parser.ast.nil' require 'parser.ast.boolean' require 'parser.ast.number' diff --git a/src/parser/ast/cats/cat.lua b/src/parser/ast/cats/cat.lua index 8d18bc3..ada6833 100644 --- a/src/parser/ast/cats/cat.lua +++ b/src/parser/ast/cats/cat.lua @@ -5,6 +5,7 @@ require 'parser.ast.cats.class' require 'parser.ast.cats.type' require 'parser.ast.cats.union' require 'parser.ast.cats.cross' +require 'parser.ast.cats.function' ---@class LuaParser.Node.Cat: LuaParser.Node.Base ---@field subtype string diff --git a/src/parser/ast/cats/function.lua b/src/parser/ast/cats/function.lua new file mode 100644 index 0000000..fddc211 --- /dev/null +++ b/src/parser/ast/cats/function.lua @@ -0,0 +1,116 @@ +local class = require 'class' + +---@class LuaParser.Node.CatFunction: LuaParser.Node.Base +---@field params LuaParser.Node.CatParam[] +---@field returns LuaParser.Node.CatType[] +---@field symbolPos1? integer # 左括号的位置 +---@field symbolPos2? integer # 右括号的位置 +---@field symbolPos3? integer # 冒号的位置 +---@field async? boolean # 是否异步 +local CatFunction = class.declare('LuaParser.Node.CatFunction', 'LuaParser.Node.Base') + +CatFunction.__getter.params = function () + return {} +end + +CatFunction.__getter.returns = function () + return {} +end + +---@class LuaParser.Node.CatParam: LuaParser.Node.Base +---@field parent LuaParser.Node.CatFunction +---@field name LuaParser.Node.CatParamName +---@field symbolPos? integer # 冒号的位置 +---@field value? LuaParser.Node.CatType +local CatParam = class.declare('LuaParser.Node.CatParam', 'LuaParser.Node.Base') + +---@class LuaParser.Node.CatParamName: LuaParser.Node.Base +---@field parent LuaParser.Node.CatParam +---@field index integer +---@field id string +local CatParamName = class.declare('LuaParser.Node.CatParamName', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare('LuaParser.Ast') + +function Ast:parseCatFunction() + local syncPos = self.lexer:consume 'async' + if syncPos then + self:skipSpace() + end + + local funPos = self.lexer:consume 'fun' + if not funPos then + return nil + end + + local funNode = self:createNode('LuaParser.Node.CatFunction', { + start = syncPos or funPos, + }) + + self:skipSpace() + funNode.symbolPos1 = self.lexer:consume '(' + if funNode.symbolPos1 then + + self:skipSpace() + self.params = self:parseCatFunParamList() + for _, param in ipairs(self.params) do + param.parent = funNode + end + + self:skipSpace() + funNode.symbolPos2 = self:assertSymbol ')' + end + + self:skipSpace() + self.symbolPos3 = self.lexer:consume ':' + if self.symbolPos3 then + + self:skipSpace() + self.returns = self:parseCatTypeList(true) + for _, ret in ipairs(self.returns) do + ret.parent = funNode + end + end + + funNode.finish = self:getLastPos() + + return funNode +end + +---@return LuaParser.Node.CatParam? +function Ast:parseCatFunParam() + local name = self:parseID('LuaParser.Node.CatParamName', false, true) + if not name then + return nil + end + + local param = self:createNode('LuaParser.Node.CatParam', { + start = name.start, + name = name, + }) + name.parent = param + + self:skipSpace() + param.symbolPos = self.lexer:consume ':' + if param.symbolPos then + + self:skipSpace() + param.value = self:parseCatType() + if param.value then + param.value.parent = param + end + end + + param.finish = self:getLastPos() + + return param +end + +---@return LuaParser.Node.CatParam[] +function Ast:parseCatFunParamList() + ---@type LuaParser.Node.CatParam[] + local list = {} + + return list +end diff --git a/src/parser/ast/cats/type.lua b/src/parser/ast/cats/type.lua index ce58cfd..5dd49ef 100644 --- a/src/parser/ast/cats/type.lua +++ b/src/parser/ast/cats/type.lua @@ -7,6 +7,7 @@ local class = require 'class' ---| LuaParser.Node.CatCall ---| LuaParser.Node.CatUnion ---| LuaParser.Node.CatCross +---| LuaParser.Node.CatFunction ---@class LuaParser.Node.CatParen: LuaParser.Node.ParenBase ---@field value? LuaParser.Node.CatType @@ -42,6 +43,7 @@ end ---@return LuaParser.Node.CatType? function Ast:parseCatTerm(required) local head = self:parseCatParen() + or self:parseCatFunction() or self:parseCatID() if not head then @@ -73,41 +75,7 @@ end ---@param atLeastOne? boolean ---@return LuaParser.Node.CatType[] function Ast:parseCatTypeList(atLeastOne) - ---@type LuaParser.Node.CatType[] - local list = {} - local first = self:parseCatType(atLeastOne) - list[#list+1] = first - local wantSep = first ~= nil - while true do - self:skipSpace() - local token, tp, pos = self.lexer:peek() - if not token then - break - end - ---@cast pos -? - if tp == 'Symbol' then - if token == ',' then - if not wantSep then - self:throw('UNEXPECT_SYMBOL', pos, pos + 1, { - symbol = ',', - }) - end - self.lexer:next() - self:skipSpace() - wantSep = false - else - break - end - else - break - end - local catType = self:parseCatType(true) - if catType then - list[#list+1] = catType - end - wantSep = true - end - return list + return self:parseList(atLeastOne, false, self.parseCatType) end ---@private diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index 64f442d..a6b6635 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -90,47 +90,7 @@ end ---@param greedy? boolean ---@return LuaParser.Node.Exp[] function Ast:parseExpList(atLeastOne, greedy) - ---@type LuaParser.Node.Exp[] - local list = {} - local first = self:parseExp(atLeastOne) - list[#list+1] = first - local wantSep = first ~= nil - while true do - self:skipSpace() - local token, tp, pos = self.lexer:peek() - if not token then - break - end - ---@cast pos -? - if tp == 'Symbol' then - if token == ',' then - if not wantSep then - self:throw('UNEXPECT_SYMBOL', pos, pos + 1, { - symbol = ',', - }) - end - self.lexer:next() - self:skipSpace() - wantSep = false - else - break - end - else - if not greedy then - break - end - if tp == 'Word' and self:isKeyWord(token) then - break - end - self:throwMissSymbol(self:getLastPos(), ',') - end - local exp = self:parseExp(true) - if exp then - list[#list+1] = exp - end - wantSep = true - end - return list + return self:parseList(atLeastOne, greedy, self.parseExp) end ---@alias LuaParser.Node.Term diff --git a/src/parser/ast/id.lua b/src/parser/ast/id.lua index eaecf28..bcad4ec 100644 --- a/src/parser/ast/id.lua +++ b/src/parser/ast/id.lua @@ -12,6 +12,7 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.LabelName ---| LuaParser.Node.AttrName ---| LuaParser.Node.CatAttr +---| LuaParser.Node.CatParamName ---@private ---@generic T: LuaParser.Node.ID @@ -57,38 +58,9 @@ end ---@param greedy? boolean ---@return T[] function Ast:parseIDList(nodeType, atLeastOne, greedy) - ---@type LuaParser.Node.ID[] - local list = {} - local first = self:parseID(nodeType, atLeastOne) - list[#list+1] = first - while true do - self:skipSpace() - local token, tp = self.lexer:peek() - if not token then - break - end - if tp == 'Symbol' then - if token == ',' then - self.lexer:next() - self:skipSpace() - else - break - end - else - if not greedy then - break - end - if tp == 'Word' and self:isKeyWord(token) then - break - end - self:throwMissSymbol(self:getLastPos(), ',') - end - local id = self:parseID(nodeType, true) - if id then - list[#list+1] = id - end - end - return list + return self:parseList(atLeastOne, greedy, function (_, required) + return self:parseID(nodeType, required) + end) end -- goto 单独处理 diff --git a/src/parser/ast/list.lua b/src/parser/ast/list.lua new file mode 100644 index 0000000..2caa683 --- /dev/null +++ b/src/parser/ast/list.lua @@ -0,0 +1,52 @@ +local class = require 'class' + +---@class LuaParser.Ast +local Ast = class.declare('LuaParser.Ast') + +---@private +---@param atLeastOne? boolean +---@param greedy? boolean +---@param parser function +---@return any[] +function Ast:parseList(atLeastOne, greedy, parser) + local list = {} + local first = parser(self, atLeastOne) + list[#list+1] = first + local wantSep = first ~= nil + while true do + self:skipSpace() + local token, tp, pos = self.lexer:peek() + if not token then + break + end + ---@cast pos -? + if tp == 'Symbol' then + if token == ',' then + if not wantSep then + self:throw('UNEXPECT_SYMBOL', pos, pos + 1, { + symbol = ',', + }) + end + self.lexer:next() + self:skipSpace() + wantSep = false + else + break + end + else + if not greedy then + break + end + if tp == 'Word' and self:isKeyWord(token) then + break + end + self:throwMissSymbol(self:getLastPos(), ',') + end + local unit = parser(self, true) + if unit then + list[#list+1] = unit + end + wantSep = true + end + return list +end diff --git a/test/ast/cats.lua b/test/ast/cats.lua index 02814df..80ea527 100644 --- a/test/ast/cats.lua +++ b/test/ast/cats.lua @@ -242,3 +242,14 @@ TEST [[ } } } + +TEST [[ +---@type fun() +]] +{ + value = { + type = 'CatFunction', + start = 9, + finish = 14, + } +} From 0119ff1f8e29e614aa3513a55e6ddc9a9b8a6941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 18 Oct 2023 19:30:53 +0800 Subject: [PATCH 84/94] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/cats/function.lua | 23 +++++++++-- src/parser/ast/list.lua | 6 +-- src/parser/ast/state/function.lua | 66 +++++++++---------------------- test/ast/cats.lua | 13 ++++++ test/syntax_check.lua | 7 +--- 5 files changed, 55 insertions(+), 60 deletions(-) diff --git a/src/parser/ast/cats/function.lua b/src/parser/ast/cats/function.lua index fddc211..adce962 100644 --- a/src/parser/ast/cats/function.lua +++ b/src/parser/ast/cats/function.lua @@ -3,6 +3,7 @@ local class = require 'class' ---@class LuaParser.Node.CatFunction: LuaParser.Node.Base ---@field params LuaParser.Node.CatParam[] ---@field returns LuaParser.Node.CatType[] +---@field funPos integer # `fun` 的位置 ---@field symbolPos1? integer # 左括号的位置 ---@field symbolPos2? integer # 右括号的位置 ---@field symbolPos3? integer # 冒号的位置 @@ -46,6 +47,8 @@ function Ast:parseCatFunction() local funNode = self:createNode('LuaParser.Node.CatFunction', { start = syncPos or funPos, + async = syncPos and true or false, + funPos = funPos, }) self:skipSpace() @@ -78,9 +81,22 @@ function Ast:parseCatFunction() return funNode end +---@param required? boolean ---@return LuaParser.Node.CatParam? -function Ast:parseCatFunParam() - local name = self:parseID('LuaParser.Node.CatParamName', false, true) +function Ast:parseCatParam(required) + local name + + local pos = self.lexer:consume '...' + if pos then + name = self:createNode('LuaParser.Node.CatParamName', { + start = pos, + finish = pos + #'...', + id = '...', + }) + else + name = self:parseID('LuaParser.Node.CatParamName', required, true) + end + if not name then return nil end @@ -109,8 +125,7 @@ end ---@return LuaParser.Node.CatParam[] function Ast:parseCatFunParamList() - ---@type LuaParser.Node.CatParam[] - local list = {} + local list = self:parseList(false, false, self.parseCatParam) return list end diff --git a/src/parser/ast/list.lua b/src/parser/ast/list.lua index 2caa683..2a2db3d 100644 --- a/src/parser/ast/list.lua +++ b/src/parser/ast/list.lua @@ -23,9 +23,7 @@ function Ast:parseList(atLeastOne, greedy, parser) if tp == 'Symbol' then if token == ',' then if not wantSep then - self:throw('UNEXPECT_SYMBOL', pos, pos + 1, { - symbol = ',', - }) + self:throw('UNEXPECT_SYMBOL', pos, pos + 1) end self.lexer:next() self:skipSpace() @@ -45,6 +43,8 @@ function Ast:parseList(atLeastOne, greedy, parser) local unit = parser(self, true) if unit then list[#list+1] = unit + else + break end wantSep = true end diff --git a/src/parser/ast/state/function.lua b/src/parser/ast/state/function.lua index 5489e94..b62ac95 100644 --- a/src/parser/ast/state/function.lua +++ b/src/parser/ast/state/function.lua @@ -152,48 +152,7 @@ end ---@private ---@return LuaParser.Node.Param[] function Ast:parseParamList() - ---@type LuaParser.Node.Param[] - local list = {} - - local wantExp = true - while true do - self:skipSpace() - - local param = self:parseParam() - if param then - list[#list+1] = param - if not wantExp then - self:throwMissSymbol(self:getLastPos(), ',') - end - wantExp = false - goto continue - end - - local token, tp, pos = self.lexer:peek() - if not token then - break - end - ---@cast pos -? - if tp == 'Symbol' then - if token == ',' then - if wantExp then - self:throw('MISS_NAME', self:getLastPos()) - end - self.lexer:next() - wantExp = true - else - break - end - else - self:throw('UNKNOWN_SYMBOL', pos, pos + #token) - self.lexer:next() - end - ::continue:: - end - - if wantExp and #list > 0 then - self:throw('MISS_NAME', self:getLastPos()) - end + local list = self:parseList(false, true, self.parseParam) for i = 1, #list do local param = list[i] @@ -210,19 +169,30 @@ function Ast:parseParamList() end ---@private +---@param required? boolean ---@return LuaParser.Node.Param? -function Ast:parseParam() - local param = self:parseID('LuaParser.Node.Param', false, true) - if param then - return param - end +function Ast:parseParam(required) local pos = self.lexer:consume '...' if pos then return self:createNode('LuaParser.Node.Param', { start = pos, - finish = pos, + finish = pos + #'...', id = '...', }) end + local param = self:parseID('LuaParser.Node.Param', required, true) + if param then + return param + end + + local token, tokenType, nextPos = self.lexer:peek() + if token then + ---@cast nextPos -? + if tokenType ~= 'Symbol' then + self.lexer:next() + self:throw('UNKNOWN_SYMBOL', nextPos, nextPos + #token) + end + end + return nil end diff --git a/test/ast/cats.lua b/test/ast/cats.lua index 80ea527..2476fbd 100644 --- a/test/ast/cats.lua +++ b/test/ast/cats.lua @@ -253,3 +253,16 @@ TEST [[ finish = 14, } } + +TEST [[ +---@type async fun() +]] +{ + value = { + type = 'CatFunction', + start = 9, + finish = 20, + async = true, + funPos = 15, + } +} diff --git a/test/syntax_check.lua b/test/syntax_check.lua index 3d4ca8f..f4f37bf 100644 --- a/test/syntax_check.lua +++ b/test/syntax_check.lua @@ -305,9 +305,6 @@ f(1) ]] { type = 'UNEXPECT_SYMBOL', - extra = { - symbol = ',', - } } TEST[[ @@ -559,10 +556,10 @@ function f(a,) end } TEST[[ -function f(,a) end +function f(a) end ]] { - type = 'MISS_NAME', + type = 'UNEXPECT_SYMBOL', } TEST[[ From e9525c0a54918b2cbff061220551ad7dae25c050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 19 Oct 2023 14:21:01 +0800 Subject: [PATCH 85/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=20fun?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/cats/function.lua | 98 ++++++++++++++++++++-- src/parser/ast/id.lua | 1 + test/ast/cats.lua | 140 +++++++++++++++++++++++++++++++ 3 files changed, 234 insertions(+), 5 deletions(-) diff --git a/src/parser/ast/cats/function.lua b/src/parser/ast/cats/function.lua index adce962..0a9e13d 100644 --- a/src/parser/ast/cats/function.lua +++ b/src/parser/ast/cats/function.lua @@ -7,6 +7,8 @@ local class = require 'class' ---@field symbolPos1? integer # 左括号的位置 ---@field symbolPos2? integer # 右括号的位置 ---@field symbolPos3? integer # 冒号的位置 +---@field symbolPos4? integer # 返回值左括号的位置 +---@field symbolPos5? integer # 返回值右括号的位置 ---@field async? boolean # 是否异步 local CatFunction = class.declare('LuaParser.Node.CatFunction', 'LuaParser.Node.Base') @@ -31,9 +33,23 @@ local CatParam = class.declare('LuaParser.Node.CatParam', 'LuaParser.Node.Base') ---@field id string local CatParamName = class.declare('LuaParser.Node.CatParamName', 'LuaParser.Node.Base') +---@class LuaParser.Node.CatReturn: LuaParser.Node.Base +---@field parent LuaParser.Node.CatFunction +---@field name LuaParser.Node.CatReturnName +---@field symbolPos? integer # 冒号的位置 +---@field value? LuaParser.Node.CatType +local CatReturn = class.declare('LuaParser.Node.CatReturn', 'LuaParser.Node.Base') + +---@class LuaParser.Node.CatReturnName: LuaParser.Node.Base +---@field parent LuaParser.Node.CatReturn +---@field index integer +---@field id string +local CatReturnName = class.declare('LuaParser.Node.CatReturnName', 'LuaParser.Node.Base') + ---@class LuaParser.Ast local Ast = class.declare('LuaParser.Ast') +---@private function Ast:parseCatFunction() local syncPos = self.lexer:consume 'async' if syncPos then @@ -56,8 +72,8 @@ function Ast:parseCatFunction() if funNode.symbolPos1 then self:skipSpace() - self.params = self:parseCatFunParamList() - for _, param in ipairs(self.params) do + funNode.params = self:parseCatParamList() + for _, param in ipairs(funNode.params) do param.parent = funNode end @@ -70,10 +86,16 @@ function Ast:parseCatFunction() if self.symbolPos3 then self:skipSpace() - self.returns = self:parseCatTypeList(true) - for _, ret in ipairs(self.returns) do + funNode.symbolPos4 = self.lexer:consume '(' + self:skipSpace() + funNode.returns = self:parseCatReturnList() + for _, ret in ipairs(funNode.returns) do ret.parent = funNode end + if funNode.symbolPos4 then + self:skipSpace() + funNode.symbolPos5 = self:assertSymbol ')' + end end funNode.finish = self:getLastPos() @@ -81,6 +103,7 @@ function Ast:parseCatFunction() return funNode end +---@private ---@param required? boolean ---@return LuaParser.Node.CatParam? function Ast:parseCatParam(required) @@ -123,9 +146,74 @@ function Ast:parseCatParam(required) return param end +---@private ---@return LuaParser.Node.CatParam[] -function Ast:parseCatFunParamList() +function Ast:parseCatParamList() local list = self:parseList(false, false, self.parseCatParam) return list end + +---@private +---@param required? boolean +---@return LuaParser.Node.CatReturn? +function Ast:parseCatReturn(required) + local name + + local pos = self.lexer:consume '...' + if pos then + name = self:createNode('LuaParser.Node.CatReturnName', { + start = pos, + finish = pos + #'...', + id = '...', + }) + else + local _, curType = self.lexer:peek() + if curType == 'Word' and self.lexer:peek(1) == ':' then + name = self:parseID('LuaParser.Node.CatReturnName', required, true) + end + end + + + local symbolPos + if name then + self:skipSpace() + symbolPos = self.lexer:consume ':' + self:skipSpace() + end + + local value + if not name or symbolPos then + value = self:parseCatType() + end + + if not name and not value then + return nil + end + + local ret = self:createNode('LuaParser.Node.CatReturn', { + name = name, + value = value, + start = (name or value).start, + symbolPos = symbolPos, + finish = self:getLastPos(), + }) + + if name then + name.parent = ret + end + + if value then + value.parent = ret + end + + return ret +end + +---@private +---@return LuaParser.Node.CatReturn[] +function Ast:parseCatReturnList() + local list = self:parseList(false, false, self.parseCatReturn) + + return list +end diff --git a/src/parser/ast/id.lua b/src/parser/ast/id.lua index bcad4ec..b3127fe 100644 --- a/src/parser/ast/id.lua +++ b/src/parser/ast/id.lua @@ -13,6 +13,7 @@ local Ast = class.declare 'LuaParser.Ast' ---| LuaParser.Node.AttrName ---| LuaParser.Node.CatAttr ---| LuaParser.Node.CatParamName +---| LuaParser.Node.CatReturnName ---@private ---@generic T: LuaParser.Node.ID diff --git a/test/ast/cats.lua b/test/ast/cats.lua index 2476fbd..090cdf3 100644 --- a/test/ast/cats.lua +++ b/test/ast/cats.lua @@ -266,3 +266,143 @@ TEST [[ funPos = 15, } } + +TEST [[ +---@type fun(x, y, ...) +]] +{ + value = { + type = 'CatFunction', + finish = 23, + symbolPos1 = 12, + symbolPos2 = 22, + params = { + [1] = { + name = { + id = 'x' + } + }, + [2] = { + name = { + id = 'y' + } + }, + [3] = { + name = { + id = '...' + } + } + }, + } +} + +TEST [[ +---@type fun(x: number, y: number) +]] +{ + value = { + params = { + [1] = { + name = { id = 'x' }, + value = { id = 'number' }, + }, + [2] = { + name = { id = 'y' }, + value = { id = 'number' }, + } + } + } +} + +TEST [[ +---@type fun(): number, boolean, ... +]] +{ + value = { + returns = { + [1] = { + value = { + id = 'number' + } + }, + [2] = { + value = { + id = 'boolean', + } + }, + [3] = { + name = { + id = '...' + } + } + } + } +} + +TEST [[ +---@type fun(): r1: number, r2: boolean, ...: string +]] +{ + value = { + returns = { + [1] = { + name = { + id = 'r1' + }, + value = { + id = 'number' + } + }, + [2] = { + name = { + id = 'r2' + }, + value = { + id = 'boolean', + } + }, + [3] = { + name = { + id = '...' + }, + value = { + id ='string' + } + } + } + } +} + +TEST [[ +---@type fun(): (r1: number, r2: boolean, ...: string) +]] +{ + value = { + returns = { + [1] = { + name = { + id = 'r1' + }, + value = { + id = 'number' + } + }, + [2] = { + name = { + id = 'r2' + }, + value = { + id = 'boolean', + } + }, + [3] = { + name = { + id = '...' + }, + value = { + id ='string' + } + } + } + } +} From 647527553067259c4bd170df116a40e645fe55bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 15 Nov 2023 15:27:08 +0800 Subject: [PATCH 86/94] =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E5=A4=9A=E8=A1=8C=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backlog.md | 34 ++++++++++++++++++++++++++++++++++ src/parser/ast/cats/cat.lua | 26 +++++++++++++++++++++++++- test/ast/cats.lua | 23 +++++++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 backlog.md diff --git a/backlog.md b/backlog.md new file mode 100644 index 0000000..ba9abfe --- /dev/null +++ b/backlog.md @@ -0,0 +1,34 @@ +### 注解属性 + +```lua +---@type(attr) number + +--不能有空格,避免以下歧义 + +---@type (number) +``` + +### 多行注解的规则 + +* `---` 表示为注解,注释请用 `--` 。 +* 作为部分兼容措施,最先出现的 `---` 被识别为 `--`。 + +```lua +---注释 +---@class A +---不能作为字段注释 +--这才是字段注释 +---@field getX fun() +---: number +---@field x number +``` + +被解释为: + +```lua +--注释 +---@class A --不能作为字段注释 +--这才是字段注释 +---@field getX fun(): number +---@field x number +``` diff --git a/src/parser/ast/cats/cat.lua b/src/parser/ast/cats/cat.lua index ada6833..6fc69c5 100644 --- a/src/parser/ast/cats/cat.lua +++ b/src/parser/ast/cats/cat.lua @@ -48,7 +48,8 @@ end) ---@private ---@return boolean function Ast:skipCat() - if self:parseCat() + if self:skipMultiLineCatHead() + or self:parseCat() or self:parseCatBlock() then return true else @@ -56,6 +57,29 @@ function Ast:skipCat() end end +---@private +---@return boolean +function Ast:skipMultiLineCatHead() + if self.status ~= 'ShortCats' then + return false + end + -- 下1个token是 `NL`,下2个token是 `---` 但不是 `---@` + local _, nlType = self.lexer:peek(1) + if nlType ~= 'NL' then + return false + end + local token, _, pos = self.lexer:peek(2) + if token ~= '--' then + return false + end + if self.code:sub(pos + 1, pos + 3) ~= '---' + or self.code:sub(pos + 4, pos + 4) == '@' then + return false + end + self.lexer:fastForward(pos + 3) + return true +end + -- 会将解析结果存放到 `Ast.cats` 中 ---@private ---@return LuaParser.Node.Cat? diff --git a/test/ast/cats.lua b/test/ast/cats.lua index 090cdf3..e6b1661 100644 --- a/test/ast/cats.lua +++ b/test/ast/cats.lua @@ -406,3 +406,26 @@ TEST [[ } } } + +TEST [[ +---@type fun() +---: number +]] +{ + value = { + returns = { + [1] = { + value = { + id = 'number' + }, + } + } + }, +} + +TEST [[ +---@unknown +]] +{ + subtype = 'unknown', +} From a54abf0acaec24b31aff1ca4ef3a8c0c39b0d591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 15 Nov 2023 17:45:13 +0800 Subject: [PATCH 87/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90CatTable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/cats/cat.lua | 1 + src/parser/ast/cats/function.lua | 10 +- src/parser/ast/cats/table.lua | 169 +++++++++++++++++++++++++++++++ src/parser/ast/cats/type.lua | 2 + src/parser/ast/field.lua | 2 +- test/ast/cats.lua | 44 ++++++++ 6 files changed, 219 insertions(+), 9 deletions(-) create mode 100644 src/parser/ast/cats/table.lua diff --git a/src/parser/ast/cats/cat.lua b/src/parser/ast/cats/cat.lua index 6fc69c5..6dfe636 100644 --- a/src/parser/ast/cats/cat.lua +++ b/src/parser/ast/cats/cat.lua @@ -6,6 +6,7 @@ require 'parser.ast.cats.type' require 'parser.ast.cats.union' require 'parser.ast.cats.cross' require 'parser.ast.cats.function' +require 'parser.ast.cats.table' ---@class LuaParser.Node.Cat: LuaParser.Node.Base ---@field subtype string diff --git a/src/parser/ast/cats/function.lua b/src/parser/ast/cats/function.lua index 0a9e13d..9af61dd 100644 --- a/src/parser/ast/cats/function.lua +++ b/src/parser/ast/cats/function.lua @@ -12,14 +12,6 @@ local class = require 'class' ---@field async? boolean # 是否异步 local CatFunction = class.declare('LuaParser.Node.CatFunction', 'LuaParser.Node.Base') -CatFunction.__getter.params = function () - return {} -end - -CatFunction.__getter.returns = function () - return {} -end - ---@class LuaParser.Node.CatParam: LuaParser.Node.Base ---@field parent LuaParser.Node.CatFunction ---@field name LuaParser.Node.CatParamName @@ -65,6 +57,8 @@ function Ast:parseCatFunction() start = syncPos or funPos, async = syncPos and true or false, funPos = funPos, + params = {}, + returns = {}, }) self:skipSpace() diff --git a/src/parser/ast/cats/table.lua b/src/parser/ast/cats/table.lua new file mode 100644 index 0000000..667ef71 --- /dev/null +++ b/src/parser/ast/cats/table.lua @@ -0,0 +1,169 @@ +local class = require 'class' + +---@class LuaParser.Node.CatTable: LuaParser.Node.Base +---@field fields LuaParser.Node.CatTableField[] +local CatTable = class.declare('LuaParser.Node.CatTable', 'LuaParser.Node.Base') + +---@class LuaParser.Node.CatTableField: LuaParser.Node.Base +---@field subtype 'field' | 'index' +---@field key? LuaParser.Node.CatTableFieldID | LuaParser.Node.CatType +---@field value? LuaParser.Node.CatType +---@field symbolPos? integer +---@field symbolPos2? integer +---@field parent LuaParser.Node.CatTable +local CatTableField = class.declare('LuaParser.Node.CatTableField', 'LuaParser.Node.Base') + +---@class LuaParser.Node.CatTableFieldID: LuaParser.Node.Base +---@field id string +---@field parent LuaParser.Node.CatTableField +local CatTableFieldID = class.declare('LuaParser.Node.CatTableFieldID', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.declare('LuaParser.Ast') + +---@private +---@return LuaParser.Node.CatTable? +function Ast:parseCatTable() + local pos = self.lexer:consume '{' + if not pos then + return + end + + local catTable = self:createNode('LuaParser.Node.CatTable', { + start = pos, + }) + + local fields = self:parseCatTableFields() + catTable.fields = fields + for _, field in ipairs(fields) do + field.parent = catTable + end + + self:assertSymbol '}' + + catTable.finish = self:getLastPos() + + return catTable +end + +---@private +---@return LuaParser.Node.CatTableField[] +function Ast:parseCatTableFields() + local fields = {} + local wantSep = false + while true do + local token, _, pos = self.lexer:peek() + if not token or token == '}' then + break + end + if token == ',' then + if not wantSep then + self:throwMissExp(self:getLastPos()) + end + wantSep = false + self.lexer:next() + else + if wantSep then + local lastField = fields[#fields] + self:throw('MISS_SEP_IN_TABLE', lastField.finish, pos) + break + end + wantSep = true + local field = self:parseCatTableField() + if field then + fields[#fields+1] = field + else + self:throwMissExp(self:getLastPos()) + break + end + end + self:skipSpace() + end + + return fields +end + +---@private +---@return LuaParser.Node.CatTableField? +function Ast:parseCatTableField() + return self:parseCatTableFieldAsField() + or self:parseCatTableFieldAsIndex() +end + +---@private +---@return LuaParser.Node.CatTableField? +function Ast:parseCatTableFieldAsField() + local key + + local pos = self.lexer:consume '...' + if pos then + key = self:createNode('LuaParser.Node.CatTableFieldID', { + start = pos, + finish = pos + #'...', + id = '...', + }) + else + key = self:parseID('LuaParser.Node.CatTableFieldID', false, true) + end + + if not key then + return nil + end + + local value + self:skipSpace() + if self.lexer:consume ':' then + self:skipSpace() + value = self:parseCatType(true) + end + + local tfield = self:createNode('LuaParser.Node.CatTableField', { + subtype = 'field', + key = key, + value = value, + start = key.start, + finish = self:getLastPos(), + }) + key.parent = tfield + if value then + value.parent = tfield + end + return tfield +end + +---@private +---@return LuaParser.Node.CatTableField? +function Ast:parseCatTableFieldAsIndex() + local token, _, pos = self.lexer:peek() + if token ~= '[' then + return nil + end + self.lexer:next() + self:skipSpace() + local key = self:parseCatType(true) + self:skipSpace() + local pos2 = self:assertSymbol ']' + self:skipSpace() + + local value + if self.lexer:consume ':' then + self:skipSpace() + value = self:parseCatType(true) + end + local tfield = self:createNode('LuaParser.Node.CatTableField', { + subtype = 'index', + key = key, + value = value, + start = pos, + finish = self:getLastPos(), + symbolPos = pos, + symbolPos2 = pos2, + }) + if key then + key.parent = tfield + end + if value then + value.parent = tfield + end + return tfield +end diff --git a/src/parser/ast/cats/type.lua b/src/parser/ast/cats/type.lua index 5dd49ef..889bdb3 100644 --- a/src/parser/ast/cats/type.lua +++ b/src/parser/ast/cats/type.lua @@ -8,6 +8,7 @@ local class = require 'class' ---| LuaParser.Node.CatUnion ---| LuaParser.Node.CatCross ---| LuaParser.Node.CatFunction +---| LuaParser.Node.CatTable ---@class LuaParser.Node.CatParen: LuaParser.Node.ParenBase ---@field value? LuaParser.Node.CatType @@ -44,6 +45,7 @@ end function Ast:parseCatTerm(required) local head = self:parseCatParen() or self:parseCatFunction() + or self:parseCatTable() or self:parseCatID() if not head then diff --git a/src/parser/ast/field.lua b/src/parser/ast/field.lua index cf525ee..c1d5614 100644 --- a/src/parser/ast/field.lua +++ b/src/parser/ast/field.lua @@ -17,7 +17,7 @@ local FieldID = class.declare('LuaParser.Node.FieldID', 'LuaParser.Node.Base') ---@class LuaParser.Node.TableField: LuaParser.Node.Base ---@field subtype 'field' | 'index' | 'exp' ----@field key? LuaParser.Node.Exp +---@field key? LuaParser.Node.TableFieldID | LuaParser.Node.Exp ---@field value? LuaParser.Node.Exp ---@field symbolPos? integer ---@field symbolPos2? integer diff --git a/test/ast/cats.lua b/test/ast/cats.lua index e6b1661..3f4f45c 100644 --- a/test/ast/cats.lua +++ b/test/ast/cats.lua @@ -423,6 +423,50 @@ TEST [[ }, } +TEST [[ +---@type { x: number, y, [integer]: boolean, ...: number } +]] +{ + value = { + fields = { + [1] = { + subtype = 'field', + key = { + id = 'x', + }, + value = { + id = 'number', + } + }, + [2] = { + subtype = 'field', + key = { + id = 'y', + }, + value = NIL, + }, + [3] = { + subtype = 'index', + key = { + id = 'integer', + }, + value = { + id = 'boolean', + } + }, + [4] = { + subtype = 'field', + key = { + id = '...', + }, + value = { + id = 'number', + } + } + } + }, +} + TEST [[ ---@unknown ]] From 93eb1ae641a213d501b2854ad12752bd0072b707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 15 Nov 2023 17:54:49 +0800 Subject: [PATCH 88/94] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 2 +- src/parser/ast/binary.lua | 2 +- src/parser/ast/block.lua | 2 +- src/parser/ast/boolean.lua | 2 +- src/parser/ast/call.lua | 2 +- src/parser/ast/cats/boolean.lua | 8 ++++++++ src/parser/ast/cats/cat.lua | 2 +- src/parser/ast/cats/class.lua | 2 +- src/parser/ast/cats/cross.lua | 2 +- src/parser/ast/cats/function.lua | 2 +- src/parser/ast/cats/id.lua | 2 +- src/parser/ast/cats/table.lua | 2 +- src/parser/ast/cats/type.lua | 5 ++++- src/parser/ast/cats/union.lua | 2 +- src/parser/ast/comment.lua | 2 +- src/parser/ast/exp.lua | 2 +- src/parser/ast/field.lua | 2 +- src/parser/ast/id.lua | 2 +- src/parser/ast/list.lua | 2 +- src/parser/ast/main.lua | 2 +- src/parser/ast/nil.lua | 2 +- src/parser/ast/number.lua | 2 +- src/parser/ast/state/break.lua | 2 +- src/parser/ast/state/do.lua | 2 +- src/parser/ast/state/for.lua | 2 +- src/parser/ast/state/function.lua | 2 +- src/parser/ast/state/if.lua | 2 +- src/parser/ast/state/label.lua | 2 +- src/parser/ast/state/local.lua | 2 +- src/parser/ast/state/repeat.lua | 2 +- src/parser/ast/state/return.lua | 2 +- src/parser/ast/state/state.lua | 2 +- src/parser/ast/state/while.lua | 2 +- src/parser/ast/string.lua | 2 +- src/parser/ast/table.lua | 2 +- src/parser/ast/unary.lua | 2 +- src/parser/ast/var.lua | 2 +- test/ast/cats.lua | 4 ++++ 38 files changed, 51 insertions(+), 36 deletions(-) create mode 100644 src/parser/ast/cats/boolean.lua diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index 4932564..f81a2fa 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -27,7 +27,7 @@ require 'parser.ast.cats.cat' ---@field envMode 'fenv' | '_ENV' ---@field main LuaParser.Node.Main ---@overload fun(code: string, version: LuaParser.LuaVersion, options: LuaParser.CompileOptions): LuaParser.Ast -local M = class.declare 'LuaParser.Ast' +local M = class.get 'LuaParser.Ast' ---@alias LuaParser.Status ---| 'Lua' # Lua代码 diff --git a/src/parser/ast/binary.lua b/src/parser/ast/binary.lua index 13c55bd..95358e6 100644 --- a/src/parser/ast/binary.lua +++ b/src/parser/ast/binary.lua @@ -51,7 +51,7 @@ local RevertConcat = { local Binary = class.declare('LuaParser.Node.Binary', 'LuaParser.Node.Base') ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private ---@param curExp LuaParser.Node.Exp diff --git a/src/parser/ast/block.lua b/src/parser/ast/block.lua index 7187a38..75ae122 100644 --- a/src/parser/ast/block.lua +++ b/src/parser/ast/block.lua @@ -71,7 +71,7 @@ Block.__getter.referBlock = function (self) end ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' local FinishMap = { ['end'] = true, diff --git a/src/parser/ast/boolean.lua b/src/parser/ast/boolean.lua index 628dda3..7f59bf6 100644 --- a/src/parser/ast/boolean.lua +++ b/src/parser/ast/boolean.lua @@ -7,7 +7,7 @@ local Boolean = class.declare('LuaParser.Node.Boolean', 'LuaParser.Node.Literal' Boolean.isLiteral = true ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' -- 解析布尔值 ---@private diff --git a/src/parser/ast/call.lua b/src/parser/ast/call.lua index 37315fd..bbc227e 100644 --- a/src/parser/ast/call.lua +++ b/src/parser/ast/call.lua @@ -7,7 +7,7 @@ local class = require 'class' local Call = class.declare('LuaParser.Node.Call', 'LuaParser.Node.Base') ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private ---@param last LuaParser.Node.Term diff --git a/src/parser/ast/cats/boolean.lua b/src/parser/ast/cats/boolean.lua new file mode 100644 index 0000000..79df73c --- /dev/null +++ b/src/parser/ast/cats/boolean.lua @@ -0,0 +1,8 @@ +local class = require 'class' + +---@class LuaParser.Node.CatBoolean: LuaParser.Node.Base +---@field value boolean +local CatBoolean = class.declare('LuaParser.Node.CatBoolean', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.get 'LuaParser.Ast' diff --git a/src/parser/ast/cats/cat.lua b/src/parser/ast/cats/cat.lua index 6dfe636..6431fab 100644 --- a/src/parser/ast/cats/cat.lua +++ b/src/parser/ast/cats/cat.lua @@ -28,7 +28,7 @@ local Cat = class.declare('LuaParser.Node.Cat', 'LuaParser.Node.Base') local CatAttr = class.declare('LuaParser.Node.CatAttr', 'LuaParser.Node.Base') ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private Ast.catParserMap = {} diff --git a/src/parser/ast/cats/class.lua b/src/parser/ast/cats/class.lua index b7bcaf2..6bce284 100644 --- a/src/parser/ast/cats/class.lua +++ b/src/parser/ast/cats/class.lua @@ -7,7 +7,7 @@ local class = require 'class' local CatClass = class.declare('LuaParser.Node.CatClass', 'LuaParser.Node.Base') ---@class LuaParser.Ast -local Ast = class.declare('LuaParser.Ast') +local Ast = class.get 'LuaParser.Ast' ---@return LuaParser.Node.CatClass? function Ast:parseCatClass() diff --git a/src/parser/ast/cats/cross.lua b/src/parser/ast/cats/cross.lua index 6dfc2e4..50d907b 100644 --- a/src/parser/ast/cats/cross.lua +++ b/src/parser/ast/cats/cross.lua @@ -6,7 +6,7 @@ local class = require'class' local Cross = class.declare('LuaParser.Node.CatCross', 'LuaParser.Node.Base') ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private ---@param required? boolean diff --git a/src/parser/ast/cats/function.lua b/src/parser/ast/cats/function.lua index 9af61dd..b4f7526 100644 --- a/src/parser/ast/cats/function.lua +++ b/src/parser/ast/cats/function.lua @@ -39,7 +39,7 @@ local CatReturn = class.declare('LuaParser.Node.CatReturn', 'LuaParser.Node.Base local CatReturnName = class.declare('LuaParser.Node.CatReturnName', 'LuaParser.Node.Base') ---@class LuaParser.Ast -local Ast = class.declare('LuaParser.Ast') +local Ast = class.get 'LuaParser.Ast' ---@private function Ast:parseCatFunction() diff --git a/src/parser/ast/cats/id.lua b/src/parser/ast/cats/id.lua index cf37f66..b7862c1 100644 --- a/src/parser/ast/cats/id.lua +++ b/src/parser/ast/cats/id.lua @@ -5,7 +5,7 @@ local class = require 'class' local CatID = class.declare('LuaParser.Node.CatID', 'LuaParser.Node.Base') ---@class LuaParser.Ast -local Ast = class.declare('LuaParser.Ast') +local Ast = class.get 'LuaParser.Ast' ---@return LuaParser.Node.CatID? function Ast:parseCatID() diff --git a/src/parser/ast/cats/table.lua b/src/parser/ast/cats/table.lua index 667ef71..30fd0de 100644 --- a/src/parser/ast/cats/table.lua +++ b/src/parser/ast/cats/table.lua @@ -19,7 +19,7 @@ local CatTableField = class.declare('LuaParser.Node.CatTableField', 'LuaParser.N local CatTableFieldID = class.declare('LuaParser.Node.CatTableFieldID', 'LuaParser.Node.Base') ---@class LuaParser.Ast -local Ast = class.declare('LuaParser.Ast') +local Ast = class.get 'LuaParser.Ast' ---@private ---@return LuaParser.Node.CatTable? diff --git a/src/parser/ast/cats/type.lua b/src/parser/ast/cats/type.lua index 889bdb3..b56f1e7 100644 --- a/src/parser/ast/cats/type.lua +++ b/src/parser/ast/cats/type.lua @@ -9,6 +9,9 @@ local class = require 'class' ---| LuaParser.Node.CatCross ---| LuaParser.Node.CatFunction ---| LuaParser.Node.CatTable +---| LuaParser.Node.CatBoolean +---| LuaParser.Node.CatInteger +---| LuaParser.Node.CatString ---@class LuaParser.Node.CatParen: LuaParser.Node.ParenBase ---@field value? LuaParser.Node.CatType @@ -29,7 +32,7 @@ local CatArray = class.declare('LuaParser.Node.CatArray', 'LuaParser.Node.Base') local CatCall = class.declare('LuaParser.Node.CatCall', 'LuaParser.Node.Base') ---@class LuaParser.Ast -local Ast = class.declare('LuaParser.Ast') +local Ast = class.get 'LuaParser.Ast' ---@private ---@param required? boolean diff --git a/src/parser/ast/cats/union.lua b/src/parser/ast/cats/union.lua index b4ec6ee..b6ec286 100644 --- a/src/parser/ast/cats/union.lua +++ b/src/parser/ast/cats/union.lua @@ -6,7 +6,7 @@ local class = require'class' local Union = class.declare('LuaParser.Node.CatUnion', 'LuaParser.Node.Base') ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private ---@param required? boolean diff --git a/src/parser/ast/comment.lua b/src/parser/ast/comment.lua index 16572ba..ef1cb66 100644 --- a/src/parser/ast/comment.lua +++ b/src/parser/ast/comment.lua @@ -23,7 +23,7 @@ Comment.__getter.value = function (self) end ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private ---@param inExp? boolean diff --git a/src/parser/ast/exp.lua b/src/parser/ast/exp.lua index a6b6635..4560630 100644 --- a/src/parser/ast/exp.lua +++ b/src/parser/ast/exp.lua @@ -41,7 +41,7 @@ end local Paren = class.declare('LuaParser.Node.Paren', 'LuaParser.Node.ParenBase') ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@alias LuaParser.Node.Exp ---| LuaParser.Node.Term diff --git a/src/parser/ast/field.lua b/src/parser/ast/field.lua index c1d5614..45875d8 100644 --- a/src/parser/ast/field.lua +++ b/src/parser/ast/field.lua @@ -30,7 +30,7 @@ local TableField = class.declare('LuaParser.Node.TableField', 'LuaParser.Node.Ba local TableFieldID = class.declare('LuaParser.Node.TableFieldID', 'LuaParser.Node.Base') ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private ---@param last LuaParser.Node.Term diff --git a/src/parser/ast/id.lua b/src/parser/ast/id.lua index b3127fe..995a24d 100644 --- a/src/parser/ast/id.lua +++ b/src/parser/ast/id.lua @@ -1,7 +1,7 @@ local class = require 'class' ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@alias LuaParser.Node.ID ---| LuaParser.Node.Local diff --git a/src/parser/ast/list.lua b/src/parser/ast/list.lua index 2a2db3d..c0e3f49 100644 --- a/src/parser/ast/list.lua +++ b/src/parser/ast/list.lua @@ -1,7 +1,7 @@ local class = require 'class' ---@class LuaParser.Ast -local Ast = class.declare('LuaParser.Ast') +local Ast = class.get 'LuaParser.Ast' ---@private ---@param atLeastOne? boolean diff --git a/src/parser/ast/main.lua b/src/parser/ast/main.lua index 4604d45..bd0d1ae 100644 --- a/src/parser/ast/main.lua +++ b/src/parser/ast/main.lua @@ -15,7 +15,7 @@ function Main.__getter.parentBlock() end ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private function Ast:skipShebang() diff --git a/src/parser/ast/nil.lua b/src/parser/ast/nil.lua index 34fb709..e1b0d36 100644 --- a/src/parser/ast/nil.lua +++ b/src/parser/ast/nil.lua @@ -9,7 +9,7 @@ Nil.isTruly = false ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' -- 解析 nil ---@private diff --git a/src/parser/ast/number.lua b/src/parser/ast/number.lua index a3179c9..5f74efd 100644 --- a/src/parser/ast/number.lua +++ b/src/parser/ast/number.lua @@ -109,7 +109,7 @@ Integer.__getter.asInteger = function (self) end ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' -- 解析数字(可以带负号) ---@private diff --git a/src/parser/ast/state/break.lua b/src/parser/ast/state/break.lua index 3c4abb3..7eb8ffb 100644 --- a/src/parser/ast/state/break.lua +++ b/src/parser/ast/state/break.lua @@ -8,7 +8,7 @@ local Break = class.declare('LuaParser.Node.Break', 'LuaParser.Node.Base') local Continue = class.declare('LuaParser.Node.Continue', 'LuaParser.Node.Base') ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@return LuaParser.Node.Block? function Ast:findBreakBlock() diff --git a/src/parser/ast/state/do.lua b/src/parser/ast/state/do.lua index df4509d..faadebe 100644 --- a/src/parser/ast/state/do.lua +++ b/src/parser/ast/state/do.lua @@ -5,7 +5,7 @@ local class = require 'class' local Do = class.declare('LuaParser.Node.Do', 'LuaParser.Node.Block') ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private ---@return LuaParser.Node.Do? diff --git a/src/parser/ast/state/for.lua b/src/parser/ast/state/for.lua index 0b9ac53..c09f14a 100644 --- a/src/parser/ast/state/for.lua +++ b/src/parser/ast/state/for.lua @@ -14,7 +14,7 @@ For.vars = {} For.exps = {} ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private ---@return LuaParser.Node.For? diff --git a/src/parser/ast/state/function.lua b/src/parser/ast/state/function.lua index b62ac95..6652b11 100644 --- a/src/parser/ast/state/function.lua +++ b/src/parser/ast/state/function.lua @@ -28,7 +28,7 @@ end ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private ---@param isLocal? boolean diff --git a/src/parser/ast/state/if.lua b/src/parser/ast/state/if.lua index 05f6dd8..00e9d24 100644 --- a/src/parser/ast/state/if.lua +++ b/src/parser/ast/state/if.lua @@ -11,7 +11,7 @@ local If = class.declare('LuaParser.Node.If', 'LuaParser.Node.Base') local IfChild = class.declare('LuaParser.Node.IfChild', 'LuaParser.Node.Block') ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private ---@return LuaParser.Node.If? diff --git a/src/parser/ast/state/label.lua b/src/parser/ast/state/label.lua index 2a3957e..6eaea91 100644 --- a/src/parser/ast/state/label.lua +++ b/src/parser/ast/state/label.lua @@ -21,7 +21,7 @@ local LabelName = class.declare('LuaParser.Node.LabelName', 'LuaParser.Node.Base local Goto = class.declare('LuaParser.Node.Goto', 'LuaParser.Node.Base') ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private ---@return LuaParser.Node.Label? diff --git a/src/parser/ast/state/local.lua b/src/parser/ast/state/local.lua index 362a719..62903e9 100644 --- a/src/parser/ast/state/local.lua +++ b/src/parser/ast/state/local.lua @@ -73,7 +73,7 @@ local Attr = class.declare('LuaParser.Node.Attr', 'LuaParser.Node.Base') local AttrName = class.declare('LuaParser.Node.AttrName', 'LuaParser.Node.Base') ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private ---@return (LuaParser.Node.LocalDef | LuaParser.Node.Function)? diff --git a/src/parser/ast/state/repeat.lua b/src/parser/ast/state/repeat.lua index 700b9dd..77bf268 100644 --- a/src/parser/ast/state/repeat.lua +++ b/src/parser/ast/state/repeat.lua @@ -6,7 +6,7 @@ local class = require 'class' local Repeat = class.declare('LuaParser.Node.Repeat', 'LuaParser.Node.Block') ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private ---@return LuaParser.Node.Repeat? diff --git a/src/parser/ast/state/return.lua b/src/parser/ast/state/return.lua index 31266b7..5bf1763 100644 --- a/src/parser/ast/state/return.lua +++ b/src/parser/ast/state/return.lua @@ -5,7 +5,7 @@ local class = require 'class' local Return = class.declare('LuaParser.Node.Return', 'LuaParser.Node.Base') ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private ---@return LuaParser.Node.Return? diff --git a/src/parser/ast/state/state.lua b/src/parser/ast/state/state.lua index 779686e..fb42a56 100644 --- a/src/parser/ast/state/state.lua +++ b/src/parser/ast/state/state.lua @@ -26,7 +26,7 @@ local SingleExp = class.declare('LuaParser.Node.SingleExp', 'LuaParser.Node.Base local Select = class.declare('LuaParser.Node.Select', 'LuaParser.Node.Base') ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@alias LuaParser.Node.State ---| LuaParser.Node.LocalDef diff --git a/src/parser/ast/state/while.lua b/src/parser/ast/state/while.lua index 5cdc45d..db0e49c 100644 --- a/src/parser/ast/state/while.lua +++ b/src/parser/ast/state/while.lua @@ -7,7 +7,7 @@ local class = require 'class' local While = class.declare('LuaParser.Node.While', 'LuaParser.Node.Block') ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private ---@return LuaParser.Node.While? diff --git a/src/parser/ast/string.lua b/src/parser/ast/string.lua index d39422c..70c4e55 100644 --- a/src/parser/ast/string.lua +++ b/src/parser/ast/string.lua @@ -55,7 +55,7 @@ String.__getter.view = function (self) end ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' -- 解析字符串 ---@private diff --git a/src/parser/ast/table.lua b/src/parser/ast/table.lua index ffbbff9..4b2eb78 100644 --- a/src/parser/ast/table.lua +++ b/src/parser/ast/table.lua @@ -7,7 +7,7 @@ local Table = class.declare('LuaParser.Node.Table', 'LuaParser.Node.Base') Table.isLiteral = true ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private ---@return LuaParser.Node.Table? diff --git a/src/parser/ast/unary.lua b/src/parser/ast/unary.lua index 033efc1..90ae877 100644 --- a/src/parser/ast/unary.lua +++ b/src/parser/ast/unary.lua @@ -20,7 +20,7 @@ local UnaryAlias = { local Unary = class.declare('LuaParser.Node.Unary', 'LuaParser.Node.Base') ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private ---@return LuaParser.Node.Unary? diff --git a/src/parser/ast/var.lua b/src/parser/ast/var.lua index e9a76af..08e83eb 100644 --- a/src/parser/ast/var.lua +++ b/src/parser/ast/var.lua @@ -14,7 +14,7 @@ local Var = class.declare('LuaParser.Node.Var', 'LuaParser.Node.Base') local Varargs = class.declare('LuaParser.Node.Varargs', 'LuaParser.Node.Base') ---@class LuaParser.Ast -local Ast = class.declare 'LuaParser.Ast' +local Ast = class.get 'LuaParser.Ast' ---@private ---@return LuaParser.Node.Var? diff --git a/test/ast/cats.lua b/test/ast/cats.lua index 3f4f45c..30e3868 100644 --- a/test/ast/cats.lua +++ b/test/ast/cats.lua @@ -467,6 +467,10 @@ TEST [[ }, } +TEST [[ +---@type true +]] + TEST [[ ---@unknown ]] From 5496120162764ff2a5c9fcfd036841081e03cf25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 15 Nov 2023 18:17:46 +0800 Subject: [PATCH 89/94] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/base.lua | 2 ++ src/parser/ast/boolean.lua | 2 -- src/parser/ast/cats/boolean.lua | 17 +++++++++++++++++ src/parser/ast/cats/cat.lua | 1 + src/parser/ast/cats/integer.lua | 23 +++++++++++++++++++++++ src/parser/ast/cats/type.lua | 1 + src/parser/ast/nil.lua | 2 -- src/parser/ast/number.lua | 4 ---- src/parser/ast/string.lua | 10 ---------- 9 files changed, 44 insertions(+), 18 deletions(-) create mode 100644 src/parser/ast/cats/integer.lua diff --git a/src/parser/ast/base.lua b/src/parser/ast/base.lua index 69fdc23..032f475 100644 --- a/src/parser/ast/base.lua +++ b/src/parser/ast/base.lua @@ -151,6 +151,8 @@ end ---@field value? nil|boolean|number|string|integer local Literal = class.declare('LuaParser.Node.Literal', 'LuaParser.Node.Base') +Literal.isLiteral = true + ---@param self LuaParser.Node.Literal ---@return string ---@return true diff --git a/src/parser/ast/boolean.lua b/src/parser/ast/boolean.lua index 7f59bf6..eeaa42a 100644 --- a/src/parser/ast/boolean.lua +++ b/src/parser/ast/boolean.lua @@ -4,8 +4,6 @@ local class = require 'class' ---@field value boolean local Boolean = class.declare('LuaParser.Node.Boolean', 'LuaParser.Node.Literal') -Boolean.isLiteral = true - ---@class LuaParser.Ast local Ast = class.get 'LuaParser.Ast' diff --git a/src/parser/ast/cats/boolean.lua b/src/parser/ast/cats/boolean.lua index 79df73c..be91d6a 100644 --- a/src/parser/ast/cats/boolean.lua +++ b/src/parser/ast/cats/boolean.lua @@ -6,3 +6,20 @@ local CatBoolean = class.declare('LuaParser.Node.CatBoolean', 'LuaParser.Node.Ba ---@class LuaParser.Ast local Ast = class.get 'LuaParser.Ast' + +---@private +---@return LuaParser.Node.CatBoolean? +function Ast:parseCatBoolean() + local token, _, pos = self.lexer:peek() + if token ~= 'true' and token ~= 'false' then + return nil + end + + local value = self:createNode('LuaParser.Node.CatBoolean', { + value = token == 'true' and true or false, + start = pos, + finish = pos + #token, + }) + + return value +end diff --git a/src/parser/ast/cats/cat.lua b/src/parser/ast/cats/cat.lua index 6431fab..746a0b3 100644 --- a/src/parser/ast/cats/cat.lua +++ b/src/parser/ast/cats/cat.lua @@ -7,6 +7,7 @@ require 'parser.ast.cats.union' require 'parser.ast.cats.cross' require 'parser.ast.cats.function' require 'parser.ast.cats.table' +require 'parser.ast.cats.boolean' ---@class LuaParser.Node.Cat: LuaParser.Node.Base ---@field subtype string diff --git a/src/parser/ast/cats/integer.lua b/src/parser/ast/cats/integer.lua new file mode 100644 index 0000000..06ab4db --- /dev/null +++ b/src/parser/ast/cats/integer.lua @@ -0,0 +1,23 @@ +local class = require 'class' + +---@class LuaParser.Node.CatInteger: LuaParser.Node.Literal +---@field value integer +local CatInteger = class.declare('LuaParser.Node.CatInteger', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.get 'LuaParser.Ast' + +---@private +---@return LuaParser.Node.CatInteger? +function Ast:parseCatInteger() + local token, tp, pos = self.lexer:peek() + if tp ~= 'Num' and token ~= '-' then + return nil + end + + local value + if token == '-' then + self:skipSpace() + local num, numPos = self.lexer:consumeType 'Num' + end +end diff --git a/src/parser/ast/cats/type.lua b/src/parser/ast/cats/type.lua index b56f1e7..afac95c 100644 --- a/src/parser/ast/cats/type.lua +++ b/src/parser/ast/cats/type.lua @@ -49,6 +49,7 @@ function Ast:parseCatTerm(required) local head = self:parseCatParen() or self:parseCatFunction() or self:parseCatTable() + or self:parseCatBoolean() or self:parseCatID() if not head then diff --git a/src/parser/ast/nil.lua b/src/parser/ast/nil.lua index e1b0d36..deec277 100644 --- a/src/parser/ast/nil.lua +++ b/src/parser/ast/nil.lua @@ -3,11 +3,9 @@ local class = require 'class' ---@class LuaParser.Node.Nil: LuaParser.Node.Literal local Nil = class.declare('LuaParser.Node.Nil', 'LuaParser.Node.Literal') -Nil.isLiteral = true Nil.toString = 'nil' Nil.isTruly = false - ---@class LuaParser.Ast local Ast = class.get 'LuaParser.Ast' diff --git a/src/parser/ast/number.lua b/src/parser/ast/number.lua index 5f74efd..e78863f 100644 --- a/src/parser/ast/number.lua +++ b/src/parser/ast/number.lua @@ -6,10 +6,8 @@ local class = require 'class' ---@field value number ---@field valuei? number # 虚数 ---@field numBase 2 | 10 | 16 ----@field view string local Float = class.declare('LuaParser.Node.Float', 'LuaParser.Node.Literal') -Float.isLiteral = true Float.value = 0.0 ---@param self LuaParser.Node.Float @@ -60,10 +58,8 @@ end ---@field valuei? number # 虚数 ---@field numBase 2 | 10 | 16 ---@field intTail? 'LL' | 'ULL' ----@field view string local Integer = class.declare('LuaParser.Node.Integer', 'LuaParser.Node.Literal') -Integer.isLiteral = true Integer.value = 0 ---@param self LuaParser.Node.Integer diff --git a/src/parser/ast/string.lua b/src/parser/ast/string.lua index 70c4e55..d5228e9 100644 --- a/src/parser/ast/string.lua +++ b/src/parser/ast/string.lua @@ -4,14 +4,11 @@ local class = require 'class' ---@class LuaParser.Node.String: LuaParser.Node.Literal ---@field value string ----@field view string ---@field quo string ---@field escs? table ---@field missQuo? true local String = class.declare('LuaParser.Node.String', 'LuaParser.Node.Literal') -String.isLiteral = true - local escMap = { ['a'] = '\a', ['b'] = '\b', @@ -47,13 +44,6 @@ String.__getter.value = function (self) end end ----@param self LuaParser.Node.String ----@return string ----@return true -String.__getter.view = function (self) - return self.value, true -end - ---@class LuaParser.Ast local Ast = class.get 'LuaParser.Ast' From 3463923db580ac440ef3c89ab36878edf229cd01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 15 Nov 2023 19:36:52 +0800 Subject: [PATCH 90/94] =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/cats/boolean.lua | 2 ++ src/parser/ast/cats/cat.lua | 2 ++ src/parser/ast/cats/integer.lua | 18 ++++++++++++++++-- src/parser/ast/cats/string.lua | 13 +++++++++++++ src/parser/ast/cats/type.lua | 2 ++ src/parser/ast/string.lua | 10 ++++++---- test/ast/cats.lua | 26 +++++++++++++++++++++++++- 7 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 src/parser/ast/cats/string.lua diff --git a/src/parser/ast/cats/boolean.lua b/src/parser/ast/cats/boolean.lua index be91d6a..ba801c4 100644 --- a/src/parser/ast/cats/boolean.lua +++ b/src/parser/ast/cats/boolean.lua @@ -15,6 +15,8 @@ function Ast:parseCatBoolean() return nil end + self.lexer:next() + local value = self:createNode('LuaParser.Node.CatBoolean', { value = token == 'true' and true or false, start = pos, diff --git a/src/parser/ast/cats/cat.lua b/src/parser/ast/cats/cat.lua index 746a0b3..23422e6 100644 --- a/src/parser/ast/cats/cat.lua +++ b/src/parser/ast/cats/cat.lua @@ -8,6 +8,8 @@ require 'parser.ast.cats.cross' require 'parser.ast.cats.function' require 'parser.ast.cats.table' require 'parser.ast.cats.boolean' +require 'parser.ast.cats.integer' +require 'parser.ast.cats.string' ---@class LuaParser.Node.Cat: LuaParser.Node.Base ---@field subtype string diff --git a/src/parser/ast/cats/integer.lua b/src/parser/ast/cats/integer.lua index 06ab4db..166f48d 100644 --- a/src/parser/ast/cats/integer.lua +++ b/src/parser/ast/cats/integer.lua @@ -15,9 +15,23 @@ function Ast:parseCatInteger() return nil end - local value + self.lexer:next() + local int = self:createNode('LuaParser.Node.CatInteger', { + start = pos, + }) + if token == '-' then self:skipSpace() - local num, numPos = self.lexer:consumeType 'Num' + token, pos = self.lexer:consumeType 'Num' + if token then + int.value = - tonumber(token) --[[@as integer]] + else + int.value = 0 + end + else + int.value = tonumber(token) --[[@as integer]] end + + int.finish = pos + #token + return int end diff --git a/src/parser/ast/cats/string.lua b/src/parser/ast/cats/string.lua new file mode 100644 index 0000000..99ce662 --- /dev/null +++ b/src/parser/ast/cats/string.lua @@ -0,0 +1,13 @@ +local class = require 'class' + +---@class LuaParser.Node.CatString: LuaParser.Node.String +local CatString = class.declare('LuaParser.Node.CatString', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.get 'LuaParser.Ast' + +---@private +---@return LuaParser.Node.CatString? +function Ast:parseCatString() + return self:parseShortString('LuaParser.Node.CatString') +end diff --git a/src/parser/ast/cats/type.lua b/src/parser/ast/cats/type.lua index afac95c..fbebd22 100644 --- a/src/parser/ast/cats/type.lua +++ b/src/parser/ast/cats/type.lua @@ -50,6 +50,8 @@ function Ast:parseCatTerm(required) or self:parseCatFunction() or self:parseCatTable() or self:parseCatBoolean() + or self:parseCatInteger() + or self:parseCatString() or self:parseCatID() if not head then diff --git a/src/parser/ast/string.lua b/src/parser/ast/string.lua index d5228e9..46507fe 100644 --- a/src/parser/ast/string.lua +++ b/src/parser/ast/string.lua @@ -55,7 +55,7 @@ function Ast:parseString() if token == '"' or token == "'" or token == '`' then - return self:parseShortString() + return self:parseShortString('LuaParser.Node.String') end if token == '[' then return self:parseLongString() @@ -65,8 +65,10 @@ end -- 解析短字符串 ---@private ----@return LuaParser.Node.String? -function Ast:parseShortString() +---@generic T +---@param stringType `T` +---@return T? +function Ast:parseShortString(stringType) local quo, _, pos = self.lexer:peek() if quo ~= '"' and quo ~= "'" and quo ~= '`' then return nil @@ -245,7 +247,7 @@ function Ast:parseShortString() quo = '"' end - return self:createNode('LuaParser.Node.String', { + return self:createNode(stringType, { start = pos, finish = finishPos, quo = quo, diff --git a/test/ast/cats.lua b/test/ast/cats.lua index 30e3868..360f85d 100644 --- a/test/ast/cats.lua +++ b/test/ast/cats.lua @@ -468,8 +468,32 @@ TEST [[ } TEST [[ ----@type true +---@type true | false | 1 | -1 | 'abc' | "abc" ]] +{ + value = { + exps = { + [1] = { + value = true + }, + [2] = { + value = false + }, + [3] = { + value = 1 + }, + [4] = { + value = -1 + }, + [5] = { + value = 'abc' + }, + [6] = { + value = "abc" + }, + } + } +} TEST [[ ---@unknown From ec184b190927770409e45aa3613af62f208ac5d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 29 Aug 2024 18:43:16 +0800 Subject: [PATCH 91/94] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/ast/ast.lua | 2 +- src/parser/lexer.lua | 74 ++++++++++++++++++++++++------------------ 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/src/parser/ast/ast.lua b/src/parser/ast/ast.lua index f81a2fa..4cc9874 100644 --- a/src/parser/ast/ast.lua +++ b/src/parser/ast/ast.lua @@ -50,7 +50,7 @@ function M:__init(code, version, options) ---@type table self.nssymbolMap = {} -- 词法分析结果 - self.lexer = lexer.parseLua(code) + self.lexer = lexer.new():parse(code) -- 错误信息 ---@type LuaParser.Node.Error[] self.errors = {} diff --git a/src/parser/lexer.lua b/src/parser/lexer.lua index de166f4..d8b5d78 100644 --- a/src/parser/lexer.lua +++ b/src/parser/lexer.lua @@ -1,11 +1,14 @@ local l = require 'lpeglabel' local class = require 'class' -local Sp = l.S' \t\v\f' -local Nl = l.P'\r\n' + l.S'\r\n' -local Number = l.R'09'^1 -local Word = l.R('AZ', 'az', '__', '\x80\xff') * l.R('AZ', 'az', '09', '__', '\x80\xff')^0 -local Symbol = l.P'==' +---@class Lexer +local Lexer = class.declare 'Lexer' + +Lexer.Sp = l.S' \t\v\f' +Lexer.Nl = l.P'\r\n' + l.S'\r\n' +Lexer.Number = l.R'09'^1 +Lexer.Word = l.R('AZ', 'az', '__', '\x80\xff') * l.R('AZ', 'az', '09', '__', '\x80\xff')^0 +Lexer.Symbol = l.P'==' + l.P'~=' + l.P'--' -- non-standard: @@ -38,38 +41,47 @@ local Symbol = l.P'==' -- end non-standard -- singles + l.S'+-*/!#%^&()={}[]|\\\'":;<>,.?~`' -local Unknown = (1 - Number - Word - Symbol - Sp - Nl)^1 -local Token = l.Cp() * l.C( - Nl * l.Cc 'NL' - + Number * l.Cc 'Num' - + Word * l.Cc 'Word' - + Symbol * l.Cc 'Symbol' - + Unknown * l.Cc 'Unknown' -) - -local Parser = l.Ct((Sp^1 + Token)^0) - ----@alias LuaParser.Lexer.Type + +---@param code string +---@return Lexer.Result +function Lexer:parse(code) + if not self.Unknown then + ---@private + self.Unknown = (1 - self.Number - self.Word - self.Symbol - self.Sp - self.Nl)^1 + ---@private + self.Token = l.Cp() * l.C( + self.Nl * l.Cc 'NL' + + self.Number * l.Cc 'Num' + + self.Word * l.Cc 'Word' + + self.Symbol * l.Cc 'Symbol' + + self.Unknown * l.Cc 'Unknown' + ) + self.Parser = l.Ct((self.Sp^1 + self.Token)^0) + end + local result = class.new 'Lexer.Process' (code, self.Parser) + return result +end + +---@alias Lexer.Type ---| 'NL' ---| 'Num' ---| 'Word' ---| 'Symbol' ---| 'Unknown' ----@class Lexer ----@overload fun(code: string, mode: 'Lua' | 'Cat'): Lexer -local M = class.declare 'Lexer' +---@class Lexer.Result +local M = class.declare 'Lexer.Result' ---@param code string ----@param mode 'Lua' | 'Cat' -function M:__init(code, mode) - local results = Parser:match(code) +---@param parser any +function M:__init(code, parser) + local results = parser:match(code) self.len = #code -- 总长度 ---@type string[] self.tokens = {} -- 分离出来的词 ---@type integer[] self.poses = {} -- 每个词的字节开始位置(光标位置,第一个字符为0) - ---@type LuaParser.Lexer.Type[] + ---@type Lexer.Type[] self.types = {} -- 每个词的类型 ---@type integer[] self.nls = {} -- 每个换行符的字节结束位置(下一行的开始位置) @@ -91,7 +103,7 @@ end -- 看看当前的词 ---@param next? integer # 默认为0表示当前的词,1表示下一个词,以此类推 ---@return string? ----@return LuaParser.Lexer.Type? +---@return Lexer.Type? ---@return integer? function M:peek(next) local i = self.ci + (next or 0) @@ -104,7 +116,7 @@ end -- 消耗一个词,返回这个词 ---@param count? integer # 默认为1表示消耗一个词,2表示消耗两个词,以此类推 ---@return string? ----@return LuaParser.Lexer.Type? +---@return Lexer.Type? ---@return integer? function M:next(count) local i = self.ci + (count or 1) @@ -128,7 +140,7 @@ function M:consume(token) end -- 消耗一个指定的类型,返回消耗掉词和位置 ----@param tp LuaParser.Lexer.Type +---@param tp Lexer.Type ---@return string? ---@return integer? function M:consumeType(tp) @@ -210,14 +222,12 @@ function M:savePoint() end end +---@class Lexer.API local API = {} --- 对Lua代码进行分词 ----@param code string ---@return Lexer -function API.parseLua(code) - local lexer = class.new 'Lexer' (code, 'Lua') - return lexer +function API.new() + return class.new 'Lexer' () end return API From 7861265d7f14dd3fe2587a00f118ce71c1e9763d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Wed, 4 Sep 2024 17:46:51 +0800 Subject: [PATCH 92/94] fix --- src/parser/lexer.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/lexer.lua b/src/parser/lexer.lua index d8b5d78..558f972 100644 --- a/src/parser/lexer.lua +++ b/src/parser/lexer.lua @@ -58,7 +58,7 @@ function Lexer:parse(code) ) self.Parser = l.Ct((self.Sp^1 + self.Token)^0) end - local result = class.new 'Lexer.Process' (code, self.Parser) + local result = class.new 'Lexer.Result' (code, self.Parser) return result end From 04efd0fb070f6fb8c145854253b84536dc1fa463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 24 Jul 2025 16:05:34 +0800 Subject: [PATCH 93/94] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=A4=A7=E5=B0=8F?= =?UTF-8?q?=E5=86=991?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/ast/{Nil.lua => nil2.lua} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/ast/{Nil.lua => nil2.lua} (100%) diff --git a/test/ast/Nil.lua b/test/ast/nil2.lua similarity index 100% rename from test/ast/Nil.lua rename to test/ast/nil2.lua From f69be2afd96fca80a7bc15adb14d79e101f393c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=80=E8=90=8C=E5=B0=8F=E6=B1=90?= Date: Thu, 24 Jul 2025 16:05:44 +0800 Subject: [PATCH 94/94] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=A4=A7=E5=B0=8F?= =?UTF-8?q?=E5=86=992?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/ast/{nil2.lua => nil.lua} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/ast/{nil2.lua => nil.lua} (100%) diff --git a/test/ast/nil2.lua b/test/ast/nil.lua similarity index 100% rename from test/ast/nil2.lua rename to test/ast/nil.lua