diff options
Diffstat (limited to 'script/vm')
-rw-r--r-- | script/vm/eachDef.lua | 11 | ||||
-rw-r--r-- | script/vm/getDef.lua | 175 | ||||
-rw-r--r-- | script/vm/getDocs.lua | 3 | ||||
-rw-r--r-- | script/vm/getGlobals.lua | 1 | ||||
-rw-r--r-- | script/vm/getRef.lua (renamed from script/vm/eachRef.lua) | 1 | ||||
-rw-r--r-- | script/vm/global-node.lua | 142 | ||||
-rw-r--r-- | script/vm/init.lua | 5 | ||||
-rw-r--r-- | script/vm/local-id.lua | 116 | ||||
-rw-r--r-- | script/vm/node/class.lua | 11 | ||||
-rw-r--r-- | script/vm/node/compiler.lua | 190 | ||||
-rw-r--r-- | script/vm/node/global.lua | 79 | ||||
-rw-r--r-- | script/vm/node/union.lua | 49 | ||||
-rw-r--r-- | script/vm/state.lua | 80 | ||||
-rw-r--r-- | script/vm/vm.lua | 3 |
14 files changed, 846 insertions, 20 deletions
diff --git a/script/vm/eachDef.lua b/script/vm/eachDef.lua deleted file mode 100644 index ea14ed9f..00000000 --- a/script/vm/eachDef.lua +++ /dev/null @@ -1,11 +0,0 @@ ----@class vm -local vm = require 'vm.vm' -local searcher = require 'core.searcher' - -function vm.getDefs(source, field) - return searcher.requestDefinition(source, field) -end - -function vm.getAllDefs(source, field) - return searcher.requestAllDefinition(source, field) -end diff --git a/script/vm/getDef.lua b/script/vm/getDef.lua new file mode 100644 index 00000000..df180420 --- /dev/null +++ b/script/vm/getDef.lua @@ -0,0 +1,175 @@ +---@class vm +local vm = require 'vm.vm' +local util = require 'utility' +local compiler = require 'vm.node.compiler' +local guide = require 'parser.guide' +local localID = require 'vm.local-id' +local globalNode = require 'vm.global-node' + +local simpleMap + +local function searchGetLocal(source, node, pushResult) + local key = guide.getKeyName(source) + for _, ref in ipairs(node.node.ref) do + if ref.type == 'getlocal' + and guide.isSet(ref.next) + and guide.getKeyName(ref.next) == key then + pushResult(ref.next) + end + end +end + +simpleMap = util.switch() + : case 'local' + : call(function (source, pushResult) + pushResult(source) + if source.ref then + for _, ref in ipairs(source.ref) do + if ref.type == 'setlocal' then + pushResult(ref) + end + end + end + + if source.dummy then + for _, res in ipairs(vm.getDefs(source.method.node)) do + pushResult(res) + end + end + end) + : case 'getlocal' + : case 'setlocal' + : call(function (source, pushResult) + simpleMap['local'](source.node, pushResult) + end) + : case 'field' + : call(function (source, pushResult) + local parent = source.parent + simpleMap[parent.type](parent, pushResult) + end) + : case 'setfield' + : case 'getfield' + : call(function (source, pushResult) + local node = source.node + if node.type == 'getlocal' then + searchGetLocal(source, node, pushResult) + return + end + end) + : case 'getindex' + : case 'setindex' + : call(function (source, pushResult) + local node = source.node + if node.type == 'getlocal' then + searchGetLocal(source, node, pushResult) + end + end) + : getMap() + +local searchFieldMap = util.switch() + : case 'table' + : call(function (node, key, pushResult) + for _, field in ipairs(node) do + if field.type == 'tablefield' + or field.type == 'tableindex' then + if guide.getKeyName(field) == key then + pushResult(field) + end + end + end + end) + : getMap() + +local nodeMap;nodeMap = util.switch() + : case 'field' + : call(function (source, pushResult) + local parent = source.parent + nodeMap[parent.type](parent, pushResult) + end) + : case 'getfield' + : case 'setfield' + : case 'getmethod' + : case 'setmethod' + : case 'getindex' + : case 'setindex' + : call(function (source, pushResult) + local node = compiler.compileNode(source.node) + if not node then + return + end + if searchFieldMap[node.type] then + searchFieldMap[node.type](node, guide.getKeyName(source), pushResult) + end + end) + : getMap() + + ---@param source parser.object + ---@param pushResult fun(src: parser.object) +local function searchBySimple(source, pushResult) + local simple = simpleMap[source.type] + if simple then + simple(source, pushResult) + end +end + +---@param source parser.object +---@param pushResult fun(src: parser.object) +local function searchByGlobal(source, pushResult) + local global = globalNode.getNode(source) + if not global then + return + end + for _, src in ipairs(global:getSets()) do + pushResult(src) + end +end + +---@param source parser.object +---@param pushResult fun(src: parser.object) +local function searchByID(source, pushResult) + local idSources = localID.getSources(source) + if not idSources then + return + end + for _, src in ipairs(idSources) do + if guide.isSet(src) then + pushResult(src) + end + end +end + +---@param source parser.object +---@param pushResult fun(src: parser.object) +local function searchByNode(source, pushResult) + local node = nodeMap[source.type] + if node then + node(source, pushResult) + end +end + +---@param source parser.object +---@return parser.object[] +function vm.getDefs(source) + local results = {} + local mark = {} + + local function pushResult(src) + if not mark[src] then + mark[src] = true + results[#results+1] = src + end + end + + searchBySimple(source, pushResult) + searchByGlobal(source, pushResult) + searchByID(source, pushResult) + searchByNode(source, pushResult) + + return results +end + +---@param source parser.object +---@return parser.object[] +function vm.getAllDefs(source) + return vm.getDefs(source) +end diff --git a/script/vm/getDocs.lua b/script/vm/getDocs.lua index 4aeda446..4204d785 100644 --- a/script/vm/getDocs.lua +++ b/script/vm/getDocs.lua @@ -5,11 +5,10 @@ local vm = require 'vm.vm' local config = require 'config' local collector = require 'core.collector' 'searcher' local define = require 'proto.define' -local noder = require 'core.noder' ---获取class与alias ---@param name? string ----@return parser.guide.object[] +---@return parser.object[] function vm.getDocDefines(uri, name) if type(name) ~= 'string' then return {} diff --git a/script/vm/getGlobals.lua b/script/vm/getGlobals.lua index 8af21d45..2ba734ba 100644 --- a/script/vm/getGlobals.lua +++ b/script/vm/getGlobals.lua @@ -2,7 +2,6 @@ local collector = require 'core.collector' 'searcher' local guide = require 'parser.guide' ---@class vm local vm = require 'vm.vm' -local noder = require 'core.noder' function vm.hasGlobalSets(uri, name) local id diff --git a/script/vm/eachRef.lua b/script/vm/getRef.lua index 899c04c6..ded95b61 100644 --- a/script/vm/eachRef.lua +++ b/script/vm/getRef.lua @@ -1,6 +1,5 @@ ---@class vm local vm = require 'vm.vm' -local searcher = require 'core.searcher' function vm.getRefs(source, field) return searcher.requestReference(source, field) diff --git a/script/vm/global-node.lua b/script/vm/global-node.lua new file mode 100644 index 00000000..6235ff6b --- /dev/null +++ b/script/vm/global-node.lua @@ -0,0 +1,142 @@ +local util = require 'utility' +local guide = require 'parser.guide' +local globalBuilder = require 'vm.node.global' + +---@class parser.object +---@field _globalNode vm.node.global + +---@class vm.global-node +local m = {} +---@type table<string, vm.node.global> +m.globals = util.defaultTable(globalBuilder) +---@type table<uri, table<string, boolean>> +m.globalSubs = util.defaultTable(function () + return {} +end) + +m.ID_SPLITE = '\x1F' + +local compilerGlobalMap = util.switch() + : case 'local' + : call(function (uri, source) + if source.tag ~= '_ENV' then + return + end + if source.ref then + for _, ref in ipairs(source.ref) do + m.compileNode(uri, ref) + end + end + end) + : case 'setglobal' + : call(function (uri, source) + local name = guide.getKeyName(source) + source._globalNode = m.declareGlobal(name, uri, source) + end) + : case 'getglobal' + : call(function (uri, source) + local name = guide.getKeyName(source) + local global = m.getGlobal(name) + global:addGet(uri, source) + source._globalNode = global + + local nxt = source.next + if nxt then + m.compileNode(uri, nxt) + end + end) + : case 'setfield' + : case 'setmethod' + : case 'setindex' + ---@param uri uri + ---@param source parser.object + : call(function (uri, source) + local parent = source.node._globalNode + if not parent then + return + end + local name = parent:getName() .. m.ID_SPLITE .. guide.getKeyName(source) + source._globalNode = m.declareGlobal(name, uri, source) + end) + : case 'getfield' + : case 'getmethod' + : case 'getindex' + ---@param uri uri + ---@param source parser.object + : call(function (uri, source) + local parent = source.node._globalNode + if not parent then + return + end + local name = parent:getName() .. m.ID_SPLITE .. guide.getKeyName(source) + local global = m.getGlobal(name) + global:addGet(uri, source) + source._globalNode = global + + local nxt = source.next + if nxt then + m.compileNode(uri, nxt) + end + end) + : getMap() + + +---@param name string +---@param uri uri +---@param source parser.object +---@return vm.node.global +function m.declareGlobal(name, uri, source) + m.globalSubs[uri][name] = true + local node = m.globals[name] + node:addSet(uri, source) + return node +end + +---@param name string +---@param uri? uri +---@return vm.node.global +function m.getGlobal(name, uri) + if uri then + m.globalSubs[uri][name] = true + end + return m.globals[name] +end + +---@param source parser.object +function m.compileNode(uri, source) + if source._globalNode ~= nil then + return + end + source._globalNode = false + local compiler = compilerGlobalMap[source.type] + if compiler then + compiler(uri, source) + end +end + +---@param source parser.object +function m.compileAst(source) + local uri = guide.getUri(source) + local env = guide.getENV(source) + m.compileNode(uri, env) +end + +---@return vm.node.global +function m.getNode(source) + if source.type == 'field' + or source.type == 'method' then + source = source.parent + end + return source._globalNode +end + +---@param uri uri +function m.dropUri(uri) + local globalSub = m.globalSubs[uri] + m.globalSubs[uri] = nil + for name in pairs(globalSub) do + m.globals[name]:dropUri(uri) + end +end + +return m diff --git a/script/vm/init.lua b/script/vm/init.lua index 935f39e3..5a5de99e 100644 --- a/script/vm/init.lua +++ b/script/vm/init.lua @@ -1,8 +1,9 @@ local vm = require 'vm.vm' +require 'vm.state' require 'vm.getGlobals' require 'vm.getDocs' require 'vm.getLibrary' -require 'vm.eachDef' -require 'vm.eachRef' +require 'vm.getDef' +require 'vm.getRef' require 'vm.getLinks' return vm diff --git a/script/vm/local-id.lua b/script/vm/local-id.lua new file mode 100644 index 00000000..8487d96a --- /dev/null +++ b/script/vm/local-id.lua @@ -0,0 +1,116 @@ +local util = require 'utility' +local guide = require 'parser.guide' + +---@class parser.object +---@field _localID string +---@field _localIDs table<string, parser.object[]> + +---@class vm.local-id +local m = {} + +m.ID_SPLITE = '\x1F' + +local compileMap = util.switch() + : case 'local' + : call(function (source) + if not source.ref then + return + end + for _, ref in ipairs(source.ref) do + m.compileLocalID(ref) + end + end) + : case 'getlocal' + : call(function (source) + source._localID = ('%d'):format(source.node.start) + m.compileLocalID(source.next) + end) + : case 'getfield' + : case 'setfield' + : call(function (source) + local parentID = source.node._localID + if not parentID then + return + end + source._localID = parentID .. m.ID_SPLITE .. guide.getKeyName(source) + source.field._localID = source._localID + if source.type == 'getfield' then + m.compileLocalID(source.next) + end + end) + : getMap() + +local leftMap = util.switch() + : case 'field' + : call(function (source) + return m.getLocal(source.parent) + end) + : case 'getfield' + : case 'setfield' + : call(function (source) + return m.getLocal(source.node) + end) + : case 'getlocal' + : call(function (source) + return source.node + end) + : getMap() + +---@param source parser.object +---@return parser.object? +function m.getLocal(source) + local getLeft = leftMap[source.type] + if getLeft then + return getLeft(source) + end + return nil +end + +function m.compileLocalID(source) + if not source then + return + end + source._localID = false + local compiler = compileMap[source.type] + if not compiler then + return + end + compiler(source) + local root = guide.getRoot(source) + if not root._localIDs then + root._localIDs = util.multiTable(2) + end + local sources = root._localIDs[source._localID] + sources[#sources+1] = source +end + +---@param source parser.object +---@return string|boolean +function m.getID(source) + if source._localID ~= nil then + return source._localID + end + source._localID = false + local loc = m.getLocal(source) + if not loc then + return source._localID + end + m.compileLocalID(loc) + return source._localID +end + +---@param source parser.object +---@return parser.object[]? +function m.getSources(source) + local id = m.getID(source) + if not id then + return nil + end + local root = guide.getRoot(source) + if not root._localIDs then + return nil + end + return root._localIDs[id] +end + +return m diff --git a/script/vm/node/class.lua b/script/vm/node/class.lua new file mode 100644 index 00000000..ee8b1ca8 --- /dev/null +++ b/script/vm/node/class.lua @@ -0,0 +1,11 @@ +---@class vm.node.class +local mt = {} +mt.__index = mt +mt.type = 'class' + +---@return vm.node.class +return function () + local class = setmetatable({ + }, mt) + return class +end diff --git a/script/vm/node/compiler.lua b/script/vm/node/compiler.lua new file mode 100644 index 00000000..dfe4bc0c --- /dev/null +++ b/script/vm/node/compiler.lua @@ -0,0 +1,190 @@ +local guide = require 'parser.guide' +local util = require 'utility' +local state = require 'vm.state' +local union = require 'vm.node.union' +local localID = require 'vm.local-id' + +---@class parser.object +---@field _compiledNodes boolean +---@field _node vm.node + +---@class vm.node.compiler +local m = {} + +---@class vm.node.cross + +---@alias vm.node parser.object | vm.node.union | vm.node.cross | vm.node.global + +function m.setNode(source, node) + if not node then + return + end + local me = source._node + if not me then + source._node = node + return + end + if me.type == 'union' + or me.type == 'cross' then + me:merge(node) + return + end + source._node = union(me, node) +end + +function m.eachNode(node) + if node.type == 'union' then + return node:eachNode() + end + local first = true + return function () + if first then + first = false + return node + end + return nil + end +end + +local function getReturnOfFunction(func, index) + if not func._returns then + func._returns = util.defaultTable(function () + return { + type = 'function.return', + parent = func, + index = index, + } + end) + end + return m.compileNode(func._returns[index]) +end + +local function getReturn(func, index) + local node = m.compileNode(func) + if not node then + return + end + for cnode in m.eachNode(node) do + if cnode.type == 'function' then + return getReturnOfFunction(cnode, index) + end + end +end + +local function compileByLocalID(source) + local sources = localID.getSources(source) + if not sources then + return + end + for _, src in ipairs(sources) do + if src.value then + m.setNode(source, m.compileNode(src.value)) + end + end +end + +local searchFieldMap = util.switch() + : case 'table' + : call(function (node, key, pushResult) + for _, field in ipairs(node) do + if field.type == 'tablefield' + or field.type == 'tableindex' then + if guide.getKeyName(field) == key then + pushResult(field) + end + end + end + end) + : getMap() + +local function compileByParentNode(source) + local parentNode = m.compileNode(source.node) + if not parentNode then + return + end + local key = guide.getKeyName(source) + local f = searchFieldMap[parentNode.type] + if f then + f(parentNode, key, function (field) + m.setNode(source, m.compileNode(field.value)) + end) + end +end + +local compilerMap = util.switch() + : case 'boolean' + : case 'table' + : case 'integer' + : case 'number' + : case 'string' + : case 'function' + : call(function (source) + m.setNode(source, state.declareLiteral(source)) + end) + : case 'local' + : call(function (source) + if source.value then + m.setNode(source, m.compileNode(source.value)) + end + if source.ref then + for _, ref in ipairs(source.ref) do + if ref.type == 'setlocal' then + m.setNode(source, m.compileNode(ref.value)) + end + end + end + end) + : case 'getlocal' + : call(function (source) + m.setNode(source, m.compileNode(source.node)) + end) + : case 'setfield' + : case 'setmethod' + : case 'setindex' + : call(function (source) + compileByLocalID(source) + end) + : case 'getfield' + : case 'getmethod' + : case 'getindex' + : call(function (source) + compileByLocalID(source) + compileByParentNode(source) + end) + : case 'function.return' + : call(function (source) + local func = source.parent + local index = source.index + if func.returns then + for _, rtn in ipairs(func.returns) do + if rtn[index] then + m.setNode(source, m.compileNode(rtn[index])) + end + end + end + end) + : case 'select' + : call(function (source, value) + local vararg = value.vararg + if vararg.type == 'call' then + m.setNode(source, getReturn(vararg.node, value.sindex)) + end + end) + : getMap() + +---@param source parser.object +---@return vm.node +function m.compileNode(source) + if source._node then + return source._node + end + source._node = false + local compiler = compilerMap[source.type] + if compiler then + compiler(source) + end + state.subscribeLiteral(source, source._node) + return source._node +end + +return m diff --git a/script/vm/node/global.lua b/script/vm/node/global.lua new file mode 100644 index 00000000..499e526b --- /dev/null +++ b/script/vm/node/global.lua @@ -0,0 +1,79 @@ +local util = require 'utility' + +---@class vm.node.global.link +---@field gets parser.object[] +---@field sets parser.object[] + +---@class vm.node.global +---@field links table<uri, vm.node.global.link> +---@field setsCache parser.object[] +---@field getsCache parser.object[] +local mt = {} +mt.__index = mt +mt.type = 'global' +mt.name = '' + +---@param uri uri +---@param source parser.object +function mt:addSet(uri, source) + local link = self.links[uri] + link.sets[#link.sets+1] = source +end + +---@param uri uri +---@param source parser.object +function mt:addGet(uri, source) + local link = self.links[uri] + link.gets[#link.gets+1] = source +end + +---@return parser.object[] +function mt:getSets() + if not self.setsCache then + self.setsCache = {} + for _, link in pairs(self.links) do + for _, source in ipairs(link.sets) do + self.setsCache[#self.setsCache+1] = source + end + end + end + return self.setsCache +end + +---@return parser.object[] +function mt:getGets() + if not self.getsCache then + self.getsCache = {} + for _, link in pairs(self.links) do + for _, source in ipairs(link.gets) do + self.getsCache[#self.getsCache+1] = source + end + end + end + return self.getsCache +end + +---@param uri uri +function mt:dropUri(uri) + self.links[uri] = nil + self.setsCache = nil + self.getsCache = nil +end + +---@return string +function mt:getName() + return self.name +end + +---@return vm.node.global +return function (name) + return setmetatable({ + name = name, + links = util.defaultTable(function () + return { + sets = {}, + gets = {}, + } + end), + }, mt) +end diff --git a/script/vm/node/union.lua b/script/vm/node/union.lua new file mode 100644 index 00000000..a8c917d9 --- /dev/null +++ b/script/vm/node/union.lua @@ -0,0 +1,49 @@ +local state = require 'vm.state' + +---@class vm.node.union +local mt = {} +mt.__index = mt +mt.type = 'union' + +---@param node vm.node +function mt:merge(node) + if not node then + return + end + if node.type == 'union' then + for _, c in ipairs(node) do + self[#self+1] = c + end + else + self[#self+1] = node + end +end + +---@param source parser.object +function mt:subscribeLiteral(source) + for _, c in ipairs(self) do + state.subscribeLiteral(source, c) + if c.type == 'cross' then + c:subscribeLiteral(source) + end + end +end + +function mt:eachNode() + local i = 0 + return function () + i = i + 1 + return self[i] + end +end + +---@param me parser.object +---@param node vm.node +---@return vm.node.union +return function (me, node) + local union = setmetatable({ + [1] = me, + }, mt) + union:merge(node) + return union +end diff --git a/script/vm/state.lua b/script/vm/state.lua new file mode 100644 index 00000000..b0689384 --- /dev/null +++ b/script/vm/state.lua @@ -0,0 +1,80 @@ +local util = require 'utility' +local files = require 'files' +local globalNode = require 'vm.global-node' +local guide = require 'parser.guide' + +---@class vm.state +local m = {} +---@type table<uri, parser.object[]> +m.literals = util.multiTable(2) +---@type table<parser.object, table<parser.object, boolean>> +m.literalSubs = util.multiTable(2, function () + return setmetatable({}, util.MODE_K) +end) +---@type table<parser.object, boolean> +m.allLiterals = {} + +---@param source parser.object +function m.declareLiteral(source) + if m.allLiterals[source] then + return + end + m.allLiterals[source] = true + local uri = guide.getUri(source) + local literals = m.literals[uri] + literals[#literals+1] = source +end + +---@param source parser.object +---@param node vm.node +function m.subscribeLiteral(source, node) + if not node then + return + end + if node.type == 'union' + or node.type == 'cross' then + node:subscribeLiteral(source) + return + end + if not m.allLiterals[source] then + return + end + m.literalSubs[node][source] = true +end + +---@param uri uri +function m.dropUri(uri) + local literals = m.literals[uri] + m.literals[uri] = nil + for _, literal in ipairs(literals) do + m.allLiterals[literal] = nil + local literalSubs = m.literalSubs[literal] + m.literalSubs[literal] = nil + for source in pairs(literalSubs) do + source._node = nil + end + end +end + +for uri in files.eachFile() do + local state = files.getState(uri) + if state then + globalNode.compileAst(state.ast) + end +end + +files.watch(function (ev, uri) + if ev == 'update' then + local state = files.getState(uri) + if state then + globalNode.compileAst(state.ast) + end + end + if ev == 'remove' then + m.dropUri(uri) + globalNode.dropUri(uri) + end +end) + + +return m diff --git a/script/vm/vm.lua b/script/vm/vm.lua index aa18ea73..ff893f24 100644 --- a/script/vm/vm.lua +++ b/script/vm/vm.lua @@ -1,11 +1,8 @@ local guide = require 'parser.guide' -local util = require 'utility' local files = require 'files' local timer = require 'timer' local setmetatable = setmetatable -local running = coroutine.running -local ipairs = ipairs local log = log local xpcall = xpcall local mathHuge = math.huge |