summaryrefslogtreecommitdiff
path: root/script/vm
diff options
context:
space:
mode:
author最萌小汐 <sumneko@hotmail.com>2022-04-21 22:19:26 +0800
committer最萌小汐 <sumneko@hotmail.com>2022-04-21 22:19:26 +0800
commit7291440d890483f70ddd7ba00db483af0257b499 (patch)
tree27d2a33c88a2f7d8ef908297bc5d7e9771db68d7 /script/vm
parent8ba44c3e21ac761b88226fec0cc8a5347a4a5804 (diff)
parenta19f2ee7394c5f5a10342a37497fbd7b7587316c (diff)
downloadlua-language-server-7291440d890483f70ddd7ba00db483af0257b499.zip
Merge remote-tracking branch 'origin/type-narrow'
Diffstat (limited to 'script/vm')
-rw-r--r--script/vm/compiler.lua72
-rw-r--r--script/vm/init.lua1
-rw-r--r--script/vm/node.lua56
-rw-r--r--script/vm/runner.lua134
4 files changed, 225 insertions, 38 deletions
diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua
index ef91b4b1..94968292 100644
--- a/script/vm/compiler.lua
+++ b/script/vm/compiler.lua
@@ -751,21 +751,9 @@ end
---@param source parser.object
---@return vm.node
-local function compileLocalBase(source)
- if not source._localBase then
- source._localBase = {
- type = 'localbase',
- parent = source,
- }
- end
- local baseNode = vm.getNode(source._localBase)
- if baseNode then
- return baseNode
- end
- baseNode = vm.createNode()
- vm.setNode(source._localBase, baseNode, true)
-
+local function compileLocal(source)
vm.setNode(source, source)
+
local hasMarkDoc
if source.bindDocs then
hasMarkDoc = bindDocs(source)
@@ -852,12 +840,7 @@ local function compileLocalBase(source)
end
end
- baseNode:merge(vm.getNode(source))
- vm.removeNode(source)
-
- baseNode:setData('hasDefined', hasMarkDoc or hasMarkParam or hasMarkValue)
-
- return baseNode
+ vm.getNode(source):setData('hasDefined', hasMarkDoc or hasMarkParam or hasMarkValue)
end
local compilerSwitch = util.switch()
@@ -917,35 +900,50 @@ local compilerSwitch = util.switch()
end)
: case 'local'
: case 'self'
+ ---@param source parser.object
: call(function (source)
- local baseNode = compileLocalBase(source)
- vm.setNode(source, baseNode, true)
- if not baseNode:getData 'hasDefined' and source.ref then
+ compileLocal(source)
+ local refs = source.ref
+ if not refs then
+ return
+ end
+
+ local hasMark = vm.getNode(source):getData 'hasDefined'
+
+ local runner = vm.createRunner(source)
+ runner:launch(function (src, node)
+ if src.type == 'setlocal' then
+ if src.value and not hasMark then
+ if src.value.type == 'table' then
+ vm.setNode(src, src.value)
+ else
+ vm.setNode(src, vm.compileNode(src.value))
+ end
+ else
+ vm.setNode(src, node)
+ end
+ return vm.getNode(src)
+ elseif src.type == 'getlocal' then
+ vm.setNode(src, node, true)
+ end
+ end)
+
+ if not hasMark then
+ local parentFunc = guide.getParentFunction(source)
for _, ref in ipairs(source.ref) do
- if ref.type == 'setlocal' then
- vm.setNode(source, vm.compileNode(ref))
+ if ref.type == 'setlocal'
+ and guide.getParentFunction(ref) == parentFunc then
+ vm.setNode(source, vm.getNode(ref))
end
end
end
end)
: case 'setlocal'
: call(function (source)
- local baseNode = compileLocalBase(source.node)
- if not baseNode:getData 'hasDefined' and source.value then
- if source.value.type == 'table' then
- vm.setNode(source, source.value)
- else
- vm.setNode(source, vm.compileNode(source.value))
- end
- end
- baseNode:merge(vm.getNode(source))
- vm.setNode(source, baseNode, true)
vm.compileNode(source.node)
end)
: case 'getlocal'
: call(function (source)
- local baseNode = compileLocalBase(source.node)
- vm.setNode(source, baseNode, true)
vm.compileNode(source.node)
end)
: case 'setfield'
diff --git a/script/vm/init.lua b/script/vm/init.lua
index 0058c698..aa55a6c7 100644
--- a/script/vm/init.lua
+++ b/script/vm/init.lua
@@ -8,5 +8,6 @@ require 'vm.field'
require 'vm.doc'
require 'vm.type'
require 'vm.library'
+require 'vm.runner'
require 'vm.manager'
return vm
diff --git a/script/vm/node.lua b/script/vm/node.lua
index 81405c48..81d9f335 100644
--- a/script/vm/node.lua
+++ b/script/vm/node.lua
@@ -93,6 +93,59 @@ function mt:isFalsy()
return false
end
+---@return vm.node
+function mt:copyTruly()
+ local newNode = vm.createNode()
+ newNode.optional = false
+ local hasBoolean, hasTrue
+ for _, c in ipairs(self) do
+ if c.type == 'nil'
+ 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
+ goto CONTINUE
+ end
+ if c.type == 'boolean' or c.type == 'doc.type.boolean' then
+ hasTrue = true
+ end
+ newNode:merge(c)
+ ::CONTINUE::
+ end
+ if hasBoolean and not hasTrue then
+ newNode:merge {
+ type = 'doc.type.boolean',
+ [1] = true,
+ }
+ end
+ return newNode
+end
+
+---@param name string
+---@return vm.node
+function mt:copyWithout(name)
+ local newNode = vm.createNode()
+ if self:isOptional() then
+ newNode:addOptional()
+ end
+ for _, c in ipairs(self) do
+ 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.table' and name == 'table')
+ or (c.type == 'doc.type.array' and name == 'table')
+ or (c.type == 'doc.type.function' and name == 'function') then
+ goto CONTINUE
+ end
+ newNode:merge(c)
+ ::CONTINUE::
+ end
+ return newNode
+end
+
---@return fun():vm.object
function mt:eachObject()
local i = 0
@@ -126,13 +179,14 @@ function vm.setNode(source, node, cover)
me:merge(node)
else
if node.type == 'vm.node' then
- vm.nodeCache[source] = node
+ vm.nodeCache[source] = node:copy()
else
vm.nodeCache[source] = vm.createNode(node)
end
end
end
+---@param source vm.object
---@return vm.node?
function vm.getNode(source)
return vm.nodeCache[source]
diff --git a/script/vm/runner.lua b/script/vm/runner.lua
new file mode 100644
index 00000000..3a315d5c
--- /dev/null
+++ b/script/vm/runner.lua
@@ -0,0 +1,134 @@
+---@class vm
+local vm = require 'vm.vm'
+local guide = require 'parser.guide'
+local globalMgr = require 'vm.global-manager'
+
+---@class vm.runner
+---@field loc parser.object
+---@field mainBlock parser.object
+---@field blocks table<parser.object, true>
+---@field steps vm.runner.step[]
+local mt = {}
+mt.__index = mt
+mt.index = 1
+
+---@class parser.object
+---@field _hasSorted boolean
+
+---@class vm.runner.step
+---@field type 'truly' | 'optional' | 'add' | 'remove' | 'object' | 'save' | 'load'
+---@field pos integer
+---@field node? vm.node
+---@field object? parser.object
+---@field ref? vm.runner.step
+---@field name? string
+
+---@param filter parser.object
+---@param pos integer
+function mt:_compileNarrowByFilter(filter, pos)
+ if filter.type == 'unary' then
+ elseif filter.type == 'binary' then
+ else
+ if filter.type == 'getlocal' and filter.node == self.loc then
+ self.steps[#self.steps+1] = {
+ type = 'truly',
+ pos = pos,
+ }
+ end
+ end
+end
+
+function mt:_dropBlock(block)
+ local savePoint = {
+ type = 'save',
+ pos = block.start,
+ }
+ self.steps[#self.steps+1] = savePoint
+ self.steps[#self.steps+1] = {
+ type = 'load',
+ pos = block.finish,
+ ref = savePoint,
+ }
+end
+
+---@param block parser.object
+function mt:_compileBlock(block)
+ if self.blocks[block] then
+ return
+ end
+ self.blocks[block] = true
+ if block == self.mainBlock then
+ return
+ end
+
+ local parentBlock = guide.getParentBlock(block)
+ self:_compileBlock(parentBlock)
+
+ if block.type == 'ifblock'
+ or block.type == 'elseif' then
+ if block[1] then
+ self:_compileNarrowByFilter(block.filter, block[1].start)
+ end
+ end
+
+ if block.type == 'if' then
+ self:_dropBlock(block)
+ end
+
+ if block.type == 'function' then
+ self:_dropBlock(block)
+ end
+end
+
+function mt:_preCompile()
+ for _, ref in ipairs(self.loc.ref) do
+ self.steps[#self.steps+1] = {
+ type = 'object',
+ object = ref,
+ pos = ref.start,
+ }
+ local block = guide.getParentBlock(ref)
+ self:_compileBlock(block)
+ end
+ table.sort(self.steps, function (a, b)
+ return a.pos < b.pos
+ end)
+end
+
+---@param callback fun(src: parser.object, node: vm.node)
+function mt:launch(callback)
+ local node = vm.getNode(self.loc)
+ for _, step in ipairs(self.steps) do
+ if step.type == 'truly' then
+ node = node:copyTruly()
+ elseif step.type == 'optional' then
+ node = node:copy():addOptional()
+ elseif step.type == 'add' then
+ node = node:copy():merge(globalMgr.getGlobal('type', step.name))
+ elseif step.type == 'remove' then
+ node = node:copyWithout(step.name)
+ elseif step.type == 'object' then
+ node = callback(step.object, node) or node
+ elseif step.type == 'save' then
+ -- Nothing need to do
+ elseif step.type == 'load' then
+ node = step.ref.node
+ end
+ step.node = node
+ end
+end
+
+---@param loc parser.object
+---@return vm.runner
+function vm.createRunner(loc)
+ local self = setmetatable({
+ loc = loc,
+ mainBlock = guide.getParentBlock(loc),
+ blocks = {},
+ steps = {},
+ }, mt)
+
+ self:_preCompile()
+
+ return self
+end