.dotfiles/nvim/mason/packages/lua-language-server/libexec/script/vm/global.lua

651 lines
17 KiB
Lua
Raw Normal View History

local util = require 'utility'
local scope = require 'workspace.scope'
local guide = require 'parser.guide'
---@class vm
local vm = require 'vm.vm'
---@type table<string, vm.global>
local allGlobals = {}
---@type table<uri, table<string, boolean>>
local globalSubs = util.multiTable(2)
---@class parser.object
---@field package _globalBase parser.object
---@field package _globalBaseMap table<string, parser.object>
---@field global vm.global
---@class vm.global.link
---@field sets parser.object[]
---@field gets parser.object[]
---@class vm.global
---@field links table<uri, vm.global.link>
---@field setsCache? table<uri, parser.object[]>
---@field cate vm.global.cate
local mt = {}
mt.__index = mt
mt.type = 'global'
mt.name = ''
---@param uri uri
---@param source parser.object
function mt:addSet(uri, source)
local link = self.links[uri]
link.sets[#link.sets+1] = source
self.setsCache = nil
end
---@param uri uri
---@param source parser.object
function mt:addGet(uri, source)
local link = self.links[uri]
link.gets[#link.gets+1] = source
end
---@param suri uri
---@return parser.object[]
function mt:getSets(suri)
if not self.setsCache then
self.setsCache = {}
end
local scp = scope.getScope(suri)
local cacheUri = scp.uri or '<callback>'
if self.setsCache[cacheUri] then
return self.setsCache[cacheUri]
end
local clock = os.clock()
self.setsCache[cacheUri] = {}
local cache = self.setsCache[cacheUri]
for uri, link in pairs(self.links) do
if link.sets then
if scp:isVisible(uri) then
for _, source in ipairs(link.sets) do
cache[#cache+1] = source
end
end
end
end
local cost = os.clock() - clock
if cost > 0.1 then
log.warn('global-manager getSets costs', cost, self.name)
end
return cache
end
---@return parser.object[]
function mt:getAllSets()
if not self.setsCache then
self.setsCache = {}
end
local cache = self.setsCache['*']
if cache then
return cache
end
cache = {}
self.setsCache['*'] = cache
for _, link in pairs(self.links) do
if link.sets then
for _, source in ipairs(link.sets) do
cache[#cache+1] = source
end
end
end
return cache
end
---@param uri uri
function mt:dropUri(uri)
self.links[uri] = nil
self.setsCache = nil
end
---@return string
function mt:getName()
return self.name
end
---@return string
function mt:getCodeName()
return (self.name:gsub(vm.ID_SPLITE, '.'))
end
---@return string
function mt:asKeyName()
return self.cate .. '|' .. self.name
end
---@return string
function mt:getKeyName()
return self.name:match('[^' .. vm.ID_SPLITE .. ']+$')
end
---@return string?
function mt:getFieldName()
return self.name:match(vm.ID_SPLITE .. '(.-)$')
end
---@return boolean
function mt:isAlive()
return next(self.links) ~= nil
end
---@param uri uri
---@return parser.object?
function mt:getParentBase(uri)
local parentID = self.name:match('^(.-)' .. vm.ID_SPLITE)
if not parentID then
return nil
end
local parentName = self.cate .. '|' .. parentID
local global = allGlobals[parentName]
if not global then
return nil
end
local link = global.links[uri]
if not link then
return nil
end
local luckyBoy = link.sets[1] or link.gets[1]
if not luckyBoy then
return nil
end
return vm.getGlobalBase(luckyBoy)
end
---@param cate vm.global.cate
---@return vm.global
local function createGlobal(name, cate)
return setmetatable({
name = name,
cate = cate,
links = util.multiTable(2, function ()
return {
sets = {},
gets = {},
}
end),
}, mt)
end
---@class parser.object
---@field package _globalNode vm.global|false
---@field package _enums? parser.object[]
local compileObject
local compilerGlobalSwitch = util.switch()
: case 'local'
: call(function (source)
if source.special ~= '_G' then
return
end
if source.ref then
for _, ref in ipairs(source.ref) do
compileObject(ref)
end
end
end)
: case 'getlocal'
: call(function (source)
if source.special ~= '_G' then
return
end
if not source.next then
return
end
compileObject(source.next)
end)
: case 'setglobal'
: call(function (source)
local uri = guide.getUri(source)
local name = guide.getKeyName(source)
if not name then
return
end
local global = vm.declareGlobal('variable', name, uri)
global:addSet(uri, source)
source._globalNode = global
end)
: case 'getglobal'
: call(function (source)
local uri = guide.getUri(source)
local name = guide.getKeyName(source)
if not name then
return
end
local global = vm.declareGlobal('variable', name, uri)
global:addGet(uri, source)
source._globalNode = global
local nxt = source.next
if nxt then
compileObject(nxt)
end
end)
: case 'setfield'
: case 'setmethod'
: case 'setindex'
---@param source parser.object
: call(function (source)
local name
local keyName = guide.getKeyName(source)
if not keyName then
return
end
if source.node._globalNode then
local parentName = source.node._globalNode:getName()
if parentName == '_G' then
name = keyName
else
name = ('%s%s%s'):format(parentName, vm.ID_SPLITE, keyName)
end
elseif source.node.special == '_G' then
name = keyName
end
if not name then
return
end
local uri = guide.getUri(source)
local global = vm.declareGlobal('variable', name, uri)
global:addSet(uri, source)
source._globalNode = global
end)
: case 'getfield'
: case 'getmethod'
: case 'getindex'
---@param source parser.object
: call(function (source)
local name
local keyName = guide.getKeyName(source)
if not keyName then
return
end
if source.node._globalNode then
local parentName = source.node._globalNode:getName()
if parentName == '_G' then
name = keyName
else
name = ('%s%s%s'):format(parentName, vm.ID_SPLITE, keyName)
end
elseif source.node.special == '_G' then
name = keyName
end
local uri = guide.getUri(source)
local global = vm.declareGlobal('variable', name, uri)
global:addGet(uri, source)
source._globalNode = global
local nxt = source.next
if nxt then
compileObject(nxt)
end
end)
: case 'call'
: call(function (source)
if source.node.special == 'rawset'
or source.node.special == 'rawget' then
if not source.args then
return
end
local g = source.args[1]
local key = source.args[2]
if g and key and g.special == '_G' then
local name = guide.getKeyName(key)
if name then
local uri = guide.getUri(source)
local global = vm.declareGlobal('variable', name, uri)
if source.node.special == 'rawset' then
global:addSet(uri, source)
source.value = source.args[3]
else
global:addGet(uri, source)
end
source._globalNode = global
local nxt = source.next
if nxt then
compileObject(nxt)
end
end
end
end
end)
: case 'doc.class'
---@param source parser.object
: call(function (source)
local uri = guide.getUri(source)
local name = guide.getKeyName(source)
if not name then
return
end
local class = vm.declareGlobal('type', name, uri)
class:addSet(uri, source)
source._globalNode = class
if source.signs then
local sign = vm.createSign()
vm.setSign(source, sign)
for _, obj in ipairs(source.signs) do
sign:addSign(vm.compileNode(obj))
end
if source.extends then
for _, ext in ipairs(source.extends) do
if ext.type == 'doc.type.table' then
vm.setGeneric(ext, vm.createGeneric(ext, sign))
end
end
end
end
end)
: case 'doc.alias'
: call(function (source)
local uri = guide.getUri(source)
local name = guide.getKeyName(source)
if not name then
return
end
local alias = vm.declareGlobal('type', name, uri)
alias:addSet(uri, source)
source._globalNode = alias
if source.signs then
source._sign = vm.createSign()
for _, sign in ipairs(source.signs) do
source._sign:addSign(vm.compileNode(sign))
end
source.extends._generic = vm.createGeneric(source.extends, source._sign)
end
end)
: case 'doc.enum'
: call(function (source)
local uri = guide.getUri(source)
local name = guide.getKeyName(source)
if not name then
return
end
local enum = vm.declareGlobal('type', name, uri)
enum:addSet(uri, source)
source._globalNode = enum
local tbl = source.bindSource
if not tbl then
return
end
source._enums = {}
for _, field in ipairs(tbl) do
if field.type == 'tablefield' then
source._enums[#source._enums+1] = field
local subType = vm.declareGlobal('type', name .. '.' .. field.field[1], uri)
subType:addSet(uri, field)
elseif field.type == 'tableindex' then
source._enums[#source._enums+1] = field
if field.index.type == 'string' then
local subType = vm.declareGlobal('type', name .. '.' .. field.index[1], uri)
subType:addSet(uri, field)
end
end
end
end)
: case 'doc.type.name'
: call(function (source)
local uri = guide.getUri(source)
local name = source[1]
if name == '_' then
return
end
if name == 'self' then
return
end
local type = vm.declareGlobal('type', name, uri)
type:addGet(uri, source)
source._globalNode = type
end)
: case 'doc.extends.name'
: call(function (source)
local uri = guide.getUri(source)
local name = source[1]
local class = vm.declareGlobal('type', name, uri)
class:addGet(uri, source)
source._globalNode = class
end)
---@alias vm.global.cate '"variable"' | '"type"'
---@param cate vm.global.cate
---@param name string
---@param uri? uri
---@return vm.global
function vm.declareGlobal(cate, name, uri)
local key = cate .. '|' .. name
if uri then
globalSubs[uri][key] = true
end
if not allGlobals[key] then
allGlobals[key] = createGlobal(name, cate)
end
return allGlobals[key]
end
---@param cate vm.global.cate
---@param name string
---@param field? string
---@return vm.global?
function vm.getGlobal(cate, name, field)
local key = cate .. '|' .. name
if field then
key = key .. vm.ID_SPLITE .. field
end
return allGlobals[key]
end
---@param cate vm.global.cate
---@param name string
---@return vm.global[]
function vm.getGlobalFields(cate, name)
local globals = {}
local key = cate .. '|' .. name
local clock = os.clock()
for gid, global in pairs(allGlobals) do
if gid ~= key
and util.stringStartWith(gid, key)
and gid:sub(#key + 1, #key + 1) == vm.ID_SPLITE
and not gid:find(vm.ID_SPLITE, #key + 2) then
globals[#globals+1] = global
end
end
local cost = os.clock() - clock
if cost > 0.1 then
log.warn('global-manager getFields costs', cost)
end
return globals
end
---@param cate vm.global.cate
---@return vm.global[]
function vm.getGlobals(cate)
local globals = {}
local clock = os.clock()
for gid, global in pairs(allGlobals) do
if util.stringStartWith(gid, cate)
and not gid:find(vm.ID_SPLITE) then
globals[#globals+1] = global
end
end
local cost = os.clock() - clock
if cost > 0.1 then
log.warn('global-manager getGlobals costs', cost)
end
return globals
end
---@return table<string, vm.global>
function vm.getAllGlobals()
return allGlobals
end
---@param suri uri
---@param cate vm.global.cate
---@return parser.object[]
function vm.getGlobalSets(suri, cate)
local globals = vm.getGlobals(cate)
local result = {}
for _, global in ipairs(globals) do
local sets = global:getSets(suri)
for _, set in ipairs(sets) do
result[#result+1] = set
end
end
return result
end
---@param suri uri
---@param cate vm.global.cate
---@param name string
---@return boolean
function vm.hasGlobalSets(suri, cate, name)
local global = vm.getGlobal(cate, name)
if not global then
return false
end
local sets = global:getSets(suri)
if #sets == 0 then
return false
end
return true
end
---@param source parser.object
function compileObject(source)
if source._globalNode ~= nil then
return
end
source._globalNode = false
compilerGlobalSwitch(source.type, source)
end
---@param source parser.object
---@return vm.global?
function vm.getGlobalNode(source)
return source._globalNode or nil
end
---@param source parser.object
---@return parser.object[]?
function vm.getEnums(source)
return source._enums
end
---@param source parser.object
---@return boolean
function vm.compileByGlobal(source)
local global = vm.getGlobalNode(source)
if not global then
return false
end
vm.setNode(source, global)
if global.cate == 'variable' then
if guide.isAssign(source) then
if vm.bindDocs(source) then
return true
end
if source.value and source.value.type ~= 'nil' then
vm.setNode(source, vm.compileNode(source.value))
return true
end
else
if vm.bindAs(source) then
return true
end
local node = vm.traceNode(source)
if node then
vm.setNode(source, node, true)
return true
end
end
end
local globalBase = vm.getGlobalBase(source)
if not globalBase then
return false
end
local globalNode = vm.compileNode(globalBase)
vm.setNode(source, globalNode, true)
return true
end
---@param source parser.object
---@return parser.object?
function vm.getGlobalBase(source)
if source._globalBase then
return source._globalBase
end
local global = vm.getGlobalNode(source)
if not global then
return nil
end
---@cast source parser.object
local root = guide.getRoot(source)
if not root._globalBaseMap then
root._globalBaseMap = {}
end
local name = global:asKeyName()
if not root._globalBaseMap[name] then
root._globalBaseMap[name] = {
type = 'globalbase',
parent = root,
global = global,
start = 0,
finish = 0,
}
end
source._globalBase = root._globalBaseMap[name]
return source._globalBase
end
---@param source parser.object
local function compileAst(source)
local env = guide.getENV(source)
if not env then
return
end
compileObject(env)
guide.eachSpecialOf(source, 'rawset', function (src)
compileObject(src.parent)
end)
guide.eachSpecialOf(source, 'rawget', function (src)
compileObject(src.parent)
end)
guide.eachSourceTypes(source.docs, {
'doc.class',
'doc.alias',
'doc.type.name',
'doc.extends.name',
'doc.enum',
}, function (src)
compileObject(src)
end)
end
---@param uri uri
local function dropUri(uri)
local globalSub = globalSubs[uri]
globalSubs[uri] = nil
for key in pairs(globalSub) do
local global = allGlobals[key]
if global then
global:dropUri(uri)
if not global:isAlive() then
allGlobals[key] = nil
end
end
end
end
return {
compileAst = compileAst,
dropUri = dropUri,
}