nixos/lua-lsp/script/vm/node.lua

508 lines
12 KiB
Lua

local files = require 'files'
---@class vm
local vm = require 'vm.vm'
local ws = require 'workspace.workspace'
local guide = require 'parser.guide'
local timer = require 'timer'
---@type table<vm.object, vm.node>
vm.nodeCache = {}
---@alias vm.node.object vm.object | vm.global
---@class vm.node
---@field [integer] vm.node.object
---@field [vm.node.object] true
local mt = {}
mt.__index = mt
mt.id = 0
mt.type = 'vm.node'
mt.optional = nil
mt.data = nil
mt.resolved = nil
---@param node vm.node | vm.node.object
---@return vm.node
function mt:merge(node)
if not node then
return self
end
if node.type == 'vm.node' then
if node == self then
return self
end
if node:isOptional() then
self.optional = true
end
for _, obj in ipairs(node) do
if not self[obj] then
self[obj] = true
self[#self+1] = obj
end
end
else
---@cast node -vm.node
if not self[node] then
self[node] = true
self[#self+1] = node
end
end
return self
end
---@return boolean
function mt:isEmpty()
return #self == 0
end
function mt:clear()
self.optional = nil
for i, c in ipairs(self) do
self[i] = nil
self[c] = nil
end
end
---@param n integer
---@return vm.node.object?
function mt:get(n)
return self[n]
end
function mt:setData(k, v)
if not self.data then
self.data = {}
end
self.data[k] = v
end
---@return any
function mt:getData(k)
if not self.data then
return nil
end
return self.data[k]
end
function mt:addOptional()
self.optional = true
end
function mt:removeOptional()
self:remove 'nil'
return self
end
---@return boolean
function mt:isOptional()
return self.optional == true
end
---@return boolean
function mt:hasFalsy()
if self.optional then
return true
end
for _, c in ipairs(self) do
if c.type == 'nil'
or (c.type == 'global' and c.cate == 'type' and c.name == 'nil')
or (c.type == 'global' and c.cate == 'type' and c.name == 'false')
or (c.type == 'boolean' and c[1] == false)
or (c.type == 'doc.type.boolean' and c[1] == false) then
return true
end
end
return false
end
---@return boolean
function mt:hasKnownType()
for _, c in ipairs(self) do
if c.type == 'global' and c.cate == 'type' then
return true
end
if guide.isLiteral(c) then
return true
end
end
return false
end
---@return boolean
function mt:isNullable()
if self.optional then
return true
end
if #self == 0 then
return true
end
for _, c in ipairs(self) do
if c.type == 'nil'
or (c.type == 'global' and c.cate == 'type' and c.name == 'nil')
or (c.type == 'global' and c.cate == 'type' and c.name == 'any')
or (c.type == 'global' and c.cate == 'type' and c.name == '...') then
return true
end
end
return false
end
---@return vm.node
function mt:setTruthy()
if self.optional == true then
self.optional = nil
end
local hasBoolean
for index = #self, 1, -1 do
local c = self[index]
if c.type == 'nil'
or (c.type == 'global' and c.cate == 'type' and c.name == 'nil')
or (c.type == 'global' and c.cate == 'type' and c.name == 'false')
or (c.type == 'boolean' and c[1] == false)
or (c.type == 'doc.type.boolean' and c[1] == false) then
table.remove(self, index)
self[c] = nil
goto CONTINUE
end
if c.type == 'global' and c.cate == 'type' and c.name == 'boolean' then
hasBoolean = true
table.remove(self, index)
self[c] = nil
goto CONTINUE
end
if c.type == 'boolean' or c.type == 'doc.type.boolean' then
if c[1] == false then
table.remove(self, index)
self[c] = nil
goto CONTINUE
end
end
::CONTINUE::
end
if hasBoolean then
self:merge(vm.declareGlobal('type', 'true'))
end
return self
end
---@return vm.node
function mt:setFalsy()
if self.optional == false then
self.optional = nil
end
local hasBoolean
for index = #self, 1, -1 do
local c = self[index]
if c.type == 'nil'
or (c.type == 'global' and c.cate == 'type' and c.name == 'nil')
or (c.type == 'global' and c.cate == 'type' and c.name == 'false')
or (c.type == 'boolean' and c[1] == false)
or (c.type == 'doc.type.boolean' and c[1] == false) then
goto CONTINUE
end
if c.type == 'global' and c.cate == 'type' and c.name == 'boolean' then
hasBoolean = true
table.remove(self, index)
self[c] = nil
goto CONTINUE
end
if c.type == 'boolean' or c.type == 'doc.type.boolean' then
if c[1] == true then
table.remove(self, index)
self[c] = nil
goto CONTINUE
end
end
if (c.type == 'global' and c.cate == 'type') then
table.remove(self, index)
self[c] = nil
goto CONTINUE
end
if guide.isLiteral(c) then
table.remove(self, index)
self[c] = nil
goto CONTINUE
end
::CONTINUE::
end
if hasBoolean then
self:merge(vm.declareGlobal('type', 'false'))
end
return self
end
---@param name string
function mt:remove(name)
if name == 'nil' and self.optional == true then
self.optional = nil
end
for index = #self, 1, -1 do
local c = self[index]
if (c.type == 'global' and c.cate == 'type' and c.name == name)
or (c.type == name)
or (c.type == 'doc.type.integer' and (name == 'number' or name == 'integer'))
or (c.type == 'doc.type.boolean' and name == 'boolean')
or (c.type == 'doc.type.boolean' and name == 'true' and c[1] == true)
or (c.type == 'doc.type.boolean' and name == 'false' and c[1] == false)
or (c.type == 'doc.type.table' and name == 'table')
or (c.type == 'doc.type.array' and name == 'table')
or (c.type == 'doc.type.sign' and name == c.node[1])
or (c.type == 'doc.type.function' and name == 'function')
or (c.type == 'doc.type.string' and name == 'string') then
table.remove(self, index)
self[c] = nil
end
end
return self
end
---@param uri uri
---@param name string
function mt:narrow(uri, name)
if self.optional == true then
self.optional = nil
end
for index = #self, 1, -1 do
local c = self[index]
if (c.type == name)
or (c.type == 'doc.type.integer' and (name == 'number' or name == 'integer'))
or (c.type == 'doc.type.boolean' and name == 'boolean')
or (c.type == 'doc.type.table' and name == 'table')
or (c.type == 'doc.type.array' and name == 'table')
or (c.type == 'doc.type.sign' and name == c.node[1])
or (c.type == 'doc.type.function' and name == 'function')
or (c.type == 'doc.type.string' and name == 'string') then
goto CONTINUE
end
if c.type == 'global' and c.cate == 'type' then
if (c.name == name)
or (vm.isSubType(uri, c.name, name)) then
goto CONTINUE
end
end
table.remove(self, index)
self[c] = nil
::CONTINUE::
end
if #self == 0 then
self[#self+1] = vm.getGlobal('type', name)
end
return self
end
---@param obj vm.object
function mt:removeObject(obj)
for index, c in ipairs(self) do
if c == obj then
table.remove(self, index)
self[c] = nil
return
end
end
end
---@param node vm.node
function mt:removeNode(node)
for _, c in ipairs(node) do
if c.type == 'global' and c.cate == 'type' then
---@cast c vm.global
self:remove(c.name)
elseif c.type == 'nil' then
self:remove 'nil'
elseif c.type == 'boolean'
or c.type == 'doc.type.boolean' then
if c[1] == true then
self:remove 'true'
else
self:remove 'false'
end
else
---@cast c -vm.global
self:removeObject(c)
end
end
end
---@param name string
---@return boolean
function mt:hasType(name)
for _, c in ipairs(self) do
if c.type == 'global' and c.cate == 'type' and c.name == name then
return true
end
end
return false
end
---@param name string
---@return boolean
function mt:hasName(name)
if name == 'nil' and self.optional == true then
return true
end
for _, c in ipairs(self) do
if c.type == 'global' and c.cate == 'type' and c.name == name then
return true
end
if c.type == name then
return true
end
-- TODO
end
return false
end
---@return vm.node
function mt:asTable()
self.optional = nil
for index = #self, 1, -1 do
local c = self[index]
if c.type == 'table'
or c.type == 'doc.type.table'
or c.type == 'doc.type.array' then
goto CONTINUE
end
if c.type == 'doc.type.sign' then
if c.node[1] == 'table'
or not guide.isBasicType(c.node[1]) then
goto CONTINUE
end
end
if c.type == 'global' and c.cate == 'type' then
---@cast c vm.global
if c.name == 'table'
or not guide.isBasicType(c.name) then
goto CONTINUE
end
end
table.remove(self, index)
self[c] = nil
::CONTINUE::
end
return self
end
---@return fun():vm.node.object
function mt:eachObject()
local i = 0
return function ()
i = i + 1
return self[i]
end
end
---@return vm.node
function mt:copy()
return vm.createNode(self)
end
---@param source vm.object
---@param node vm.node | vm.node.object
---@param cover? boolean
---@return vm.node
function vm.setNode(source, node, cover)
if not node then
if TEST then
error('Can not set nil node')
else
log.error('Can not set nil node')
end
end
if cover then
---@cast node vm.node
vm.nodeCache[source] = node
return node
end
local me = vm.nodeCache[source]
if me then
me:merge(node)
else
if node.type == 'vm.node' then
me = node:copy()
else
me = vm.createNode(node)
end
vm.nodeCache[source] = me
end
return me
end
---@param source vm.object
---@return vm.node?
function vm.getNode(source)
return vm.nodeCache[source]
end
---@param source vm.object
function vm.removeNode(source)
vm.nodeCache[source] = nil
end
local lockCount = 0
local needClearCache = false
function vm.lockCache()
lockCount = lockCount + 1
end
function vm.unlockCache()
lockCount = lockCount - 1
if needClearCache then
needClearCache = false
vm.clearNodeCache()
end
end
function vm.clearNodeCache()
if lockCount > 0 then
needClearCache = true
return
end
log.debug('clearNodeCache')
vm.nodeCache = {}
end
local ID = 0
---@param a? vm.node | vm.node.object
---@param b? vm.node | vm.node.object
---@return vm.node
function vm.createNode(a, b)
ID = ID + 1
local node = setmetatable({
id = ID,
}, mt)
if a then
node:merge(a)
end
if b then
node:merge(b)
end
return node
end
---@type timer?
local delayTimer
files.watch(function (ev, uri)
if ev == 'version' then
if ws.isReady(uri) then
if CACHEALIVE then
if delayTimer then
delayTimer:restart()
end
delayTimer = timer.wait(1, function ()
delayTimer = nil
vm.clearNodeCache()
end)
else
vm.clearNodeCache()
end
end
end
end)
ws.watch(function (ev, uri)
if ev == 'reload' then
vm.clearNodeCache()
end
end)