local error = error local type = type local next = next local tostring = tostring _ENV = nil local m = {} local blockTypes = { ['while'] = true, ['in'] = true, ['loop'] = true, ['repeat'] = true, ['do'] = true, ['function'] = true, ['ifblock'] = true, ['elseblock'] = true, ['elseifblock'] = true, ['main'] = true, } local breakBlockTypes = { ['while'] = true, ['in'] = true, ['loop'] = true, ['repeat'] = true, } m.childMap = { ['main'] = {'#'}, ['repeat'] = {'#', 'filter'}, ['while'] = {'filter', '#'}, ['in'] = {'keys', '#'}, ['loop'] = {'loc', 'max', 'step', '#'}, ['if'] = {'#'}, ['ifblock'] = {'filter', '#'}, ['elseifblock'] = {'filter', '#'}, ['elseblock'] = {'#'}, ['setfield'] = {'node', 'field', 'value'}, ['setglobal'] = {'value'}, ['local'] = {'attrs', 'value'}, ['setlocal'] = {'value'}, ['return'] = {'#'}, ['do'] = {'#'}, ['select'] = {'vararg'}, ['table'] = {'#'}, ['tableindex'] = {'index', 'value'}, ['tablefield'] = {'field', 'value'}, ['function'] = {'args', '#'}, ['funcargs'] = {'#'}, ['setmethod'] = {'node', 'method', 'value'}, ['getmethod'] = {'node', 'method'}, ['setindex'] = {'node', 'index', 'value'}, ['getindex'] = {'node', 'index'}, ['paren'] = {'exp'}, ['call'] = {'node', 'args'}, ['callargs'] = {'#'}, ['getfield'] = {'node', 'field'}, ['list'] = {'#'}, ['binary'] = {1, 2}, ['unary'] = {1} } m.actionMap = { ['main'] = {'#'}, ['repeat'] = {'#'}, ['while'] = {'#'}, ['in'] = {'#'}, ['loop'] = {'#'}, ['if'] = {'#'}, ['ifblock'] = {'#'}, ['elseifblock'] = {'#'}, ['elseblock'] = {'#'}, ['do'] = {'#'}, ['function'] = {'#'}, ['funcargs'] = {'#'}, } --- 是否是字面量 function m.isLiteral(obj) local tp = obj.type return tp == 'nil' or tp == 'boolean' or tp == 'string' or tp == 'number' or tp == 'table' end --- 获取字面量 function m.getLiteral(obj) local tp = obj.type if tp == 'boolean' then return obj[1] elseif tp == 'string' then return obj[1] elseif tp == 'number' then return obj[1] end return nil end --- 寻找父函数 function m.getParentFunction(obj) for _ = 1, 1000 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 --- 寻找所在区块 function m.getBlock(obj) for _ = 1, 1000 do if not obj then return nil end local tp = obj.type if blockTypes[tp] then return obj end obj = obj.parent end error('guide.getBlock overstack') end --- 寻找所在父区块 function m.getParentBlock(obj) for _ = 1, 1000 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的父区块 function m.getBreakBlock(obj) for _ = 1, 1000 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 --- 寻找根区块 function m.getRoot(obj) for _ = 1, 1000 do local parent = obj.parent if not parent then return obj end obj = parent end error('guide.getRoot overstack') end --- 寻找函数的不定参数,返回不定参在第几个参数上,以及该参数对象。 --- 如果函数是主函数,则返回`0, nil`。 ---@return table ---@return integer function m.getFunctionVarArgs(func) if func.type == 'main' then return 0, nil end if func.type ~= 'function' then return nil, nil end local args = func.args if not args then return nil, nil end for i = 1, #args do local arg = args[i] if arg.type == '...' then return i, arg end end return nil, nil end --- 获取指定区块中可见的局部变量 ---@param block table ---@param name string {comment = '变量名'} ---@param pos integer {comment = '可见位置'} function m.getLocal(block, name, pos) block = m.getBlock(block) for _ = 1, 1000 do if not block then return nil end local locals = block.locals local res if not locals then goto CONTINUE end for i = 1, #locals do local loc = locals[i] 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, res end ::CONTINUE:: block = m.getParentBlock(block) end error('guide.getLocal overstack') end --- 获取指定区块中可见的标签 ---@param block table ---@param name string {comment = '标签名'} function m.getLabel(block, name) block = m.getBlock(block) for _ = 1, 1000 do if not block then return nil end local labels = block.labels if labels then local label = labels[name] if label then return label end end if block.type == 'function' then return nil end block = m.getParentBlock(block) end error('guide.getLocal overstack') end --- 判断source是否包含offset function m.isContain(source, offset) return source.start <= offset and source.finish >= offset - 1 end --- 判断offset在source的影响范围内 --- --- 主要针对赋值等语句时,key包含value function m.isInRange(source, offset) return (source.vstart or source.start) <= offset and (source.range or source.finish) >= offset - 1 end --- 添加child function m.addChilds(list, obj, map) local keys = map[obj.type] if keys then for i = 1, #keys do local key = keys[i] if key == '#' then for i = 1, #obj do list[#list+1] = obj[i] end else list[#list+1] = obj[key] end end end end --- 遍历所有包含offset的source function m.eachSourceContain(ast, offset, callback) local list = { ast } while true do local len = #list if len == 0 then return end local obj = list[len] list[len] = nil if m.isInRange(obj, offset) then if m.isContain(obj, offset) then local res = callback(obj) if res ~= nil then return res end end m.addChilds(list, obj, m.childMap) end end end --- 遍历所有指定类型的source function m.eachSourceType(ast, type, callback) local cache = ast.typeCache if not cache then local mark = {} cache = {} ast.typeCache = cache m.eachSource(ast, function (source) if mark[source] then return end mark[source] = true 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 local myCache = cache[type] if not myCache then return end for i = 1, #myCache do callback(myCache[i]) end end --- 遍历所有的source function m.eachSource(ast, callback) local list = { ast } while true do local len = #list if len == 0 then return end local obj = list[len] list[len] = nil callback(obj) m.addChilds(list, obj, m.childMap) end end --- 获取指定的 special function m.eachSpecialOf(ast, name, callback) local root = m.getRoot(ast) if not root.specials then return end local specials = root.specials[name] if not specials then return end for i = 1, #specials do callback(specials[i]) end end --- 获取偏移对应的坐标 ---@param lines table ---@return integer {name = 'row'} ---@return integer {name = 'col'} function m.positionOf(lines, offset) if offset < 1 then return 0, 0 end local lastLine = lines[#lines] if offset > lastLine.finish then return #lines, lastLine.finish - lastLine.start + 1 end local min = 1 local max = #lines for _ = 1, 100 do if max <= min then local line = lines[min] return min, offset - line.start + 1 end local row = (max - min) // 2 + min local line = lines[row] if offset < line.start then max = row - 1 elseif offset > line.finish then min = row + 1 else return row, offset - line.start + 1 end end error('Stack overflow!') end --- 获取坐标对应的偏移 ---@param lines table ---@param row integer ---@param col integer ---@return integer {name = 'offset'} function m.offsetOf(lines, row, col) if row < 1 then return 0 end if row > #lines then local lastLine = lines[#lines] return lastLine.finish end local line = lines[row] local len = line.finish - line.start + 1 if col < 0 then return line.start elseif col > len then return line.finish else return line.start + col - 1 end end function m.lineContent(lines, text, row) local line = lines[row] if not line then return '' end return text:sub(line.start, line.finish) end function m.lineRange(lines, row) local line = lines[row] if not line then return 0, 0 end return line.start, line.finish end function m.getName(obj) local tp = obj.type if tp == 'getglobal' or tp == 'setglobal' then return obj[1] elseif tp == 'local' or tp == 'getlocal' or tp == 'setlocal' then return obj[1] elseif tp == 'getfield' or tp == 'setfield' or tp == 'tablefield' then return obj.field[1] elseif tp == 'getmethod' or tp == 'setmethod' then return obj.method[1] elseif tp == 'getindex' or tp == 'setindex' or tp == 'tableindex' then return m.getName(obj.index) elseif tp == 'field' or tp == 'method' then return obj[1] elseif tp == 'index' then return m.getName(obj.index) elseif tp == 'string' then return obj[1] end return nil end function m.getKeyName(obj) local tp = obj.type if tp == 'getglobal' or tp == 'setglobal' then return 's|' .. obj[1] elseif tp == 'getfield' or tp == 'setfield' or tp == 'tablefield' then if obj.field then return 's|' .. obj.field[1] end elseif tp == 'getmethod' or tp == 'setmethod' then if obj.method then return 's|' .. obj.method[1] end elseif tp == 'getindex' or tp == 'setindex' or tp == 'tableindex' then if obj.index then return m.getKeyName(obj.index) end elseif tp == 'field' or tp == 'method' then return 's|' .. obj[1] elseif tp == 'string' then local s = obj[1] if s then return 's|' .. s else return s end elseif tp == 'number' then local n = obj[1] if n then return ('n|%q'):format(obj[1]) else return 'n' end elseif tp == 'boolean' then local b = obj[1] if b then return 'b|' .. tostring(b) else return 'b' end end return nil end function m.getENV(ast) if ast.type ~= 'main' then return nil end return ast.locals[1] end --- 测试 a 到 b 的路径(不经过函数,不考虑 goto), --- 每个路径是一个 block 。 --- --- 如果 a 在 b 的前面,返回 `"before"` 加上 2个`list` --- --- 如果 a 在 b 的后面,返回 `"after"` 加上 2个`list` --- --- 否则返回 `false` --- --- 返回的2个 `list` 分别为基准block到达 a 与 b 的路径。 ---@param a table ---@param b table ---@return string|boolean mode ---@return table|nil pathA ---@return table|nil pathB function m.getPath(a, b) --- 首先测试双方在同一个函数内 if m.getParentFunction(a) ~= m.getParentFunction(b) then return false end local mode local objA local objB if a.finish < b.start then mode = 'before' objA = a objB = b elseif a.start > b.finish then mode = 'after' objA = b objB = a else return 'equal', {}, {} end local pathA = {} local pathB = {} for _ = 1, 1000 do objA = m.getParentBlock(objA) pathA[#pathA+1] = objA if objA.type == 'function' or objA.type == 'main' then break end end for _ = 1, 1000 do objB = m.getParentBlock(objB) pathB[#pathB+1] = objB if objB.type == 'function' or objB.type == 'main' then break end end -- pathA: {1, 2, 3, 4, 5} -- pathB: {5, 6, 2, 3} local top = #pathB local start for i = #pathA, 1, -1 do local currentBlock = pathA[i] if currentBlock == pathB[top] then start = i break end end -- pathA: { 1, 2, 3} -- pathB: {5, 6, 2, 3} local extra = 0 local align = top - start for i = start, 1, -1 do local currentA = pathA[i] local currentB = pathB[i+align] if currentA ~= currentB then extra = i break end end -- pathA: {1} local resultA = {} for i = extra, 1, -1 do resultA[#resultA+1] = pathA[i] end -- pathB: {5, 6} local resultB = {} for i = extra + align, 1, -1 do resultB[#resultB+1] = pathB[i] end return mode, resultA, resultB end return m