summaryrefslogtreecommitdiff
path: root/script/vm/runner.lua
diff options
context:
space:
mode:
Diffstat (limited to 'script/vm/runner.lua')
-rw-r--r--script/vm/runner.lua454
1 files changed, 65 insertions, 389 deletions
diff --git a/script/vm/runner.lua b/script/vm/runner.lua
index 9fe0f172..38b0b66c 100644
--- a/script/vm/runner.lua
+++ b/script/vm/runner.lua
@@ -2,257 +2,19 @@
local vm = require 'vm.vm'
local guide = require 'parser.guide'
+---@alias vm.runner.callback fun(src: parser.object, node: vm.node)
+
---@class vm.runner
----@field loc parser.object
----@field mainBlock parser.object
----@field blocks table<parser.object, true>
----@field steps vm.runner.step[]
+---@field _loc parser.object
+---@field _objs parser.object[]
+---@field _callback vm.runner.callback
local mt = {}
mt.__index = mt
-mt.index = 1
-
----@class parser.object
----@field _casts parser.object[]
-
----@class vm.runner.step
----@field type 'truthy' | 'falsy' | 'as' | 'add' | 'remove' | 'object' | 'save' | 'push' | 'merge' | 'cast'
----@field pos integer
----@field order? integer
----@field node? vm.node
----@field object? parser.object
----@field name? string
----@field cast? parser.object
----@field tag? string
----@field copy? boolean
----@field new? boolean
----@field ref1? vm.runner.step
----@field ref2? vm.runner.step
-
----@param filter parser.object
----@param outStep vm.runner.step
----@param blockStep vm.runner.step
-function mt:_compileNarrowByFilter(filter, outStep, blockStep)
- if not filter then
- return
- end
- if filter.type == 'paren' then
- if filter.exp then
- self:_compileNarrowByFilter(filter.exp, outStep, blockStep)
- end
- return
- end
- if filter.type == 'unary' then
- if not filter.op
- or not filter[1] then
- return
- end
- if filter.op.type == 'not' then
- local exp = filter[1]
- if exp.type == 'getlocal' and exp.node == self.loc then
- self.steps[#self.steps+1] = {
- type = 'falsy',
- pos = filter.finish,
- new = true,
- }
- self.steps[#self.steps+1] = {
- type = 'truthy',
- pos = filter.finish,
- ref1 = outStep,
- }
- end
- end
- elseif filter.type == 'binary' then
- if not filter.op
- or not filter[1]
- or not filter[2] then
- return
- end
- if filter.op.type == 'and' then
- local dummyStep = {
- type = 'save',
- copy = true,
- ref1 = outStep,
- pos = filter.start - 1,
- }
- self.steps[#self.steps+1] = dummyStep
- self:_compileNarrowByFilter(filter[1], dummyStep, blockStep)
- self:_compileNarrowByFilter(filter[2], dummyStep, blockStep)
- end
- if filter.op.type == 'or' then
- self:_compileNarrowByFilter(filter[1], outStep, blockStep)
- local dummyStep = {
- type = 'push',
- copy = true,
- ref1 = outStep,
- pos = filter.op.finish,
- }
- self.steps[#self.steps+1] = dummyStep
- self:_compileNarrowByFilter(filter[2], outStep, dummyStep)
- self.steps[#self.steps+1] = {
- type = 'push',
- tag = 'or reset',
- ref1 = blockStep,
- pos = filter.finish,
- }
- end
- if filter.op.type == '=='
- or filter.op.type == '~=' then
- local loc, exp
- for i = 1, 2 do
- loc = filter[i]
- if loc.type == 'getlocal' and loc.node == self.loc then
- exp = filter[i % 2 + 1]
- break
- end
- end
- if not loc or not exp then
- return
- end
- if guide.isLiteral(exp) then
- if filter.op.type == '==' then
- self.steps[#self.steps+1] = {
- type = 'remove',
- name = exp.type,
- pos = filter.finish,
- ref1 = outStep,
- }
- self.steps[#self.steps+1] = {
- type = 'as',
- name = exp.type,
- pos = filter.finish,
- new = true,
- }
- end
- if filter.op.type == '~=' then
- self.steps[#self.steps+1] = {
- type = 'as',
- name = exp.type,
- pos = filter.finish,
- ref1 = outStep,
- }
- self.steps[#self.steps+1] = {
- type = 'remove',
- name = exp.type,
- pos = filter.finish,
- new = true,
- }
- end
- end
- end
- else
- if filter.type == 'getlocal' and filter.node == self.loc then
- self.steps[#self.steps+1] = {
- type = 'truthy',
- pos = filter.finish,
- new = true,
- }
- self.steps[#self.steps+1] = {
- type = 'falsy',
- pos = filter.finish,
- ref1 = outStep,
- }
- end
- end
-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 == 'if' then
- ---@type vm.runner.step[]
- local finals = {}
- for i, childBlock in ipairs(block) do
- local blockStep = {
- type = 'save',
- tag = 'block',
- copy = true,
- pos = childBlock.start,
- }
- local outStep = {
- type = 'save',
- tag = 'out',
- copy = true,
- pos = childBlock.start,
- }
- self.steps[#self.steps+1] = blockStep
- self.steps[#self.steps+1] = outStep
- self.steps[#self.steps+1] = {
- type = 'push',
- ref1 = blockStep,
- pos = childBlock.start,
- }
- self:_compileNarrowByFilter(childBlock.filter, outStep, blockStep)
- if not childBlock.hasReturn
- and not childBlock.hasGoTo
- and not childBlock.hasBreak then
- local finalStep = {
- type = 'save',
- pos = childBlock.finish,
- tag = 'final #' .. i,
- }
- finals[#finals+1] = finalStep
- self.steps[#self.steps+1] = finalStep
- end
- self.steps[#self.steps+1] = {
- type = 'push',
- tag = 'reset child',
- ref1 = outStep,
- pos = childBlock.finish,
- }
- end
- self.steps[#self.steps+1] = {
- type = 'push',
- tag = 'reset if',
- pos = block.finish,
- copy = true,
- }
- for _, final in ipairs(finals) do
- self.steps[#self.steps+1] = {
- type = 'merge',
- ref2 = final,
- pos = block.finish,
- }
- end
- end
-
- if block.type == 'function'
- or block.type == 'while'
- or block.type == 'loop'
- or block.type == 'in'
- or block.type == 'repeat'
- or block.type == 'for' then
- local savePoint = {
- type = 'save',
- copy = true,
- pos = block.start,
- }
- self.steps[#self.steps+1] = {
- type = 'push',
- copy = true,
- pos = block.start,
- }
- self.steps[#self.steps+1] = savePoint
- self.steps[#self.steps+1] = {
- type = 'push',
- pos = block.finish,
- ref1 = savePoint,
- }
- end
-end
+mt._index = 1
---@return parser.object[]
function mt:_getCasts()
- local root = guide.getRoot(self.loc)
+ local root = guide.getRoot(self._loc)
if not root._casts then
root._casts = {}
local docs = root.docs
@@ -265,180 +27,94 @@ function mt:_getCasts()
return root._casts
end
-function mt:_preCompile()
- local startPos = self.loc.start
+function mt:_collect()
+ local startPos = self._loc.start
local finishPos = 0
- for _, ref in ipairs(self.loc.ref) do
- self.steps[#self.steps+1] = {
- type = 'object',
- object = ref,
- pos = ref.range or ref.start,
- }
- if ref.start > finishPos then
- finishPos = ref.start
+ for _, ref in ipairs(self._loc.ref) do
+ if ref.type == 'getlocal'
+ or ref.type == 'setlocal' then
+ self._objs[#self._objs+1] = ref
+ if ref.start > finishPos then
+ finishPos = ref.start
+ end
end
- local block = guide.getParentBlock(ref)
- self:_compileBlock(block)
end
- for i, step in ipairs(self.steps) do
- if step.type ~= 'object' then
- step.order = i
- end
+ if #self._objs == 0 then
+ return
end
local casts = self:_getCasts()
for _, cast in ipairs(casts) do
- if cast.loc[1] == self.loc[1]
+ if cast.loc[1] == self._loc[1]
and cast.start > startPos
and cast.finish < finishPos
- and guide.getLocal(self.loc, self.loc[1], cast.start) == self.loc then
- self.steps[#self.steps+1] = {
- type = 'cast',
- cast = cast,
- pos = cast.start,
- }
+ and guide.getLocal(self._loc, self._loc[1], cast.start) == self._loc then
+ self._objs[#self._objs+1] = cast
end
end
- table.sort(self.steps, function (a, b)
- if a.pos == b.pos then
- return (a.order or 0) < (b.order or 0)
- else
- return a.pos < b.pos
- end
+ table.sort(self._objs, function (a, b)
+ return (a.range or a.start) < (b.range or b.start)
end)
end
----@param loc parser.object
+
+---@param pos integer
---@param node vm.node
+---@return parser.object
---@return vm.node
-local function checkAssert(loc, node)
- local parent = loc.parent
- if parent.type == 'binary' then
- if parent.op and (parent.op.type == '~=' or parent.op.type == '==') then
- local exp
- for i = 1, 2 do
- if parent[i] == loc then
- exp = parent[i % 2 + 1]
- end
- end
- if exp and guide.isLiteral(exp) then
- local callargs = parent.parent
- if callargs.type == 'callargs'
- and callargs.parent.node.special == 'assert'
- and callargs[1] == parent then
- if parent.op.type == '~=' then
- node:remove(exp.type)
- end
- if parent.op.type == '==' then
- node = vm.compileNode(exp)
- end
- end
- end
+function mt:_fastWard(pos, node)
+ for i = self._index, #self._objs do
+ local obj = self._objs[i]
+ if obj.start > pos then
+ self._index = i
+ return obj, node
+ end
+ if obj.type == 'getlocal'
+ or obj.type == 'setlocal' then
+ node = self._callback(obj, node) or node
+ else
+ error('unexpected type: ' .. obj.type)
end
end
- if parent.type == 'callargs'
- and parent.parent.node.special == 'assert'
- and parent[1] == loc then
- node:setTruthy()
- end
- return node
end
----@param callback fun(src: parser.object, node: vm.node)
-function mt:launch(callback)
- local topNode = vm.getNode(self.loc):copy()
- for _, step in ipairs(self.steps) do
- local node = step.ref1 and step.ref1.node or topNode
- if step.type == 'truthy' then
- if step.new then
- node = node:copy()
- topNode = node
- end
- node:setTruthy()
- elseif step.type == 'falsy' then
- if step.new then
- node = node:copy()
- topNode = node
- end
- node:setFalsy()
- elseif step.type == 'as' then
- if step.new then
- topNode = vm.createNode(vm.getGlobal('type', step.name))
- else
- node:clear()
- node:merge(vm.getGlobal('type', step.name))
- end
- elseif step.type == 'add' then
- if step.new then
- node = node:copy()
- topNode = node
- end
- node:merge(vm.getGlobal('type', step.name))
- elseif step.type == 'remove' then
- if step.new then
- node = node:copy()
- topNode = node
- end
- node:remove(step.name)
- elseif step.type == 'object' then
- topNode = callback(step.object, node) or node
- if step.object.type == 'getlocal' then
- topNode = checkAssert(step.object, node)
- end
- elseif step.type == 'save' then
- if step.copy then
- node = node:copy()
- end
- step.node = node
- elseif step.type == 'push' then
- if step.copy then
- node = node:copy()
- end
- topNode = node
- elseif step.type == 'merge' then
- node:merge(step.ref2.node)
- elseif step.type == 'cast' then
- topNode = node:copy()
- for _, cast in ipairs(step.cast.casts) do
- if cast.mode == '+' then
- if cast.optional then
- topNode:addOptional()
- end
- if cast.extends then
- topNode:merge(vm.compileNode(cast.extends))
- end
- elseif cast.mode == '-' then
- if cast.optional then
- topNode:removeOptional()
- end
- if cast.extends then
- topNode:removeNode(vm.compileNode(cast.extends))
- end
- else
- if cast.extends then
- topNode:clear()
- topNode:merge(vm.compileNode(cast.extends))
- end
- end
- end
+---@param block parser.object
+---@param node vm.node
+function mt:_launchBlock(block, node)
+ local top, topNode = self:_fastWard(block.start, node)
+ if not top then
+ return
+ end
+ for _, action in ipairs(block) do
+ if action.finish < top.start then
+ goto CONTINUE
+ end
+ top, topNode = self:_fastWard(action.finish, topNode)
+ if not top then
+ return
end
+ ::CONTINUE::
end
+ self:_fastWard(math.huge, topNode)
end
---@param loc parser.object
----@return vm.runner
-function vm.createRunner(loc)
+---@param callback vm.runner.callback
+function vm.launchRunner(loc, callback)
local self = setmetatable({
- loc = loc,
- mainBlock = guide.getParentBlock(loc),
- blocks = {},
- steps = {},
+ _loc = loc,
+ _objs = {},
+ _callback = callback,
}, mt)
- self:_preCompile()
+ self:_collect()
+
+ if #self._objs == 0 then
+ return
+ end
- return self
+ self:_launchBlock(guide.getParentBlock(loc), vm.getNode(loc))
end