508 lines
12 KiB
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)
|