nixos/lua-lsp/script/parser/guide.lua

1337 lines
35 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 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<string, integer>
---@field upvalues table<string, string[]>
---@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 hasError? true
---@field [integer] parser.object|any
---@field package _root parser.object
---@class guide
---@field debugMode boolean
local m = {}
m.ANY = {"<ANY>"}
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 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', '#'},
['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'},
['tableexp'] = {'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},
['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'] = {'loc', '#casts'},
['doc.cast.block'] = {'extends'},
['doc.operator'] = {'op', 'exp', 'extends'},
}
---@type table<string, fun(obj: parser.object, list: parser.object[])>
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
---@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.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 isSetMap = {
['setglobal'] = true,
['local'] = true,
['self'] = true,
['setlocal'] = true,
['setfield'] = true,
['setmethod'] = true,
['setindex'] = true,
['tablefield'] = true,
['tableindex'] = true,
['tableexp'] = 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.isSet(source)
local tp = source.type
if isSetMap[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 isGetMap = {
['getglobal'] = true,
['getlocal'] = true,
['getfield'] = true,
['getmethod'] = true,
['getindex'] = true,
}
function m.isGet(source)
local tp = source.type
if isGetMap[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' 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
--- 测试 a 到 b 的路径(不经过函数,不考虑 goto
--- 每个路径是一个 block 。
---
--- 如果 a 在 b 的前面,返回 `"before"` 加上 2个`list<block>`
---
--- 如果 a 在 b 的后面,返回 `"after"` 加上 2个`list<block>`
---
--- 否则返回 `false`
---
--- 返回的2个 `list` 分别为基准block到达 a 与 b 的路径。
---@param a table
---@param b table
---@return string|false mode
---@return table? pathA
---@return table? pathB
function m.getPath(a, b, sameFunction)
--- 首先测试双方在同一个函数内
if sameFunction and 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)
if objA then
pathA[#pathA+1] = objA
if (not sameFunction and objA.type == 'function') or objA.type == 'main' then
break
end
end
end
for _ = 1, 1000 do
objB = m.getParentBlock(objB)
if objB then
pathB[#pathB+1] = objB
if (not sameFunction and objB.type == 'function') or objB.type == 'main' then
break
end
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
if not start then
return false
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
---是否是全局变量(包括 _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
return m