diff --git a/.luarc.json b/.luarc.json index 8222162..22b5475 100644 --- a/.luarc.json +++ b/.luarc.json @@ -5,12 +5,32 @@ "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 + }, + "hover": { + "expandAlias": false } } diff --git a/.vscode/launch.json b/.vscode/launch.json index d549ea5..f4c7bb6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -16,11 +16,11 @@ "-e", "DEBUG=true", ], - "arg": [ + "outputCapture": [ + "print" ], - "console": "internalConsole", + "internalConsoleOptions": "openOnSessionStart", "sourceCoding": "utf8", - "luaVersion": "5.4", "luaArch": "x86_64", }, { @@ -43,7 +43,6 @@ ], "console": "internalConsole", "sourceCoding": "utf8", - "luaVersion": "5.4", "luaArch": "x86_64", }, ] 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/class.lua b/src/class.lua new file mode 100644 index 0000000..0313e38 --- /dev/null +++ b/src/class.lua @@ -0,0 +1,339 @@ +---@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 r = class[k] + if r == nil then + local f = getter[k] + if f then + local res, needCache = f(self) + if needCache then + self[k] = res + end + return res + else + return nil + end + else + self[k] = r + return r + 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/ast/ast.lua b/src/parser/ast/ast.lua new file mode 100644 index 0000000..4cc9874 --- /dev/null +++ b/src/parser/ast/ast.lua @@ -0,0 +1,224 @@ +local class = require 'class' +local lexer = require 'parser.lexer' +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' +require 'parser.ast.string' +require 'parser.ast.comment' +require 'parser.ast.exp' +require 'parser.ast.id' +require 'parser.ast.var' +require 'parser.ast.call' +require 'parser.ast.table' +require 'parser.ast.block' +require 'parser.ast.field' +require 'parser.ast.unary' +require 'parser.ast.binary' +require 'parser.ast.state.state' +require 'parser.ast.main' +require 'parser.ast.cats.cat' + +---@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.get '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 +function M:__init(code, version, options) + -- 代码内容 + self.code = code + -- Lua版本 + ---@type LuaParser.LuaVersion + self.version = version or 'Lua 5.4' + -- 非标准符号的映射表 + ---@type table + self.nssymbolMap = {} + -- 词法分析结果 + self.lexer = lexer.new():parse(code) + -- 错误信息 + ---@type LuaParser.Node.Error[] + self.errors = {} + -- 注释 + ---@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 = {} + -- 当前的局部变量计数(最大只能存在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 + self.versionNum = major * 10 + minor + + local envMode = 'auto' + 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 + + 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 + +-- 跳过换行符 +---@private +---@return boolean # 是否成功跳过换行符 +function M:skipNL() + 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 + +-- 跳过注释 +---@private +---@param inExp? boolean # 在表达式中 +---@return boolean # 是否成功跳过注释 +function M:skipComment(inExp) + local comment = self:parseComment(inExp) + if comment then + self.comments[#self.comments+1] = comment + return true + end + 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) + + return true +end + +-- 跳过空白符 +---@private +---@param inExp? boolean # 在表达式中 +function M:skipSpace(inExp) + if self.lexer.ci ~= self.lastSpaceCI then + self.lastRightCI = self.lexer.ci + end + repeat until not self:skipNL() + and not self:skipCat() + and not self:skipComment(inExp) + and not self:skipUnknown() + self.lastSpaceCI = self.lexer.ci +end + +-- 获取上个词的右侧位置(不包括换行符和注释) +---@private +---@return integer +function M:getLastPos() + local ci = self.lexer.ci + if ci == self.lastSpaceCI then + ci = self.lastRightCI + end + local token = self.lexer.tokens[ci - 1] + local pos = self.lexer.poses[ci - 1] + return pos + #token +end + +-- 创建一个节点 +---@private +---@generic T: string +---@param type `T` +---@param data table +---@return T +function M:createNode(type, data) + data.ast = self + 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 + 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 new file mode 100644 index 0000000..032f475 --- /dev/null +++ b/src/parser/ast/base.lua @@ -0,0 +1,182 @@ +local class = require 'class' + +---@class LuaParser.Node.Base: Class.Base +---@field type string +---@field ast LuaParser.Ast +---@field start integer # 开始位置(偏移) +---@field finish integer # 结束位置(偏移) +---@field left integer # 开始位置(行号与列号合并) +---@field right integer # 结束位置(行号与列号合并) +---@field startRow integer # 开始行号 +---@field startCol integer # 开始列号 +---@field finishRow integer # 结束行号 +---@field finishCol integer # 结束列号 +---@field code string # 对应的代码 +---@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 +---@field referFunction LuaParser.Node.Function | false # 如果自己是function,则是自己;否则向上搜索一个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 +---@field index? integer +local Base = class.declare 'LuaParser.Node.Base' + +---@type boolean +Base.isBlock = false + +---@type boolean +Base.isFunction = false + +---@type boolean +Base.isLiteral = false + +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 +Base.__getter.left = function (self) + local row, col = self.ast.lexer:rowcol(self.start) + local start = row * rowcolMulti + col + 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 * rowcolMulti + col + return finish, true +end + +---@param self LuaParser.Node.Base +---@return integer +---@return true +Base.__getter.startRow = function (self) + local startRow = self.left // rowcolMulti + return startRow, true +end + +---@param self LuaParser.Node.Base +---@return integer +---@return true +Base.__getter.startCol = function (self) + local startCol = self.left % rowcolMulti + return startCol, true +end + +---@param self LuaParser.Node.Base +---@return integer +---@return true +Base.__getter.finishRow = function (self) + local finishRow = self.right // rowcolMulti + return finishRow, true +end + +---@param self LuaParser.Node.Base +---@return integer +---@return true +Base.__getter.finishCol = function (self) + local finishCol = self.right % rowcolMulti + return finishCol, true +end + +---@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 + +---@param self LuaParser.Node.Base +---@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 + 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 +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 + 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') + +Literal.isLiteral = true + +---@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/binary.lua b/src/parser/ast/binary.lua new file mode 100644 index 0000000..95358e6 --- /dev/null +++ b/src/parser/ast/binary.lua @@ -0,0 +1,129 @@ +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', + ['!='] = '~=', +} + +-- 这些符号从右向左结合 +local RevertConcat = { + ['..'] = true, + ['^'] = true, +} + +---@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.get 'LuaParser.Ast' + +---@private +---@param curExp LuaParser.Node.Exp +---@param curLevel? integer +---@param asState? boolean # 是否作为语句解析 +---@return LuaParser.Node.Binary? +---@return integer? opLevel +function Ast:parseBinary(curExp, curLevel, asState) + 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 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 + -- 如果设置了 `//` 为非标准符号,那么就认为 `//` 是注释而不是整除 + return nil + end + if op == '=' then + if asState then + return nil + end + 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, { + symbol = BinaryAlias[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, false, myLevel) + + local binary = self:createNode('LuaParser.Node.Binary', { + 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/block.lua b/src/parser/ast/block.lua new file mode 100644 index 0000000..75ae122 --- /dev/null +++ b/src/parser/ast/block.lua @@ -0,0 +1,174 @@ +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 localMap table +---@field labelMap table +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 + +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 + +---@class LuaParser.Ast +local Ast = class.get 'LuaParser.Ast' + +local FinishMap = { + ['end'] = true, + ['elseif'] = true, + ['else'] = true, + ['until'] = true, + ['}'] = true, + [')'] = true, +} + +---@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 + block.parentBlock = parentBlock +end + +---@private +---@param block LuaParser.Node.Block +function Ast:blockFinish(block) + assert(self.curBlock == block) + self.blocks[#self.blocks] = nil + self.curBlock = self.blocks[#self.blocks] + + self.localCount = self.localCount - block.localCount +end + +---@private +---@param block LuaParser.Node.Block +function Ast:blockParseChilds(block) + local lastState + while true do + while self.lexer:consume ';' do + self:skipSpace() + end + local token, _, pos = self.lexer:peek() + if not token then + break + end + ---@cast pos -? + if FinishMap[token] and not block.isMain then + break + end + local state = self:parseState() + 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.finish) + end + lastState = state + self:skipSpace() + else + if block.isMain then + self.lexer:next() + self:throw('UNKNOWN_SYMBOL', pos, pos + #token) + else + break + end + 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/boolean.lua b/src/parser/ast/boolean.lua new file mode 100644 index 0000000..eeaa42a --- /dev/null +++ b/src/parser/ast/boolean.lua @@ -0,0 +1,33 @@ +local class = require 'class' + +---@class LuaParser.Node.Boolean: LuaParser.Node.Literal +---@field value boolean +local Boolean = class.declare('LuaParser.Node.Boolean', 'LuaParser.Node.Literal') + +---@class LuaParser.Ast +local Ast = class.get 'LuaParser.Ast' + +-- 解析布尔值 +---@private +---@return LuaParser.Node.Boolean? +function Ast:parseBoolean() + local token = self.lexer:peek() + if token == 'true' then + local start, finish = self.lexer:range() + self.lexer:next() + return self:createNode('LuaParser.Node.Boolean', { + start = start, + finish = finish, + value = true, + }) + end + if token == 'false' then + local start, finish = self.lexer:range() + self.lexer:next() + return self:createNode('LuaParser.Node.Boolean', { + start = start, + finish = finish, + value = false, + }) + end +end diff --git a/src/parser/ast/call.lua b/src/parser/ast/call.lua new file mode 100644 index 0000000..bbc227e --- /dev/null +++ b/src/parser/ast/call.lua @@ -0,0 +1,54 @@ +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') + +---@class LuaParser.Ast +local Ast = class.get 'LuaParser.Ast' + +---@private +---@param last LuaParser.Node.Term +---@return LuaParser.Node.Call? +function Ast:parseCall(last) + 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', { + start = last.start, + finish = self:getLastPos(), + node = last, + args = exps, + argPos = pos, + }) + 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 = self:createNode('LuaParser.Node.Call', { + start = last.start, + finish = self:getLastPos(), + node = last, + args = { literalArg }, + argPos = literalArg.start, + }) + last.parent = call + literalArg.parent = call + return call + end + + return nil +end diff --git a/src/parser/ast/cats/boolean.lua b/src/parser/ast/cats/boolean.lua new file mode 100644 index 0000000..ba801c4 --- /dev/null +++ b/src/parser/ast/cats/boolean.lua @@ -0,0 +1,27 @@ +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' + +---@private +---@return LuaParser.Node.CatBoolean? +function Ast:parseCatBoolean() + local token, _, pos = self.lexer:peek() + if token ~= 'true' and token ~= 'false' then + return nil + end + + self.lexer:next() + + 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 new file mode 100644 index 0000000..23422e6 --- /dev/null +++ b/src/parser/ast/cats/cat.lua @@ -0,0 +1,171 @@ +local class = require 'class' + +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' +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 +---@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 +---| LuaParser.Node.CatType + +---@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.get 'LuaParser.Ast' + +---@private +Ast.catParserMap = {} + +---@private +---@param catType string +---@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 +function Ast:skipCat() + if self:skipMultiLineCatHead() + or self:parseCat() + or self:parseCatBlock() then + return true + else + return false + 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? +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 + + ---@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, + subtype = subtype, + symbolPos = symbolPos - 1, + }) + + 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 + end + cat.attrPos2 = self.lexer:consume ')' + end + + local parser = Ast.catParserMap[cat.subtype] + if parser then + local value = parser(self) + if value then + cat.value = value + value.parent = cat + end + end + + cat.finish = self:getLastPos() + + cat.tail = self:parseTail() + + self.status = oldStatus + + 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/class.lua b/src/parser/ast/cats/class.lua new file mode 100644 index 0000000..6bce284 --- /dev/null +++ b/src/parser/ast/cats/class.lua @@ -0,0 +1,39 @@ +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 +local Ast = class.get '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, + }) + + 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 +end diff --git a/src/parser/ast/cats/cross.lua b/src/parser/ast/cats/cross.lua new file mode 100644 index 0000000..50d907b --- /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.get '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/function.lua b/src/parser/ast/cats/function.lua new file mode 100644 index 0000000..b4f7526 --- /dev/null +++ b/src/parser/ast/cats/function.lua @@ -0,0 +1,213 @@ +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 # 冒号的位置 +---@field symbolPos4? integer # 返回值左括号的位置 +---@field symbolPos5? integer # 返回值右括号的位置 +---@field async? boolean # 是否异步 +local CatFunction = class.declare('LuaParser.Node.CatFunction', 'LuaParser.Node.Base') + +---@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.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.get 'LuaParser.Ast' + +---@private +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, + async = syncPos and true or false, + funPos = funPos, + params = {}, + returns = {}, + }) + + self:skipSpace() + funNode.symbolPos1 = self.lexer:consume '(' + if funNode.symbolPos1 then + + self:skipSpace() + funNode.params = self:parseCatParamList() + for _, param in ipairs(funNode.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() + 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() + + return funNode +end + +---@private +---@param required? boolean +---@return LuaParser.Node.CatParam? +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 + + 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 + +---@private +---@return LuaParser.Node.CatParam[] +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/cats/id.lua b/src/parser/ast/cats/id.lua new file mode 100644 index 0000000..b7862c1 --- /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.get '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/src/parser/ast/cats/integer.lua b/src/parser/ast/cats/integer.lua new file mode 100644 index 0000000..166f48d --- /dev/null +++ b/src/parser/ast/cats/integer.lua @@ -0,0 +1,37 @@ +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 + + self.lexer:next() + local int = self:createNode('LuaParser.Node.CatInteger', { + start = pos, + }) + + if token == '-' then + self:skipSpace() + 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/table.lua b/src/parser/ast/cats/table.lua new file mode 100644 index 0000000..30fd0de --- /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.get '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 new file mode 100644 index 0000000..fbebd22 --- /dev/null +++ b/src/parser/ast/cats/type.lua @@ -0,0 +1,172 @@ +local class = require 'class' + +---@alias LuaParser.Node.CatType +---| LuaParser.Node.CatID +---| LuaParser.Node.CatParen +---| LuaParser.Node.CatArray +---| LuaParser.Node.CatCall +---| LuaParser.Node.CatUnion +---| 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 +---@field symbolPos? integer # 右括号的位置 +local CatParen = class.declare('LuaParser.Node.CatParen', 'LuaParser.Node.ParenBase') + +---@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.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.get 'LuaParser.Ast' + +---@private +---@param required? boolean +---@return LuaParser.Node.CatType? +function Ast:parseCatType(required) + local catType = self:parseCatUnion(required) + return catType +end + +---@private +---@param required? boolean +---@return LuaParser.Node.CatType? +function Ast:parseCatTerm(required) + local head = self:parseCatParen() + or self:parseCatFunction() + or self:parseCatTable() + or self:parseCatBoolean() + or self:parseCatInteger() + or self:parseCatString() + or self:parseCatID() + + if not head then + if required then + self:throw('MISS_CAT_NAME', self:getLastPos()) + end + return nil + end + + ---@type LuaParser.Node.CatType + local current = head + while true do + self:skipSpace() + + local chain = self:parseCatArray(current) + or self:parseCatCall(current) + + if chain then + current = chain + else + break + end + end + + return current +end + +---@private +---@param atLeastOne? boolean +---@return LuaParser.Node.CatType[] +function Ast:parseCatTypeList(atLeastOne) + return self:parseList(atLeastOne, false, self.parseCatType) +end + +---@private +---@return LuaParser.Node.CatParen? +function Ast:parseCatParen() + local plPos = self.lexer:consume '(' + if not plPos then + return nil + end + + self:skipSpace() + local value = self:parseCatType(true) + local paren = self:createNode('LuaParser.Node.CatParen', { + start = plPos, + value = value, + }) + + if value then + value.parent = paren + end + + self:skipSpace() + 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 + + 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/src/parser/ast/cats/union.lua b/src/parser/ast/cats/union.lua new file mode 100644 index 0000000..b6ec286 --- /dev/null +++ b/src/parser/ast/cats/union.lua @@ -0,0 +1,50 @@ +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.get 'LuaParser.Ast' + +---@private +---@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 first + end + + ---@type LuaParser.Node.CatUnion + local union = self:createNode('LuaParser.Node.CatUnion', { + start = first.start, + poses = { pos }, + exps = { first }, + }) + + + 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() + + return union +end diff --git a/src/parser/ast/comment.lua b/src/parser/ast/comment.lua new file mode 100644 index 0000000..ef1cb66 --- /dev/null +++ b/src/parser/ast/comment.lua @@ -0,0 +1,99 @@ +local class = require 'class' + +---@class LuaParser.Node.Comment: LuaParser.Node.Base +---@field subtype 'short' | 'long' +---@field value string +local Comment = class.declare('LuaParser.Node.Comment', 'LuaParser.Node.Base') + +---@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 Ast = class.get 'LuaParser.Ast' + +---@private +---@param inExp? boolean +---@return LuaParser.Node.Comment? +function Ast:parseComment(inExp) + return self:parseLongComment() + or self:parseShortComment(inExp) +end + +---@private +---@param inExp? boolean +---@return LuaParser.Node.Comment? +function Ast:parseShortComment(inExp) + local token, _, pos = self.lexer:peek() + if not token then + return nil + end + ---@cast pos -? + if token == '--' + 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 + local offset = self.code:find('[\r\n]', pos + 2) or (#self.code + 1) + self.lexer:fastForward(offset) + return self:createNode('LuaParser.Node.Comment', { + subtype = 'short', + start = pos, + finish = offset - 1, + }) + end + return nil +end + +---@private +---@return LuaParser.Node.Comment? +function Ast: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:throw('ERR_C_LONG_COMMENT', pos, pos + 2) + 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: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 = finish, + }) +end diff --git a/src/parser/ast/error.lua b/src/parser/ast/error.lua new file mode 100644 index 0000000..775fa33 --- /dev/null +++ b/src/parser/ast/error.lua @@ -0,0 +1,114 @@ +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' + +---@class LuaParser.Ast +---@field extra? table +local Ast = class.declare 'LuaParser.Ast' + +-- 添加错误信息 +---@param errorCode string +---@param start integer +---@param finish integer? +---@param extra table? +function Ast:throw(errorCode, start, finish, extra) + self.errors[#self.errors+1] = self:createNode('LuaParser.Node.Error', { + code = errorCode, + start = start, + finish = finish or start, + extra = extra, + }) +end + +-- 添加错误“缺少符号” +---@param start integer +---@param symbol string +function Ast:throwMissSymbol(start, symbol) + self:throw('MISS_SYMBOL', start, start, { + symbol = symbol, + }) +end + +-- 添加错误“缺少表达式” +---@param start integer +function Ast:throwMissExp(start) + self:throw('MISS_EXP', start, start) +end + +-- 断言下个符号,如果成功则消耗,否则报错 +---@private +---@param symbol string +---@return integer? pos +function Ast:assertSymbol(symbol) + local pos = self.lexer:consume(symbol) + if not pos then + self:throwMissSymbol(self:getLastPos(), 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 + +---@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/exp.lua b/src/parser/ast/exp.lua new file mode 100644 index 0000000..4560630 --- /dev/null +++ b/src/parser/ast/exp.lua @@ -0,0 +1,196 @@ +local class = require 'class' + +---@class LuaParser.Node.ParenBase: LuaParser.Node.Base +local ParenBase = class.declare('LuaParser.Node.ParenBase', 'LuaParser.Node.Base') + +function ParenBase.__getter.asNumber(self) + return self.exp.asNumber, true +end + +function ParenBase.__getter.asString(self) + return self.exp.asString, true +end + +function ParenBase.__getter.asBoolean(self) + return self.exp.asBoolean, true +end + +function ParenBase.__getter.asInteger(self) + return self.exp.asInteger, true +end + +function ParenBase.__getter.toNumber(self) + return self.exp.toNumber, true +end + +function ParenBase.__getter.toString(self) + return self.exp.toString, true +end + +function ParenBase.__getter.toInteger(self) + return self.exp.toInteger, true +end + +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.get 'LuaParser.Ast' + +---@alias LuaParser.Node.Exp +---| LuaParser.Node.Term +---| LuaParser.Node.Unary +---| LuaParser.Node.Binary +---| LuaParser.Node.Select + +-- 解析表达式 +---@private +---@param required? boolean +---@param curLevel? integer # 新表达式的符号优先级必须比这个大 +---@param asState? boolean # 是否作为语句解析 +---@return LuaParser.Node.Exp? +function Ast:parseExp(required, asState, 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 + + while true do + local binary = self:parseBinary(curExp, curLevel, asState) + if not binary then + break + end + curExp = binary + end + + return curExp +end + +-- 解析表达式列表,以逗号分隔 +---@private +---@param atLeastOne? boolean +---@param greedy? boolean +---@return LuaParser.Node.Exp[] +function Ast:parseExpList(atLeastOne, greedy) + return self:parseList(atLeastOne, greedy, self.parseExp) +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 +---| LuaParser.Node.Varargs +---| LuaParser.Node.Table +---| LuaParser.Node.Function + +---@alias LuaParser.Node.TermChain +---| LuaParser.Node.Field +---| LuaParser.Node.Call + +-- 解析表达式中的一项 +---@private +---@return LuaParser.Node.Term? +function Ast:parseTerm() + ---@type LuaParser.Node.TermHead? + local head = self:parseNil() + or self:parseBoolean() + or self:parseNumber() + or self:parseString() + or self:parseVarargs() + or self:parseFunction() + or self:parseVar() + or self:parseParen() + or self:parseTable() + + if not head then + 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 + + while true do + self:skipSpace(true) + + 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 + 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 + end + end + + return current +end + +---@private +---@return LuaParser.Node.Paren? +function Ast:parseParen() + local pos = self.lexer:consume '(' + if not pos then + return nil + end + local exp = self:parseExp(true) + local paren = self:createNode('LuaParser.Node.Paren', { + start = pos, + exp = exp, + }) + if exp then + exp.parent = paren + end + self:skipSpace() + self:assertSymbol ')' + paren.finish = self:getLastPos() + + return paren +end diff --git a/src/parser/ast/field.lua b/src/parser/ast/field.lua new file mode 100644 index 0000000..45875d8 --- /dev/null +++ b/src/parser/ast/field.lua @@ -0,0 +1,188 @@ +local class = require 'class' + +---@class LuaParser.Node.Field: LuaParser.Node.Base +---@field subtype 'field' | 'method' | 'index' +---@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 +---@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.TableFieldID | 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.get 'LuaParser.Ast' + +---@private +---@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 = self:createNode('LuaParser.Node.Field', { + 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 + 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 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 + key.parent = field + end + return field + end + return nil +end + +---@private +---@return LuaParser.Node.TableField? +function Ast:parseTableField() + return self:parseTableFieldAsField() + or self:parseTableFieldAsIndex() + or self:parseTableFieldAsExp() +end + +---@private +---@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 = self:createNode('LuaParser.Node.TableField', { + 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.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() + 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, + 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 + +---@private +---@return LuaParser.Node.TableField? +function Ast:parseTableFieldAsExp() + local exp = self:parseExp() + if not exp then + return nil + end + local tfield = self:createNode('LuaParser.Node.TableField', { + 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 new file mode 100644 index 0000000..995a24d --- /dev/null +++ b/src/parser/ast/id.lua @@ -0,0 +1,109 @@ +local class = require 'class' + +---@class LuaParser.Ast +local Ast = class.get 'LuaParser.Ast' + +---@alias LuaParser.Node.ID +---| LuaParser.Node.Local +---| LuaParser.Node.Var +---| LuaParser.Node.FieldID +---| LuaParser.Node.TableFieldID +---| LuaParser.Node.Param +---| LuaParser.Node.LabelName +---| LuaParser.Node.AttrName +---| LuaParser.Node.CatAttr +---| LuaParser.Node.CatParamName +---| LuaParser.Node.CatReturnName + +---@private +---@generic T: LuaParser.Node.ID +---@param nodeType `T` +---@param required? boolean +---@param canBeKeyword? boolean +---@return T? +function Ast:parseID(nodeType, required, canBeKeyword) + local token, tp, pos = self.lexer:peek() + if tp ~= 'Word' then + if required then + self:throw('MISS_NAME', self:getLastPos()) + end + return nil + end + ---@cast token -? + ---@cast pos -? + if self:isKeyWord(token) 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, + start = pos, + finish = pos + #token, + }) +end + +---@private +---@generic T: LuaParser.Node.ID +---@param nodeType `T` +---@param atLeastOne? boolean +---@param greedy? boolean +---@return T[] +function Ast:parseIDList(nodeType, atLeastOne, greedy) + return self:parseList(atLeastOne, greedy, function (_, required) + return self:parseID(nodeType, required) + end) +end + +-- goto 单独处理 +Ast.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, +} + +---@private +---@param word string +---@return boolean +function Ast: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/list.lua b/src/parser/ast/list.lua new file mode 100644 index 0000000..c0e3f49 --- /dev/null +++ b/src/parser/ast/list.lua @@ -0,0 +1,52 @@ +local class = require 'class' + +---@class LuaParser.Ast +local Ast = class.get '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) + 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 + else + break + end + wantSep = true + end + return list +end diff --git a/src/parser/ast/main.lua b/src/parser/ast/main.lua new file mode 100644 index 0000000..bd0d1ae --- /dev/null +++ b/src/parser/ast/main.lua @@ -0,0 +1,68 @@ +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 + +function Main.__getter.parent() + return false, true +end + +function Main.__getter.parentBlock() + return false, true +end + +---@class LuaParser.Ast +local Ast = class.get 'LuaParser.Ast' + +---@private +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) + else + self.lexer:fastForward(#self.code) + 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) + -- 虽然 _ENV 是上值,但是不计入200个的数量限制 + self.localCount = 0 + end + + self:skipSpace() + self:blockParseChilds(main) + self:blockFinish(main) + + return main +end diff --git a/src/parser/ast/nil.lua b/src/parser/ast/nil.lua new file mode 100644 index 0000000..deec277 --- /dev/null +++ b/src/parser/ast/nil.lua @@ -0,0 +1,26 @@ +local class = require 'class' + +---@class LuaParser.Node.Nil: LuaParser.Node.Literal +local Nil = class.declare('LuaParser.Node.Nil', 'LuaParser.Node.Literal') + +Nil.toString = 'nil' +Nil.isTruly = false + +---@class LuaParser.Ast +local Ast = class.get 'LuaParser.Ast' + +-- 解析 nil +---@private +---@return LuaParser.Node.Nil? +function Ast:parseNil() + local token = self.lexer:peek() + if token ~= 'nil' then + return nil + end + local start, finish = self.lexer:range() + self.lexer:next() + return self:createNode('LuaParser.Node.Nil', { + 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..e78863f --- /dev/null +++ b/src/parser/ast/number.lua @@ -0,0 +1,361 @@ +local class = require 'class' + +---@alias LuaParser.Node.Number LuaParser.Node.Float | LuaParser.Node.Integer + +---@class LuaParser.Node.Float: LuaParser.Node.Literal +---@field value number +---@field valuei? number # 虚数 +---@field numBase 2 | 10 | 16 +local Float = class.declare('LuaParser.Node.Float', 'LuaParser.Node.Literal') + +Float.value = 0.0 + +---@param self LuaParser.Node.Float +---@return 2 | 10 | 16 +---@return true +Float.__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 + +---@param self LuaParser.Node.Float +---@return string +---@return true +Float.__getter.toString = 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 + +---@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' +local Integer = class.declare('LuaParser.Node.Integer', 'LuaParser.Node.Literal') + +Integer.value = 0 + +---@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 + +---@param self LuaParser.Node.Integer +---@return string +---@return true +Integer.__getter.toString = 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 + +---@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.get 'LuaParser.Ast' + +-- 解析数字(可以带负号) +---@private +---@return LuaParser.Node.Float | LuaParser.Node.Integer | nil +function Ast:parseNumber() + -- 快速判断是否为数字 + 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:consume '-' + + local node = self:parseNumber16() + or self:parseNumber2() + or self:parseNumber10() + if not node then + 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 Ast:fastForwardNumber(curOffset) + local word = self.code:match('^[%.%w_\x80-\xff]+', curOffset) + if not word then + self.lexer:fastForward(curOffset - 1) + return + end + self:throw('MALFORMED_NUMBER', curOffset - 1, curOffset - 1 + #word) + self.lexer:fastForward(curOffset - 1 + #word) +end + +---@private +---@param curOffset integer +---@return boolean +function Ast:parseNumberI(curOffset) + if self.code:find('^[iI]', curOffset) then + return true + end + return false +end + +---@private +---@param value number? +---@param startPos integer +---@param curOffset integer +---@return LuaParser.Node.Float +function Ast:buildFloat(value, startPos, curOffset) + local valuei + if self:parseNumberI(curOffset) then + 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, + 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 Ast: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 + + 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, + finish = curOffset - 1, + value = valuei and 0 or value, + valuei = valuei, + intTail = intTail, + }) +end + +-- 解析十六进制数字(不支持负号) +---@private +---@return LuaParser.Node.Float | LuaParser.Node.Integer | nil +function Ast:parseNumber16() + local token, _, pos = self.lexer:peek() + + if token ~= '0' then + return nil + end + ---@cast pos -? + if not self.code:match('^[xX]', pos + 2) then + return + end + + local curOffset = pos + 3 + + 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*)()', curOffset) + if expOffset then + curOffset = expOffset + if #expPart == 0 then + self:throw('MISS_EXPONENT', curOffset - 1, curOffset - 1) + end + end + + if not intPart then + if not numPart then + self:throw('MUST_X16', pos + 2, pos + 2) + end + if numPart == '' then + self:throw('MUST_X16', numOffset - 1, numOffset - 1) + 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 + +-- 解析二进制数字(不支持负号) +---@private +---@return LuaParser.Node.Integer | nil +function Ast: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:throw('UNSUPPORT_SYMBOL', pos, curOffset - 1) + end + + return self:buildInteger(value, pos, curOffset) +end + +-- 解析十进制数字(不支持负号) +---@private +---@return LuaParser.Node.Float | LuaParser.Node.Integer | nil +function Ast: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:throw('MISS_EXPONENT', curOffset - 1, curOffset - 1) + end + end + + if not intPart then + if numPart == '' then + self.lexer:next() + self:throw('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/ast/state/break.lua b/src/parser/ast/state/break.lua new file mode 100644 index 0000000..7eb8ffb --- /dev/null +++ b/src/parser/ast/state/break.lua @@ -0,0 +1,72 @@ +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 +local Continue = class.declare('LuaParser.Node.Continue', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.get '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() + local pos = self.lexer:consume 'break' + if not pos then + 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 + +---@private +---@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 + + 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/state/do.lua b/src/parser/ast/state/do.lua new file mode 100644 index 0000000..faadebe --- /dev/null +++ b/src/parser/ast/state/do.lua @@ -0,0 +1,33 @@ +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 +local Ast = class.get 'LuaParser.Ast' + +---@private +---@return LuaParser.Node.Do? +function Ast:parseDo() + local pos = self.lexer:consume 'do' + if not pos then + return nil + end + + local doNode = self:createNode('LuaParser.Node.Do', { + start = pos, + }) + + self:skipSpace() + self:blockStart(doNode) + self:blockParseChilds(doNode) + self:blockFinish(doNode) + + self:skipSpace() + doNode.symbolPos = self:assertSymbol 'end' + + doNode.finish = self:getLastPos() + + return doNode +end diff --git a/src/parser/ast/state/for.lua b/src/parser/ast/state/for.lua new file mode 100644 index 0000000..c09f14a --- /dev/null +++ b/src/parser/ast/state/for.lua @@ -0,0 +1,109 @@ +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.get 'LuaParser.Ast' + +---@private +---@return LuaParser.Node.For? +function Ast:parseFor() + local pos = self.lexer:consume 'for' + if not pos then + return nil + end + + local forNode = self:createNode('LuaParser.Node.For', { + start = pos, + }) + + self:skipSpace() + local vars = self:parseLocalList() + 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 + + 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() + local exps = self:parseExpList(true, true) + forNode.exps = exps + for i = 1, #exps do + local exp = exps[i] + exp.parent = forNode + 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:assertSymbolDo(true) + + if symbolPos2 then + forNode.symbolPos2 = symbolPos2 + + self:skipSpace() + self:blockStart(forNode) + for i = 1, #vars do + self:initLocal(vars[i]) + end + self:blockParseChilds(forNode) + self:blockFinish(forNode) + + end + + self:skipSpace() + local symbolPos3 = self:assertSymbolEnd(pos, pos + #'for') + forNode.symbolPos3 = symbolPos3 + + forNode.finish = self:getLastPos() + + return forNode +end diff --git a/src/parser/ast/state/function.lua b/src/parser/ast/state/function.lua new file mode 100644 index 0000000..6652b11 --- /dev/null +++ b/src/parser/ast/state/function.lua @@ -0,0 +1,198 @@ +local class = require 'class' + +---@class LuaParser.Node.Param: LuaParser.Node.Local +---@field parent LuaParser.Node.Function +---@field index integer +---@field id string +local Param = class.declare('LuaParser.Node.Param', 'LuaParser.Node.Local') + +---@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 +---@field params? LuaParser.Node.Local[] +---@field symbolPos1? integer # 左括号 +---@field symbolPos2? integer # 右括号 +---@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) + return self, true +end + + +---@class LuaParser.Ast +local Ast = class.get 'LuaParser.Ast' + +---@private +---@param isLocal? boolean +---@return LuaParser.Node.Function? +function Ast:parseFunction(isLocal) + local pos = self.lexer:consume 'function' + if not pos then + return nil + end + + self:skipSpace() + 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 + + self:skipSpace() + local symbolPos1 = self.lexer:consume '(' + + local params, symbolPos2 + if symbolPos1 then + self:skipSpace() + params = self:parseParamList() + self:skipSpace() + symbolPos2 = self:assertSymbol ')' + else + self:throwMissSymbol(self:getLastPos(), '(') + end + + local func = self:createNode('LuaParser.Node.Function', { + start = pos, + name = name, + params = params, + symbolPos1 = symbolPos1, + 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() + if isLocal and name then + ---@cast name LuaParser.Node.Local + self:initLocal(name) + end + self:blockStart(func) + if params then + for i = 1, #params do + self:initLocal(params[i]) + end + end + self:blockParseChilds(func) + self:blockFinish(func) + end + + self:skipSpace() + local symbolPos3 = self:assertSymbolEnd(pos, pos + #'function') + + func.symbolPos3 = symbolPos3 + func.finish = self:getLastPos() + + return func +end + +---@private +---@return LuaParser.Node.FuncName? +function Ast:parseFunctionName() + local head = self:parseVar() + + if not head then + return nil + end + + ---@type LuaParser.Node.Var|LuaParser.Node.Field + local current = head + + while true do + self:skipSpace() + + local chain = self:parseField(current) + + if chain then + if current.type == 'Field' + and current.subtype == 'method' then + self:throwMissSymbol(current.finish, '(') + end + + if chain.subtype == 'index' then + self:throw('INDEX_IN_FUNC_NAME', chain.symbolPos, chain.finish) + end + + current = chain + else + break + end + end + + return current +end + +---@private +---@return LuaParser.Node.Param[] +function Ast:parseParamList() + local list = self:parseList(false, true, self.parseParam) + + 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(required) + local pos = self.lexer:consume '...' + if pos then + return self:createNode('LuaParser.Node.Param', { + start = 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/src/parser/ast/state/if.lua b/src/parser/ast/state/if.lua new file mode 100644 index 0000000..00e9d24 --- /dev/null +++ b/src/parser/ast/state/if.lua @@ -0,0 +1,144 @@ +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.get 'LuaParser.Ast' + +---@private +---@return LuaParser.Node.If? +function Ast:parseIf() + local token, _, pos = self.lexer:peek() + if token ~= 'if' then + return nil + end + + ---@cast pos -? + + local ifNode = self:createNode('LuaParser.Node.If', { + start = pos, + childs = {}, + }) + + local hasElse + while true do + local child = self:parseIfChild() + if not child then + break + 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() + self:assertSymbolEnd(pos, pos + #'if') + + ifNode.finish = self:getLastPos() + + return ifNode +end + +---@private +---@return LuaParser.Node.IfChild? +function Ast:parseIfChild() + return self:parseIfChildIf() + or self:parseIfChildElseIf() + or self:parseIfChildElse() +end + +---@private +---@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(true) + + local node = self:createNode('LuaParser.Node.IfChild', { + subtype = 'if', + start = pos, + condition = condition, + }) + + self:skipSpace() + node.symbolPos = self:assertSymbolThen(condition ~= nil) + + self:skipSpace() + self:blockStart(node) + self:blockParseChilds(node) + self:blockFinish(node) + node.finish = self:getLastPos() + + return node +end + +---@private +---@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(true) + + local node = self:createNode('LuaParser.Node.IfChild', { + subtype = 'elseif', + start = pos, + condition = condition, + }) + + self:skipSpace() + node.symbolPos = self:assertSymbolThen(condition ~= nil) + + self:skipSpace() + self:blockStart(node) + self:blockParseChilds(node) + self:blockFinish(node) + node.finish = self:getLastPos() + + return node +end + +---@private +---@return LuaParser.Node.IfChild? +function Ast:parseIfChildElse() + local pos = self.lexer:consume 'else' + if not pos then + return nil + end + + self:skipSpace() + + local node = self:createNode('LuaParser.Node.IfChild', { + subtype = 'else', + start = pos, + }) + + self:blockStart(node) + self:blockParseChilds(node) + self:blockFinish(node) + + node.finish = self:getLastPos() + + return node +end diff --git a/src/parser/ast/state/label.lua b/src/parser/ast/state/label.lua new file mode 100644 index 0000000..6eaea91 --- /dev/null +++ b/src/parser/ast/state/label.lua @@ -0,0 +1,173 @@ +local class = require 'class' + +---@class LuaParser.Node.Label: LuaParser.Node.Base +---@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 name? LuaParser.Node.LabelName +---@field label? LuaParser.Node.Label # 关联的Label +local Goto = class.declare('LuaParser.Node.Goto', 'LuaParser.Node.Base') + +---@class LuaParser.Ast +local Ast = class.get 'LuaParser.Ast' + +---@private +---@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, true) + + local symbolPos + if labelName then + self:skipSpace() + symbolPos = self:assertSymbol '::' + end + + local label = self:createNode('LuaParser.Node.Label', { + start = pos, + finish = self:getLastPos(), + symbolPos = symbolPos, + }) + + 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 and not self.jit then + self:throw('UNSUPPORT_SYMBOL', pos, pos + 2) + end + + return label +end + +---@private +---@return LuaParser.Node.Goto? +function Ast:parseGoto() + local token, _, pos = self.lexer:peek() + if token ~= 'goto' then + return nil + end + ---@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) + + local gotoNode = self:createNode('LuaParser.Node.Goto', { + start = pos, + finish = self:getLastPos(), + }) + + if labelName then + gotoNode.name = labelName + labelName.parent = gotoNode + end + + if self.versionNum <= 51 and not self.jit then + self:throw('UNSUPPORT_SYMBOL', pos, pos + #'goto') + end + + 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 + + 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) + return + end + + 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 + +-- 获取在指定位置可见的所有标签 +---@public +---@param pos integer +---@return table +function Ast:findVisibleLabels(pos) + local results = {} + local myBlock = self:getRecentBlock(pos) + if not myBlock then + return results + end + return myBlock.labelMap +end diff --git a/src/parser/ast/state/local.lua b/src/parser/ast/state/local.lua new file mode 100644 index 0000000..62903e9 --- /dev/null +++ b/src/parser/ast/state/local.lua @@ -0,0 +1,272 @@ +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 | 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 envRefs? LuaParser.Node.Var[] +---@field attr? LuaParser.Node.Attr +local Local = class.declare('LuaParser.Node.Local', 'LuaParser.Node.Base') + +-- 所有的引用对象 +Local.__getter.refs = function () + return {}, true +end + +-- 所有的赋值对象 +---@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 + 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 +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 and parent.type ~= 'Assign' then + gets[#gets+1] = ref + end + end + 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 # > 的位置 +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.get 'LuaParser.Ast' + +---@private +---@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 = self:createNode('LuaParser.Node.LocalDef', { + start = pos, + }) + + local vars = self:parseLocalList(true) + localdef.vars = vars + + self:skipSpace() + local symbolPos = self.lexer:consume '=' + if symbolPos then + 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 + var.value = value + end + end + end + + 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 + +---@package +Ast.hasThrowedLocalLimit = false + +---@private +---@param loc LuaParser.Node.Local +function Ast:initLocal(loc) + ---@class LuaParser.Node.Block + local block = self.curBlock + if not block then + return + end + + block.locals[#block.locals+1] = loc + + local name = loc.id + block.localMap[name] = loc + + 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 +end + +---@private +---@param name string +---@return LuaParser.Node.Local? +function Ast:getLocal(name) + ---@class LuaParser.Node.Block + local block = self.curBlock + if not block then + return nil + end + return block.localMap[name] or nil +end + +---@private +---@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 + +---@private +---@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 + + if self.versionNum <= 53 then + self:throw('UNSUPPORT_SYMBOL', attrNode.start, attrNode.finish) + end + + 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/ast/state/repeat.lua b/src/parser/ast/state/repeat.lua new file mode 100644 index 0000000..77bf268 --- /dev/null +++ b/src/parser/ast/state/repeat.lua @@ -0,0 +1,44 @@ +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.get 'LuaParser.Ast' + +---@private +---@return LuaParser.Node.Repeat? +function Ast:parseRepeat() + local pos = self.lexer:consume 'repeat' + if not pos then + return nil + end + + local repeatNode = self:createNode('LuaParser.Node.Repeat', { + start = pos, + }) + + self:skipSpace() + self:blockStart(repeatNode) + self:blockParseChilds(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 + self:blockFinish(repeatNode) + + repeatNode.finish = self:getLastPos() + + return repeatNode +end diff --git a/src/parser/ast/state/return.lua b/src/parser/ast/state/return.lua new file mode 100644 index 0000000..5bf1763 --- /dev/null +++ b/src/parser/ast/state/return.lua @@ -0,0 +1,32 @@ +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.get 'LuaParser.Ast' + +---@private +---@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 = self:createNode('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/state.lua b/src/parser/ast/state/state.lua new file mode 100644 index 0000000..fb42a56 --- /dev/null +++ b/src/parser/ast/state/state.lua @@ -0,0 +1,228 @@ +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[] +---@field values LuaParser.Node.Var[] +local Assign = class.declare('LuaParser.Node.Assign', 'LuaParser.Node.Base') + +---@class LuaParser.Node.SingleExp: 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.get 'LuaParser.Ast' + +---@alias LuaParser.Node.State +---| LuaParser.Node.LocalDef +---| LuaParser.Node.StateStartWithExp +---| LuaParser.Node.Label +---| LuaParser.Node.Goto +---| LuaParser.Node.If +---| LuaParser.Node.Do +---| LuaParser.Node.Break +---| LuaParser.Node.Continue +---| LuaParser.Node.Return +---| LuaParser.Node.For +---| LuaParser.Node.While +---| LuaParser.Node.Repeat +---| LuaParser.Node.Function + +---@private +Ast.stateParserMap = {} + +-- 注册语句解析 +---@private +---@param token string +---@param parser fun(self: LuaParser.Ast): LuaParser.Node.State?, boolean? +function Ast:registerStateParser(token, parser) + self.stateParserMap[token] = parser +end + +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 not func.name then + self:throw('MISS_NAME', func.symbolPos1) + end + return func +end) + +---@private +---@return LuaParser.Node.State? +function Ast:parseState() + local token = self.lexer:peek() + + local parser = self.stateParserMap[token] + if parser then + local state, dontConsumeToken = parser(self) + if not dontConsumeToken then + return state + end + end + + return self:parseStateStartWithExp() +end + +---@alias LuaParser.Node.StateStartWithExp +---| LuaParser.Node.Call +---| LuaParser.Node.Assign +---| LuaParser.Node.SingleExp + +---@private +---@return LuaParser.Node.StateStartWithExp? +function Ast:parseStateStartWithExp() + local exp = self:parseExp(false, true) + if not exp then + return nil + end + + if exp.type == 'Call' then + ---@cast exp LuaParser.Node.Call + return exp + end + + if exp.type == 'Var' + or exp.type == 'Field' then + ---@cast exp LuaParser.Node.Field + self:skipSpace() + local assign = self:parseAssign(exp) + if assign then + return assign + end + end + + local state = self:createNode('LuaParser.Node.SingleExp', { + start = exp.start, + finish = exp.finish, + exp = exp, + }) + 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 + +---@private +---@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 exps = {} + local assign = self:createNode('LuaParser.Node.Assign', { + start = first.start, + exps = exps, + }) + + 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 + exps[#exps+1] = exp + self:skipSpace() + end + + local eqPos = self:assertSymbol '=' + + if eqPos then + 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 = exps[i] + if exp then + exp.value = value + end + end + end + + assign.finish = self:getLastPos() + + 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 not lastValue then + return + end + 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/src/parser/ast/state/while.lua b/src/parser/ast/state/while.lua new file mode 100644 index 0000000..db0e49c --- /dev/null +++ b/src/parser/ast/state/while.lua @@ -0,0 +1,50 @@ +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.get 'LuaParser.Ast' + +---@private +---@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) + + local whileNode = self:createNode('LuaParser.Node.While', { + start = pos, + condition = condition, + }) + if condition then + condition.parent = whileNode + end + + self:skipSpace() + local symbolPos1 = self:assertSymbolDo(true) + if symbolPos1 then + whileNode.symbolPos1 = symbolPos1 + + self:skipSpace() + self:blockStart(whileNode) + self:blockParseChilds(whileNode) + self:blockFinish(whileNode) + + end + + self:skipSpace() + local symbolPos2 = self:assertSymbolEnd(pos, pos + #'while') + whileNode.symbolPos2 = symbolPos2 + + whileNode.finish = self:getLastPos() + + return whileNode +end diff --git a/src/parser/ast/string.lua b/src/parser/ast/string.lua new file mode 100644 index 0000000..46507fe --- /dev/null +++ b/src/parser/ast/string.lua @@ -0,0 +1,296 @@ +local class = require 'class' + +---@alias LuaParser.EscMode 'normal' | 'unicode' | 'err' | 'byte' + +---@class LuaParser.Node.String: LuaParser.Node.Literal +---@field value string +---@field quo string +---@field escs? table +---@field missQuo? true +local String = class.declare('LuaParser.Node.String', 'LuaParser.Node.Literal') + +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 + error('短字符串应该在解析时求值!') + else + local raw + if self.missQuo then + raw = self.ast.code:sub(self.start + #self.quo + 1, self.finish - 1) + else + raw = self.ast.code:sub(self.start + #self.quo + 1, self.finish - #self.quo) + end + local value = raw + : gsub('\r\n', '\n') + : gsub('\r', '\n') + : gsub('^\n', '') + return value, true + end +end + +---@class LuaParser.Ast +local Ast = class.get 'LuaParser.Ast' + +-- 解析字符串 +---@private +---@return LuaParser.Node.String? +function Ast:parseString() + local token = self.lexer:peek() + if token == '"' + or token == "'" + or token == '`' then + return self:parseShortString('LuaParser.Node.String') + end + if token == '[' then + return self:parseLongString() + end + return nil +end + +-- 解析短字符串 +---@private +---@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 + end + ---@cast quo -? + ---@cast pos -? + + if quo == '`' and not self.nssymbolMap['`'] then + self:throw('ERR_NONSTANDARD_SYMBOL', pos, pos + 1, { + symbol = '"' + }) + 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) + 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 + 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:throwMissSymbol(curOffset - 1, quo) + break + end + if char == '\\' then + 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' 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' 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:throw('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 + pushEsc('normal', offset - 2, offset) + self.lexer:fastForward(offset) + repeat until not self.lexer:consumeType '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 + 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) + self:throw('MISS_ESC_X', offset, offset) + curOffset = offset + 1 + end + if self.versionNum <= 51 then + self:throw('ERR_ESC', curPos - 1, 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 + pushEsc('err', offset - 2, offset) + self:throwMissSymbol(offset, '{') + curOffset = offset + goto continue + end + pushEsc('unicode', offset - 2, tailOffset - 1) + if self.versionNum <= 52 then + self:throw('ERR_ESC', offset - 2, tailOffset - 1) + else + 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 = '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('MUST_X16', offset + 1, offset + #word + 1) + end + end + end + if rightP == '' then + self:throwMissSymbol(tailOffset - 1, '}') + end + curOffset = tailOffset + 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:throw('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 + + return self:createNode(stringType, { + start = pos, + finish = finishPos, + quo = quo, + value = table.concat(pieces), + escs = escs, + }) +end + +-- 解析长字符串 +---@private +---@return LuaParser.Node.String? +function Ast: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: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:throw('NESTING_LONG_MARK', nestOffset - 1, nestOffset + 1) + end + end + + self.lexer:fastForward(finishPos) + + return self:createNode('LuaParser.Node.String', { + start = pos, + finish = finishPos, + quo = quo, + missQuo = missQuo, + }) +end diff --git a/src/parser/ast/table.lua b/src/parser/ast/table.lua new file mode 100644 index 0000000..4b2eb78 --- /dev/null +++ b/src/parser/ast/table.lua @@ -0,0 +1,84 @@ +local class = require 'class' + +---@class LuaParser.Node.Table: LuaParser.Node.Base +---@field fields LuaParser.Node.Field[] +local Table = class.declare('LuaParser.Node.Table', 'LuaParser.Node.Base') + +Table.isLiteral = true + +---@class LuaParser.Ast +local Ast = class.get 'LuaParser.Ast' + +---@private +---@return LuaParser.Node.Table? +function Ast:parseTable() + local pos = self.lexer:consume '{' + if not pos then + return nil + end + + self:skipSpace() + local fields = self:parseTableFields() + self:skipSpace() + + self:assertSymbol '}' + + local table = self:createNode('LuaParser.Node.Table', { + 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 = self:createNode('LuaParser.Node.Integer', { + dummy = true, + value = expi, + start = field.start, + finish = field.start, + parent = field, + }) + end + end + + return table +end + +---@private +---@return LuaParser.Node.TableField[] +function Ast:parseTableFields() + local fields = {} + local wantSep = false + while true do + local token, _, pos = self.lexer:peek() + if not token or token == '}' then + break + end + if token == ',' + or 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) + end + wantSep = true + local field = self:parseTableField() + if field then + fields[#fields+1] = field + else + self:throwMissExp(self:getLastPos()) + break + end + end + self:skipSpace() + end + return fields +end diff --git a/src/parser/ast/unary.lua b/src/parser/ast/unary.lua new file mode 100644 index 0000000..90ae877 --- /dev/null +++ b/src/parser/ast/unary.lua @@ -0,0 +1,80 @@ +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.get 'LuaParser.Ast' + +---@private +---@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, { + symbol = UnaryAlias[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, false, myLevel) + local unary = self:createNode('LuaParser.Node.Unary', { + start = pos, + finish = self:getLastPos(), + op = token, + exp = exp, + }) + if exp then + exp.parent = unary + end + + return unary +end diff --git a/src/parser/ast/var.lua b/src/parser/ast/var.lua new file mode 100644 index 0000000..08e83eb --- /dev/null +++ b/src/parser/ast/var.lua @@ -0,0 +1,76 @@ +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 +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 +local Ast = class.get 'LuaParser.Ast' + +---@private +---@return LuaParser.Node.Var? +function Ast:parseVar() + local var = self:parseID('LuaParser.Node.Var') + if not var then + return nil + end + + local loc = self:getLocal(var.id) + 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 +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() + local pos = self.lexer:consume '...' + 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/src/parser/compile.lua b/src/parser/compile.lua index e09c958..896cd30 100644 --- a/src/parser/compile.lua +++ b/src/parser/compile.lua @@ -1,3989 +1,43 @@ -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' + +require 'parser.ast.ast' + +---@class LuaParser +local LuaParser = class.get 'LuaParser' + +---@alias LuaParser.LuaVersion +---| 'Lua 5.1' +---| 'Lua 5.2' +---| 'Lua 5.3' +---| 'Lua 5.4' + +---@alias LuaParser.NonestandardSymbol +---|'//' | '/**/' +---|'`' +---|'+=' | '-=' | '*=' | '/=' | '%=' | '^=' | '//=' +---|'|=' | '&=' | '<<=' | '>>=' +---|'||' | '&&' | '!' | '!=' +---|'continue', + +---@class LuaParser.CompileOptions +---@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代码 +---@param version? LuaParser.LuaVersion # 默认为 '5.4' +---@param options? LuaParser.CompileOptions +---@return LuaParser.Ast +function LuaParser.compile(code, version, options) + local ast = class.new 'LuaParser.Ast' (code, version, options) + ast.main = ast:parseMain() + + ---@diagnostic disable-next-line: invisible + ast:resolveAllGoto() + ---@diagnostic disable-next-line: invisible + ast:checkAssignConst() + + return ast end diff --git a/src/parser/guide.lua b/src/parser/guide.lua deleted file mode 100644 index e7eb375..0000000 --- a/src/parser/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/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/lexer.lua b/src/parser/lexer.lua new file mode 100644 index 0000000..558f972 --- /dev/null +++ b/src/parser/lexer.lua @@ -0,0 +1,233 @@ +local l = require 'lpeglabel' +local class = require 'class' + +---@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: + + 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'+-*/!#%^&()={}[]|\\\'":;<>,.?~`' + +---@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.Result' (code, self.Parser) + return result +end + +---@alias Lexer.Type +---| 'NL' +---| 'Num' +---| 'Word' +---| 'Symbol' +---| 'Unknown' + +---@class Lexer.Result +local M = class.declare 'Lexer.Result' + +---@param code string +---@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 Lexer.Type[] + self.types = {} -- 每个词的类型 + ---@type integer[] + self.nls = {} -- 每个换行符的字节结束位置(下一行的开始位置) + self.ci = 1 -- 当前词的索引 + for i, res in ipairs(results) do + if i % 3 == 1 then + 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] - 1 + end + end + end +end + +-- 看看当前的词 +---@param next? integer # 默认为0表示当前的词,1表示下一个词,以此类推 +---@return string? +---@return 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] + local pos = self.poses[i] + return token, tp, pos +end + +-- 消耗一个词,返回这个词 +---@param count? integer # 默认为1表示消耗一个词,2表示消耗两个词,以此类推 +---@return string? +---@return 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, pos +end + +-- 消耗一个指定的词,返回消耗掉词的位置 +---@param token string +---@return integer? +function M:consume(token) + local ci = self.ci + if self.tokens[ci] == token then + self.ci = ci + 1 + return self.poses[ci] + end + return nil +end + +-- 消耗一个指定的类型,返回消耗掉词和位置 +---@param tp 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? +---@return integer? +function M:range(offset) + local i = self.ci + (offset or 0) + local token = self.tokens[i] + local pos = self.poses[i] + if not token then + return nil, nil + end + return pos, pos + #token +end + +-- 快进到某个光标位置 +---@param pos integer +function M:fastForward(pos) + for i = self.ci + 1, #self.poses do + if self.poses[i] >= pos then + self.ci = i + return + end + end + self.ci = #self.poses + 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 + + if offset >= nls[#nls] then + return #nls, offset - nls[#nls] + 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 + +-- 设置保存点 +---@return fun() +function M:savePoint() + local ci = self.ci + return function () + self.ci = ci + end +end + +---@class Lexer.API +local API = {} + +---@return Lexer +function API.new() + return class.new 'Lexer' () +end + +return API diff --git a/src/parser/lines.lua b/src/parser/lines.lua deleted file mode 100644 index 964aabf..0000000 --- a/src/parser/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/luadoc.lua b/src/parser/luadoc.lua deleted file mode 100644 index 1a88d38..0000000 --- a/src/parser/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/relabel.lua b/src/parser/relabel.lua deleted file mode 100644 index ac90240..0000000 --- a/src/parser/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/tokens.lua b/src/parser/tokens.lua deleted file mode 100644 index 5f455be..0000000 --- a/src/parser/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/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/Action.lua b/test/ast/Action.lua deleted file mode 100644 index b83511c..0000000 --- a/test/ast/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/Boolean.lua b/test/ast/Boolean.lua deleted file mode 100644 index d3b292e..0000000 --- a/test/ast/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/Comment.lua b/test/ast/Comment.lua deleted file mode 100644 index 81c1c92..0000000 --- a/test/ast/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/Dirty.lua b/test/ast/Dirty.lua deleted file mode 100644 index 373a495..0000000 --- a/test/ast/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/Exp.lua b/test/ast/Exp.lua deleted file mode 100644 index 8ba4a4a..0000000 --- a/test/ast/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/Lua.lua b/test/ast/Lua.lua deleted file mode 100644 index e171b13..0000000 --- a/test/ast/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/LuaDoc.lua b/test/ast/LuaDoc.lua deleted file mode 100644 index 2b13a58..0000000 --- a/test/ast/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/Nil.lua b/test/ast/Nil.lua deleted file mode 100644 index 70744e2..0000000 --- a/test/ast/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/Number.lua b/test/ast/Number.lua deleted file mode 100644 index c956850..0000000 --- a/test/ast/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/String.lua b/test/ast/String.lua deleted file mode 100644 index ac32030..0000000 --- a/test/ast/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/block.lua b/test/ast/block.lua new file mode 100644 index 0000000..d2f5a24 --- /dev/null +++ b/test/ast/block.lua @@ -0,0 +1,440 @@ +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) + 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, + }, + } +} + +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 = 10010, + } + } + } + }, + [3] = { + type = 'Call', + args = { + [1] = { + id = 'x', + loc = { + left = 10010, + } + } + } + }, + } +} + +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', + } + } + } + } + } + } +} + +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, + } + } + } + } + } +} diff --git a/test/ast/boolean.lua b/test/ast/boolean.lua new file mode 100644 index 0000000..48d18e8 --- /dev/null +++ b/test/ast/boolean.lua @@ -0,0 +1,24 @@ +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) + Match(node, expect) + end +end + +TEST [[true]] +{ + left = 0, + finish = 4, + value = true, +} +TEST [[false]] +{ + left = 0, + right = 5, + value = false, +} diff --git a/test/ast/cats.lua b/test/ast/cats.lua new file mode 100644 index 0000000..360f85d --- /dev/null +++ b/test/ast/cats.lua @@ -0,0 +1,503 @@ +local class = require 'class' + +local function TEST(code) + return function (expect) + ---@class LuaParser.Ast + local ast = class.new 'LuaParser.Ast' (code) + local cat = ast:parseCat() + Match(cat, expect) + end +end + +TEST [[ +---@class A +]] +{ + subtype = 'class', + start = 0, + finish = 11, + symbolPos = 3, + value = { + start = 10, + finish = 11, + classID = { + start = 10, + finish = 11, + id = 'A', + }, + } +} + +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, + } + } +} + +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, + } + } +} + +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', + } + }, + } +} + +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', + } + } + } +} + +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', + } + } + } +} + +TEST [[ +---@type fun() +]] +{ + value = { + type = 'CatFunction', + start = 9, + finish = 14, + } +} + +TEST [[ +---@type async fun() +]] +{ + value = { + type = 'CatFunction', + start = 9, + finish = 20, + async = true, + 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' + } + } + } + } +} + +TEST [[ +---@type fun() +---: number +]] +{ + value = { + returns = { + [1] = { + value = { + id = 'number' + }, + } + } + }, +} + +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 [[ +---@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 +]] +{ + subtype = 'unknown', +} diff --git a/test/ast/comment.lua b/test/ast/comment.lua new file mode 100644 index 0000000..ad8af53 --- /dev/null +++ b/test/ast/comment.lua @@ -0,0 +1,47 @@ +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) + Match(node, expect) + end +end + +TEST [[--AAA]] +{ + left = 0, + right = 5, + subtype = 'short', + value = 'AAA', +} + +TEST [[//AAA]] +{ + left = 0, + right = 5, + subtype = 'short', + value = 'AAA', +} + +TEST [===[--[[ +1234 +]]]===] +{ + left = 0, + right = 20002, + subtype = 'long', + value = '\n1234\n', +} + +TEST [===[/* +1234 +*/]===] +{ + left = 0, + right = 20002, + subtype = 'long', + value = '\n1234\n', +} diff --git a/test/ast/exp.lua b/test/ast/exp.lua new file mode 100644 index 0000000..8b2c2b5 --- /dev/null +++ b/test/ast/exp.lua @@ -0,0 +1,584 @@ +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) + 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, + symbolPos = 1, + subtype = 'field', + key = { + start = 2, + finish = 3, + id = 'b', + parent = {}, + }, + last = { + start = 0, + finish = 1, + id = 'a', + parent = {}, + next = { + __class__ = 'LuaParser.Node.Field', + }, + } +} + +TEST 'a.b.c' +{ + start = 0, + finish = 5, + symbolPos = 3, + subtype = 'field', + key = { + start = 4, + finish = 5, + id = 'c', + parent = {}, + }, + last = { + start = 0, + finish = 3, + symbolPos = 1, + 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', + }, + }, + } +} + +TEST 'func()' +{ + start = 0, + finish = 6, + argPos = 4, + node = { + start = 0, + finish = 4, + id = 'func', + parent = { + __class__ = 'LuaParser.Node.Call', + }, + }, + args = {}, +} + +TEST 'a.b.c()' +{ + start = 0, + finish = 7, + argPos = 5, + node = { + start = 0, + finish = 5, + key = {}, + parent = {}, + } +} + +TEST '1 or 2' +{ + start = 0, + finish = 6, + op = 'or', + symbolPos = 2, + exp1 = { + value = 1, + }, + exp2 = { + value = 2, + } +} + +TEST '1 < 2' +{ + start = 0, + finish = 5, + op = '<', + exp1 = { + value = 1, + }, + exp2 = { + value = 2, + } +} + +TEST '- 1' +{ + __class__ = 'LuaParser.Node.Integer', + start = 0, + finish = 3, + value = -1, +} + +TEST '- x' +{ + __class__ = 'LuaParser.Node.Unary', + start = 0, + finish = 3, + op = '-', + exp = { + __class__ = 'LuaParser.Node.Var', + start = 2, + finish = 3, + id = 'x', + } +} + +TEST 'not not true' +{ + start = 0, + finish = 12, + op = 'not', + exp = { + start = 4, + finish = 12, + op = 'not', + exp = { + value = true, + } + } +} + +TEST '1 ^ 2' +{ + start = 0, + finish = 5, + op = '^', + exp1 = { + value = 1, + }, + exp2 = { + value = 2, + } +} +TEST '1 ^ -2' +{ + start = 0, + finish = 6, + op = '^', + exp1 = { + value = 1, + }, + exp2 = { + value = -2, + } +} + +TEST '-1 ^ 2' +{ + start = 0, + finish = 6, + op = '^', + exp1 = { + value = -1, + }, + exp2 = { + value = 2, + } +} + +TEST '1 + 2 + 3' +{ + op = '+', + exp1 = { + op = '+', + exp1 = { + value = 1, + }, + exp2 = { + value = 2, + } + }, + exp2 = { + value = 3, + } +} + +TEST '1 + 2 * 3' +{ + op = '+', + exp1 = { + value = 1, + }, + exp2 = { + op = '*', + exp1 = { + value = 2, + }, + exp2 = { + value = 3, + } + } +} + +TEST '- 1 + 2 * 3' +{ + op = '+', + exp1 = { + value = -1, + }, + exp2 = { + op = '*', + exp1 = { + value = 2, + }, + exp2 = { + value = 3, + } + } +} +TEST '-1 + 2 * 3' +{ + op = '+', + exp1 = { + value = -1, + }, + exp2 = { + op = '*', + exp1 = { + value = 2, + }, + exp2 = { + value = 3, + } + } +} + +TEST "x and y == 'unary' and z" +{ + start = 0, + finish = 24, + op = 'and', + exp1 = { + op = 'and', + exp1 = { + id = 'x' + }, + exp2 = { + op = '==', + exp1 = { + id = 'y' + }, + exp2 = { + value = 'unary' + }, + }, + }, + exp2 = { + id = 'z' + }, +} + +TEST "x and y or '' .. z" +{ + op = 'or', + exp1 = { + op = 'and', + exp1 = { + id = 'x', + }, + exp2 = { + id = 'y', + }, + }, + exp2 = { + op = '..', + exp1 = { + value = '', + }, + exp2 = { + id = 'z', + }, + }, +} + +-- 幂运算从右向左连接 +TEST '1 ^ 2 ^ 3' +{ + op = '^', + exp1 = { + value = 1, + }, + exp2 = { + op = '^', + exp1 = { + value = 2, + }, + exp2 = { + value = 3, + }, + }, +} + +-- 连接运算从右向左连接 +TEST '1 .. 2 .. 3' +{ + op = '..', + exp1 = { + value = 1, + }, + exp2 = { + op = '..', + exp1 = { + value = 2, + }, + exp2 = { + value = 3, + }, + }, +} + +TEST '1 + - - 1' +{ + op = '+', + exp1 = { + value = 1, + }, + exp2 = { + op = '-', + exp = { + value = -1 + } + } +} + +TEST '(1)' +{ + start = 0, + finish = 3, + exp = { + start = 1, + finish = 2, + value = 1, + }, +} + +TEST '(1 + 2)' +{ + exp = { + op = '+', + exp1 = { + value = 1, + }, + exp2 = { + value = 2, + } + } +} + +TEST 'func(1)' +{ + start = 0, + finish = 7, + argPos = 4, + node = { + __class__ = 'LuaParser.Node.Var', + id = 'func', + }, + args = { + [1] = { + value = 1 + }, + }, +} + +TEST 'func(1, 2)' +{ + node = { + id = 'func', + }, + args = { + [1] = { + value = 1, + }, + [2] = { + value = 2, + }, + } +} + +TEST 'func(...)' +{ + args = { + [1] = { + __class__ = 'LuaParser.Node.Varargs', + }, + }, +} + +TEST 'func(1, ...)' +{ + args = { + [1] = { + value = 1, + }, + [2] = { + __class__ = 'LuaParser.Node.Varargs', + }, + }, +} + +TEST 'func ""' +{ + args = { + [1] = { + value = '' + }, + }, +} + +TEST 'func {}' +{ + args = { + [1] = { + __class__ = 'LuaParser.Node.Table', + } + }, +} + +TEST 'table[1]' +{ + subtype = 'index', + symbolPos = 5, + symbolPos2 = 7, + key = { + value = 1, + } +} + +TEST 'table[[1]]' +{ + start = 0, + finish = 10, + node = { + id = 'table' + }, + args = { + [1] = { + value = '1' + }, + }, +} + +TEST 'get_point().x' +{ + subtype = 'field', + key = { + id = 'x' + }, + last = { + __class__ = 'LuaParser.Node.Call', + node = { + id = 'get_point', + }, + } +} + +TEST 'obj:remove()' +{ + args = {}, + node = { + subtype = 'method', + key = { + id = 'remove' + }, + last = { + id = 'obj' + } + } +} + +TEST '(...)[1]' +{ + subtype = 'index', + key = { + value = 1, + }, + last = { + exp = { + __class__ = 'LuaParser.Node.Varargs', + } + } +} + +TEST 'function () end' +{ + start = 0, + finish = 15, + symbolPos1 = 9, + symbolPos2 = 10, + symbolPos3 = 12, +} + +TEST 'function (...) end' +{ + params = { + [1] = { + id = '...' + } + } +} + +TEST 'function (a, ...) end' +{ + params = { + [1] = { + id = 'a' + }, + [2] = { + id = '...' + } + } +} + +TEST 'a ^ - b' +{ + op = '^', + exp1 = { + id = 'a' + }, + exp2 = { + op = '-', + exp = { + id = 'b' + } + } +} diff --git a/test/ast/init.lua b/test/ast/init.lua index fa4eed8..81433f3 100644 --- a/test/ast/init.lua +++ b/test/ast/init.lua @@ -1,230 +1,32 @@ -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 +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) + return 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 + if type(v) == 'table' then + Match(result[k], v) + else + assert(result[k] == v) 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' +require 'parser' + +require 'test.ast.nil' +require 'test.ast.boolean' +require 'test.ast.string' +require 'test.ast.number' +require 'test.ast.comment' +require 'test.ast.local' +require 'test.ast.table' +require 'test.ast.exp' +require 'test.ast.state' +require 'test.ast.block' +require 'test.ast.main' +require 'test.ast.cats' diff --git a/test/ast/local.lua b/test/ast/local.lua new file mode 100644 index 0000000..2d78ed1 --- /dev/null +++ b/test/ast/local.lua @@ -0,0 +1,153 @@ +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) + Match(node, expect) + end +end + +TEST [[local x]] +{ + left = 0, + finish = 7, + vars = { + [1] = { + start = 6, + finish = 7, + id = 'x', + index = 1, + }, + }, +} + +TEST [[local x, y, z]] +{ + left = 0, + finish = 13, + vars = { + [1] = { + start = 6, + finish = 7, + id = 'x', + index = 1, + }, + [2] = { + start = 9, + finish = 10, + id = 'y', + index = 2, + }, + [3] = { + start = 12, + finish = 13, + id = 'z', + index = 3, + }, + }, +} + +TEST [[local x = 1]] +{ + left = 0, + finish = 11, + vars = { + [1] = { + start = 6, + finish = 7, + id = 'x', + value = { + start = 10, + finish = 11, + value = 1, + } + }, + }, + values = { + [1] = { + start = 10, + finish = 11, + value = 1, + }, + } +} + +TEST [[local x, y = 1, 2, 3]] +{ + left = 0, + finish = 20, + vars = { + [1] = { + start = 6, + finish = 7, + id = 'x', + value = { + start = 13, + finish = 14, + value = 1, + } + }, + [2] = { + start = 9, + finish = 10, + id = 'y', + value = { + start = 16, + finish = 17, + value = 2, + } + }, + }, + values = { + [1] = { + start = 13, + finish = 14, + value = 1, + }, + [2] = { + start = 16, + finish = 17, + value = 2, + }, + [3] = { + start = 19, + finish = 20, + value = 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', + } + } + }, + }, +} diff --git a/test/ast/main.lua b/test/ast/main.lua new file mode 100644 index 0000000..0bc5493 --- /dev/null +++ b/test/ast/main.lua @@ -0,0 +1,301 @@ +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 + } + } + } + } + } + } + } + } +} + +TEST [[ +x = 1 +local _ENV +x = 1 +]] +{ + childs = { + [1] = { + exps = { + [1] = { + loc = NIL, + env = { + left = 0, + } + } + } + }, + [3] = { + exps = { + [1] = { + loc = NIL, + env = { + left = 10006, + } + } + } + } + } +} + +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, + } + } + } + } + } + } + } +} diff --git a/test/ast/nil.lua b/test/ast/nil.lua new file mode 100644 index 0000000..e2008cd --- /dev/null +++ b/test/ast/nil.lua @@ -0,0 +1,22 @@ +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) + Match(node, expect) + end +end + +TEST [[nil]] +{ + left = 0, + right = 3, +} +TEST [[ nil]] +{ + left = 3, + right = 6, +} diff --git a/test/ast/number.lua b/test/ast/number.lua new file mode 100644 index 0000000..3d66ac7 --- /dev/null +++ b/test/ast/number.lua @@ -0,0 +1,150 @@ +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) + Match(node, expect) + end +end + +TEST '345' +{ + left = 0, + right = 3, + value = 345, + numBase = 10, + toString= '345', +} +TEST '345.0' +{ + left = 0, + right = 5, + value = 0x1.59p+8, + numBase = 10, + toString= '345.0', +} +TEST '0xff' +{ + left = 0, + right = 4, + value = 255, + numBase = 16, + toString= '255', +} +TEST '314.16e-2' +{ + left = 0, + right = 9, + value = 3.1416, + numBase = 10, + toString= '3.1416', +} +TEST '0.31416E1' +{ + left = 0, + right = 9, + value = 0x1.921ff2e48e8a7p+1, + numBase = 10, + toString= '3.1416', +} +TEST '.31416E1' +{ + left = 0, + right = 8, + value = 0x1.921ff2e48e8a7p+1, + numBase = 10, + toString= '3.1416', +} +TEST '34e1' +{ + left = 0, + right = 4, + value = 0x1.54p+8, + numBase = 10, + toString= '340.0', +} +TEST '0x0.1E' +{ + left = 0, + right = 6, + value = 0x1.ep-4, + numBase = 16, + toString= '0.1171875', +} +TEST '0xA23p-4' +{ + left = 0, + right = 8, + value = 0x1.446p+7, + numBase = 16, + toString= '162.1875' +} +TEST '0X1.921FB54442D18P+1' +{ + left = 0, + right = 20, + value = 0x1.921fb54442d18p+1, + numBase = 16, + toString= '3.1415926536' +} +TEST '-345' +{ + left = 0, + right = 4, + value = -345, + numBase = 10, + toString= '-345', +} +TEST '0b110110' +{ + left = 0, + right = 8, + value = 54, + numBase = 2, + toString= '54', +} +TEST '123ll' +{ + left = 0, + right = 5, + value = 123, + numBase = 10, + toString= '123LL', +} +TEST '123ull' +{ + left = 0, + right = 6, + value = 123, + numBase = 10, + toString= '123ULL', +} +TEST '123llu' +{ + left = 0, + right = 6, + value = 123, + numBase = 10, + toString= '123ULL', +} +TEST '123i' +{ + left = 0, + right = 4, + value = 0, + valuei = 123, + numBase = 10, + toString= '0+123i', +} +TEST '123.45i' +{ + left = 0, + right = 7, + value = 0, + valuei = 123.45, + numBase = 10, + toString= '0+123.45i', +} diff --git a/test/ast/state.lua b/test/ast/state.lua new file mode 100644 index 0000000..a409573 --- /dev/null +++ b/test/ast/state.lua @@ -0,0 +1,520 @@ +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) + Match(node, expect) + end +end + +TEST [[local x, y = 1, 2, 3]] +{ + type = 'LocalDef', + start = 0, + finish = 20, + symbolPos = 11, + vars = { + [1] = { + id = 'x', + value = { + value = 1, + } + }, + [2] = { + id = 'y', + value = { + value = 2, + } + }, + }, + values = { + [1] = { + value = 1, + }, + [2] = { + value = 2, + }, + [3] = { + value = 3, + }, + } +} + +TEST [[x = 1]] +{ + type = 'Assign', + start = 0, + finish = 5, + symbolPos = 2, + exps = { + [1] = { + start = 0, + finish = 1, + id = 'x', + value = { + value = 1, + } + }, + }, + values = { + [1] = { + value = 1, + }, + } +} + +TEST [[x.x, y.y = 1, 2, 3]] +{ + type = 'Assign', + symbolPos = 9, + 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()]] +{ + type = 'Call', +} + +TEST [[x.y().z = 1]] +{ + type = 'Assign', + symbolPos = 8, + exps = { + [1] = { + subtype = 'field', + last = { + type = 'Call', + }, + key = { + id = 'z', + }, + value = { + value = 1, + }, + }, + }, + values = { + [1] = { + value = 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 [[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', + start = 0, + finish = 14, + name = { + start = 3, + finish = 11, + id = 'continue', + } +} + +TEST [[goto continue]] +{ + type = 'Goto', + start = 0, + finish = 13, + name = { + start = 5, + finish = 13, + id = 'continue', + } +} + +TEST [[ +do +end +]] +{ + type = 'Do', + left = 0, + right = 10003, + symbolPos = 3, +} + +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', + }, + } +} + +TEST [[break]] +{ + type = '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, + } + } +} + +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, +} + +TEST [[ +while true do +end +]] +{ + type = 'While', + left = 0, + right = 10003, + condition = { + value = true, + }, + symbolPos1 = 11, + symbolPos2 = 14, +} + +TEST [[ +repeat +until false +]] +{ + type = 'Repeat', + left = 0, + right = 10011, + symbolPos = 7, + condition = { + value = false, + }, +} + +TEST [[ +function f(x, y) +end +]] +{ + type = 'Function', + name = { + type = 'Var', + 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' + } + } +} + +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' + } + } +} diff --git a/test/ast/string.lua b/test/ast/string.lua new file mode 100644 index 0000000..51dd433 --- /dev/null +++ b/test/ast/string.lua @@ -0,0 +1,245 @@ +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) + Match(node, expect) + end +end + +TEST [['123']] +{ + left = 0, + right = 5, + value = "123", + quo = "'", +} +TEST [['123\'']] +{ + left = 0, + right = 7, + escs = { + [1] = 4, + [2] = 6, + [3] = "normal", + }, + value = "123'", + quo = "'", +} +TEST [['123\z + 345']] +{ + left = 0, + right = 10008, + escs = { + [1] = 4, + [2] = 6, + [3] = "normal", + }, + value = "123345", + quo = "'", +} + +TEST [['123\ +345']] +{ + left = 0, + right = 10004, + escs = { + [1] = 4, + [2] = 5, + [3] = "normal", + }, + value = "123\ +345", + quo = "'", +} +TEST [===[[[123]]]===] +{ + left = 0, + right = 7, + value = "123", + quo = "[[", +} +TEST [===[[[123 +345]]]===] +{ + left = 0, + right = 10005, + value = "123\ +345", + quo = "[[", +} +TEST [['alo\n123"']] +{ + left = 0, + right = 11, + escs = { + [1] = 4, + [2] = 6, + [3] = "normal", + }, + value = "alo\ +123\"", + quo = "'", +} +TEST [['\97lo\10\04923"']] +{ + 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']] +{ + left = 0, + right = 6, + escs = { + [1] = 1, + [2] = 5, + [3] = "byte", + }, + value = "\xff", + quo = "'", +} +TEST [['\x1A']] +{ + left = 0, + right = 6, + escs = { + [1] = 1, + [2] = 5, + [3] = "byte", + }, + value = "\26", + quo = "'", +} +TEST [['\32']] +{ + left = 0, + right = 5, + escs = { + [1] = 1, + [2] = 4, + [3] = "byte", + }, + value = " ", + quo = "'", +} +TEST [['\123']] +{ + left = 0, + right = 6, + escs = { + [1] = 1, + [2] = 5, + [3] = "byte", + }, + value = "{", + quo = "'", +} +TEST [['\0123']] +{ + left = 0, + right = 7, + escs = { + [1] = 1, + [2] = 5, + [3] = "byte", + }, + value = "\0123", + quo = "'", +} +TEST [['\492']] +{ + left = 0, + right = 6, + escs = { + [1] = 1, + [2] = 5, + [3] = "err", + }, + value = "", + quo = "'", +} +TEST [['\u{3b1}']] +{ + left = 0, + right = 9, + escs = { + [1] = 1, + [2] = 8, + [3] = "unicode", + }, + value = "α", + quo = "'", +} +TEST [['\u{0}']] +{ + left = 0, + right = 7, + escs = { + [1] = 1, + [2] = 6, + [3] = "unicode", + }, + value = "\0", + quo = "'", +} +TEST [['\u{ffffff}']] +{ + left = 0, + right = 12, + escs = { + [1] = 1, + [2] = 11, + [3] = "unicode", + }, + value = "\u{ffffff}", + quo = "'", +} +TEST [=[[[ +abcdef]]]=] +{ + left = 0, + right = 10008, + value = "abcdef", + quo = "[[", +} +TEST [['aaa]] +{ + left = 0, + right = 4, + value = "aaa", + quo = "'", +} +TEST [['aaa +]] +{ + left = 0, + right = 4, + value = "aaa", + quo = "'", +} +TEST [[`12345`]] +{ + left = 0, + right = 7, + value = "12345", + quo = '"', +} diff --git a/test/ast/table.lua b/test/ast/table.lua new file mode 100644 index 0000000..daf3801 --- /dev/null +++ b/test/ast/table.lua @@ -0,0 +1,338 @@ +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) + Match(node, expect) + end +end + +TEST '{}' +{ + start = 0, + finish = 2, +} + +TEST '{...}' +{ + start = 0, + finish = 5, + fields = { + [1] = { + subtype = 'exp', + start = 1, + finish = 4, + key = { + dummy = true, + value = 1, + }, + value = { + start = 1, + finish = 4, + }, + } + } +} +TEST '{1, 2, 3}' +{ + start = 0, + finish = 9, + fields = { + [1] = { + subtype = 'exp', + start = 1, + finish = 2, + key = { + dummy = true, + value = 1, + }, + value = { + value = 1, + }, + }, + [2] = { + subtype = 'exp', + start = 4, + finish = 5, + key = { + dummy = true, + value = 2, + }, + value = { + value = 2, + }, + }, + [3] = { + subtype = 'exp', + start = 7, + finish = 8, + key = { + dummy = true, + value = 3, + }, + value = { + value = 3, + }, + }, + } +} + +TEST '{x = 1, y = 2}' +{ + start = 0, + finish = 14, + fields = { + [1] = { + subtype = 'field', + start = 1, + finish = 6, + key = { + id = 'x', + }, + value = { + value = 1, + }, + }, + [2] = { + subtype = 'field', + start = 8, + finish = 13, + key = { + id = 'y', + }, + value = { + value = 2, + }, + }, + } +} + +TEST '{["x"] = 1, ["y"] = 2}' +{ + start = 0, + finish = 22, + fields = { + [1] = { + subtype = 'index', + symbolPos = 1, + symbolPos2 = 5, + key = { + __class__ = 'LuaParser.Node.String', + value = 'x', + }, + value = { + value = 1, + } + }, + [2] = { + subtype = 'index', + symbolPos = 12, + symbolPos2 = 16, + key = { + __class__ = 'LuaParser.Node.String', + value = 'y', + }, + value = { + value = 2, + } + }, + } +} + +TEST '{[x] = 1, [y] = 2}' +{ + start = 0, + finish = 18, + fields = { + [1] = { + subtype = 'index', + key = { + __class__ = 'LuaParser.Node.Var', + id = 'x', + } + }, + [2] = { + subtype = 'index', + key = { + __class__ = 'LuaParser.Node.Var', + id = 'y', + } + }, + } +} + +TEST '{x = 1, y = 2, 3}' +{ + start = 0, + finish = 17, + fields = { + [1] = { + subtype = 'field' + }, + [2] = { + subtype = 'field' + }, + [3] = { + subtype = 'exp', + key = { + dummy = true, + value = 1, + } + }, + } +} + +TEST '{{}}' +{ + start = 0, + finish = 4, + fields = { + [1] = { + subtype = 'exp', + key = { + dummy = true, + value = 1, + }, + value = { + __class__ = 'LuaParser.Node.Table', + start = 1, + finish = 3, + } + } + } +} + +TEST '{ a = { b = { c = {} } } }' +{ + 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 '{{}, {}, {{}, {}}}' +{ + fields = { + [1] = { + subtype = 'exp', + }, + [2] = { + subtype = 'exp', + }, + [3] = { + subtype = 'exp', + value = { + fields = { + [1] = { + subtype = 'exp', + }, + [2] = { + subtype = 'exp', + }, + } + } + } + } +} + +TEST '{1, 2, 3,}' +{ + fields = { + [1] = { + subtype = 'exp', + key = { + dummy = true, + value = 1, + }, + value = { + value = 1, + } + }, + [2] = { + subtype = 'exp', + key = { + dummy = true, + value = 2, + }, + value = { + value = 2, + } + }, + [3] = { + subtype = 'exp', + key = { + dummy = true, + value = 3, + }, + value = { + value = 3, + } + }, + } +} + +TEST [=[ +{ +[[]] +}]=] +{ + fields = { + [1] = { + subtype = 'exp', + key = { + dummy = true, + value = 1, + }, + value = { + __class__ = 'LuaParser.Node.String', + value = '', + } + } + } +} + +TEST [[ +{ + [xxx] +} +]] +{ + fields = { + [1] = { + subtype = 'index', + key = { + __class__ = 'LuaParser.Node.Var', + id = 'xxx', + }, + } + } +} 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/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 a044c77..d7a771f 100644 --- a/test/grammar.lua +++ b/test/grammar.lua @@ -1,9 +1,24 @@ -local parser = require 'parser' +local class = require 'class' -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 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 + if mode == 'Dirty' then + parser = 'parseMain' + end + local node = ast[parser](ast) + assert(node) + removeErrors(ast.errors, 'UNEXPECT_DOTS') + if #ast.errors > 0 and mode ~= 'Dirty' then error(([[ [%s]测试失败: %s @@ -12,7 +27,7 @@ local function check_str(str, name, mode) ]]):format( name, ('='):rep(30), - str, + code, ('='):rep(30) )) end @@ -21,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 @@ -40,20 +55,6 @@ check 'Comment' [===[-- [[Abc]]a]===], } -check 'Sp' -{ -'', -' ', -' ', -'\t', -'--', -'--123', -' \t', -[===[--[[123 -123 -123]]]===], -} - check 'Nil' { 'nil', @@ -127,7 +128,7 @@ check 'Number' '9.' } -check 'Name' +check 'ID' { '_', 'And', @@ -211,7 +212,7 @@ check 'Exp' '{1, 2, 3,}', } -check 'Action' +check 'State' { 'x = 1', 'x, y, z = 1, 2, 3', @@ -302,7 +303,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 feecb3f..a8d1787 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,18 +11,14 @@ 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 local function main() - --collectgarbage 'stop' unitTest 'ast' unitTest 'grammar' - --unitTest 'lines' - unitTest 'grammar_check' unitTest 'syntax_check' - --unitTest 'guide' unitTest 'perform' print('测试完成') 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 diff --git a/test/syntax_check.lua b/test/syntax_check.lua index 26fb115..f4f37bf 100644 --- a/test/syntax_check.lua +++ b/test/syntax_check.lua @@ -1,134 +1,1519 @@ local parser = require 'parser' +local catch = require 'test.catch' +local util = require 'utility' -local EXISTS = {} +---@param script string +---@return fun(expect: table) +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] + if not expect.type 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.code == expect.type) + assert(first.left == target[1]) + assert(first.right == target[2]) + assert(util.equal(first.extra, expect.extra)) + end +end + + +TEST[[ +local +]] +{ + type = 'MISS_NAME', +} + +TEST[[ +local +]] +{ + 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', +} + +TEST[[ +f(1,) +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +f(1 1) +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = ',', + }, +} + +TEST[[ +().x() +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +print(x +]] +{ + type = 'MISS_SYMBOL', + extra = { + symbol = ')', + } +} + +TEST[[ +x.() +]] +{ + type = 'MISS_NAME', +} + +TEST[[ +x:() +]] +{ + type = 'MISS_NAME', +} + +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', + extra = { + start = 12, + finish = 12, + } +} + +TEST[[ +function f:() end +]] +{ + type = 'MISS_NAME', +} + +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 = 'UNEXPECT_SYMBOL', +} + +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', + extra = { + start = 19, + finish = 19, + } +} + +TEST[[ +for i =, 2 do +end +]] +{ + multi = 1, + type = 'MISS_EXP', +} + +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 = 1, + type = 'MISS_EXP' +} + +TEST[[ +for i = do +end +]] +{ + type = 'MISS_EXP' +} + +TEST[[ +for i = 1, 2, do +end +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +for i = 1, 2 3 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', + extra = { + start = 15, + finish = 15, + } +} + +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', + extra = { + start = 13, + finish = 13, + } +} + +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', + extra = { + start = 12, + finish = 12, + } +} + +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[[ +if true then +else + true then +end +]] +{ + type = 'BLOCK_AFTER_ELSE', +} + +TEST[[ +xxxx +]] +{ + type = 'ERR_COMMENT_PREFIX', +} + +TEST[[ + +adadasd +*/ +]] +{ + type = 'ERR_C_LONG_COMMENT', +} + +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 = '', +} +]] +{} + +TEST[[ +return { + _VERSION == '', +} +]] +{} + +-- 以下测试来自 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', +} + +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', + extra = { + start = 41, + finish = 41, + } +} + +TEST[[ +a = 3 / / 2 +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +b = 1 1 +]] +{ + type = 'ERR_NONSTANDARD_SYMBOL', + extra = { + symbol = 'and', + } +} + +TEST[[ +b = 1 <> 0 +]] +{ + type = 'MISS_EXP', +} + +TEST[[ +b = 1 < < 0 +]] +{ + type = 'MISS_EXP', +} + +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', + extra = { + start = 9, + finish = 9, + } +} + +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', + extra = { + start = 14, + finish = 14, + } +} + +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', +} -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 +TEST[[ +local function a() end +]] +{ + type = 'UNEXPECT_LFUNC_NAME' +} -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 +TEST [[ +f() 1 +]] +{ + multi = 1, + type = 'UNKNOWN_SYMBOL', +} -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 +TEST[[ +xx:: +]] +{ + type = 'UNSUPPORT_SYMBOL', + version = 'Lua 5.1', +} ----@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 +TEST [[ + X +]] +{ + multi = 1, + type = 'UNSUPPORT_SYMBOL', + version = 'Lua 5.1', +} -local Version +TEST[[ +local goto = 1 +]] +{ + version = 'Lua 5.1', +} -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 +TEST[[ +local x = '' +]] +{ + type = 'ERR_ESC', + version ='Lua 5.1', +} -function TestWith(version) - return function (script) - return function (expect) - Version = version - TEST(script)(expect) - Version = 'Lua 5.4' - end - end +TEST[[ +local x = '' +]] +{ + type = 'ERR_ESC', + version = 'Lua 5.1', +} + +TEST[[ +local x = 1 2 +]] +{ + type = 'UNSUPPORT_SYMBOL', + version = 'Lua 5.2', +} + +TEST[[ +local x = 1 >!> 2 +]] +{ + type = 'UNSUPPORT_SYMBOL', + version = 'Lua 5.2', +} + +TEST[[ +local x = '' +]] +{ + type = 'ERR_ESC', + version = 'Lua 5.2', +} + +TEST[[ +while true do + break + x = 1 end +]] +{ + version = 'Lua 5.1', +} -TEST [[ -local = 1 +TEST[[ +local x !> = 1 ]] { - type = 'KEYWORD' + type = 'UNSUPPORT_SYMBOL', + version = 'Lua 5.3' +} + +TEST[[ +local x = 1 +]] +{} + +TEST[[ +s = '\u{1FFFFF}' +]] +{} + +TEST[[ +s = '\u{}' +]] +{ + type = 'UTF8_MAX', + extra = { + min = '00000000', + max = '7FFFFFFF', + } +} + +TEST[[ +x = 42 +]] +{ + type = 'UNSUPPORT_SYMBOL', +} + +TEST[[ +x = +]] +{ + type = 'UNSUPPORT_SYMBOL', +} + +TEST[[ +x = 12.5 +]] +{ + type = 'UNSUPPORT_SYMBOL', +} + +TEST[[ +x = 1.23 +]] +{ + type = 'MALFORMED_NUMBER', +} + +TEST[[ +x = 42LL +x = 42ULl +x = 0x2aLL +x = 0x2All +x = 12.5i +x = 1I +x = 18446744073709551615ULL +x = 0b11011 +]] +{ + optional = { + jit = true, + } +} + +TEST[[ +x = 1.23 +]] +{ + type = 'MALFORMED_NUMBER', +} + +TEST[[ +x = 0b1 +]] +{ + type = 'MALFORMED_NUMBER', + optional = { + jit = true, + } } TEST [[ @@ -164,35 +1549,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[[ @@ -242,13 +1627,13 @@ TEST[[ ::label:: goto label ]] -(nil) +{} TEST[[ goto label ::label:: ]] -(nil) +{} TEST[[ do @@ -256,7 +1641,7 @@ do end ::label:: ]] -(nil) +{} TEST[[ ::label:: @@ -264,42 +1649,13 @@ do goto label end ]] -(nil) - -TEST[[ -goto label -local x = 1 -x = 2 -::label:: -]] -(nil) - -TEST[[ -local x = 1 -goto label -x = 2 -::label:: -print(x) -]] -(nil) - -TEST[[ -local x = 1 -::label:: -print(x) -local x -goto label -]] -(nil) +{} TEST[[ goto ]] { type = 'NO_VISIBLE_LABEL', - info = { - label = 'label', - } } TEST[[ @@ -308,9 +1664,6 @@ do do do goto end end end ]] { type = 'NO_VISIBLE_LABEL', - info = { - label = 'label', - } } TEST[[ @@ -321,56 +1674,38 @@ end ]] { type = 'NO_VISIBLE_LABEL', - info = { - label = 'label', - } } TEST[[ goto local x = 1 ::label:: -x = 2 ]] { type = 'JUMP_LOCAL_SCOPE', - info = { + 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', - info = { - loc = 'x', - }, - relative = { - { - start = 26, - finish = 30, - }, - { - start = 18, - finish = 18, - }, - } -} +{} + +TEST[[ +local x +::label:: +print(x) +local x +goto label +]] +{} TEST[[ ::label:: @@ -379,15 +1714,12 @@ TEST[[ ]] { type = 'REDEFINED_LABEL', - related = { - { - start = 3, - finish = 7, - }, + extra = { + start = 2, + finish = 7, } } -Version = 'Lua 5.4' TEST[[ ::label:: ::other_label:: @@ -397,11 +1729,9 @@ end ]] { type = 'REDEFINED_LABEL', - related = { - { - start = 3, - finish = 7, - }, + extra = { + start = 2, + finish = 7, } } @@ -411,9 +1741,8 @@ if true then end ::label:: ]] -(nil) +{} -Version = 'Lua 5.3' TEST[[ ::label:: ::other_label:: @@ -421,7 +1750,9 @@ if true then ::label:: end ]] -(nil) +{ + version = 'Lua 5.3', +} TEST[[ if true then @@ -429,9 +1760,10 @@ if true then end ::label:: ]] -(nil) +{ + version = 'Lua 5.3', +} -Version = 'Lua 5.4' TEST[[ local x = 1 = 2 @@ -512,7 +1844,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', @@ -523,12 +1855,12 @@ 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 -for in _ do end +for in _ do end -- use 4 + X local variables ]] { type = 'LOCAL_LIMIT', @@ -539,16 +1871,15 @@ 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 -for in _ do end +for in _ do end -- use 3 + X local variables ]] { + version = 'Lua 5.1', type = 'LOCAL_LIMIT', } @@ -557,14 +1888,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 @@ -575,8 +1908,6 @@ local = nil type = 'LOCAL_LIMIT', } -Version = 'Lua 5.4' - TEST [[ local x =!> 1 ]] @@ -600,27 +1931,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 [[ @@ -643,6 +1975,7 @@ f ]] { + version = 'Lua 5.1', type = 'AMBIGUOUS_SYNTAX', } @@ -651,6 +1984,7 @@ f:xx ]] { + version = 'Lua 5.1', type = 'AMBIGUOUS_SYNTAX', } @@ -660,6 +1994,7 @@ f .x = 1 ]] { + version = 'Lua 5.1', type = 'AMBIGUOUS_SYNTAX', } @@ -669,6 +2004,7 @@ print [[ ]] ]===] { + version = 'Lua 5.1', type = 'NESTING_LONG_MARK', } @@ -678,6 +2014,7 @@ print [[ ]] ]===] { + version = 'Lua 5.1', type = 'NESTING_LONG_MARK', } @@ -686,52 +2023,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()) @@ -765,47 +2105,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) +{} -TestWith 'LuaJIT' [[ +TEST [[ goto LABEL ::LABEL:: ]] -(nil) +{ + version = 'Lua 5.1', + optional = { + jit = true, + } +} -TestWith 'LuaJIT' [[ +TEST [[ local goto = 1 ]] -(nil) +{ + version = 'Lua 5.1', + optional = { + jit = true, + } +} -TestWith 'LuaJIT' [[ +TEST [[ local goto]] -(nil) +{ + version = 'Lua 5.1', + optional = { + jit = true, + } +} -TestWith 'LuaJIT' [[ +TEST [[ f(1, goto, 2) ]] -(nil) +{ + version = 'Lua 5.1', + optional = { + jit = true, + } +} -TestWith 'LuaJIT' [[ +TEST [[ local function f(x, goto, y) end ]] -(nil) +{ + version = 'Lua 5.1', + optional = { + jit = true, + } +}