diff options
author | AlexCai2019 <89138532+AlexCai2019@users.noreply.github.com> | 2022-05-08 01:43:28 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-05-08 01:43:28 +0800 |
commit | 0fd83c4ca9f82a02becab6c304a8a7de75098507 (patch) | |
tree | be9790d9d4823fe728c5b36e94093fe5f42b7725 /script/vm/runner.lua | |
parent | 89203efad8c9b5513e05ca4d5696107924865b10 (diff) | |
parent | 67b4c574849d1667e0ecb39c51aeed8e30b43056 (diff) | |
download | lua-language-server-0fd83c4ca9f82a02becab6c304a8a7de75098507.zip |
Merge branch 'sumneko:master' into master
Diffstat (limited to 'script/vm/runner.lua')
-rw-r--r-- | script/vm/runner.lua | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/script/vm/runner.lua b/script/vm/runner.lua new file mode 100644 index 00000000..9fe0f172 --- /dev/null +++ b/script/vm/runner.lua @@ -0,0 +1,444 @@ +---@class vm +local vm = require 'vm.vm' +local guide = require 'parser.guide' + +---@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 _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 + +---@return parser.object[] +function mt:_getCasts() + local root = guide.getRoot(self.loc) + if not root._casts then + root._casts = {} + local docs = root.docs + for _, doc in ipairs(docs) do + if doc.type == 'doc.cast' and doc.loc then + root._casts[#root._casts+1] = doc + end + end + end + return root._casts +end + +function mt:_preCompile() + 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 + 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 + end + + local casts = self:_getCasts() + for _, cast in ipairs(casts) do + 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, + } + 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 + end) +end + +---@param loc parser.object +---@param node vm.node +---@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 + 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 + end + 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 |