local util = require 'utility' local guide = require 'parser.guide' local globalBuilder = require 'vm.global' local signMgr = require 'vm.sign' local genericMgr = require 'vm.generic' local localID = require 'vm.local-id' ---@class vm local vm = require 'vm.vm' ---@class parser.object ---@field _globalNode? vm.global ---@class vm.global-manager local m = {} ---@type table m.globals = {} ---@type table> m.globalSubs = util.multiTable(2) local compilerGlobalSwitch = util.switch() : case 'local' : call(function (source) if source.special ~= '_G' then return end if source.ref then for _, ref in ipairs(source.ref) do m.compileObject(ref) end end end) : case 'getlocal' : call(function (source) if source.special ~= '_G' then return end if not source.next then return end m.compileObject(source.next) end) : case 'setglobal' : call(function (source) local uri = guide.getUri(source) local name = guide.getKeyName(source) local global = m.declareGlobal('variable', name, uri) global:addSet(uri, source) source._globalNode = global end) : case 'getglobal' : call(function (source) local uri = guide.getUri(source) local name = guide.getKeyName(source) local global = m.declareGlobal('variable', name, uri) global:addGet(uri, source) source._globalNode = global local nxt = source.next if nxt then m.compileObject(nxt) end end) : case 'setfield' : case 'setmethod' : case 'setindex' ---@param source parser.object : call(function (source) local name local keyName = guide.getKeyName(source) if not keyName then return end if source.node._globalNode then local parentName = source.node._globalNode:getName() if parentName == '_G' then name = keyName else name = ('%s%s%s'):format(parentName, vm.ID_SPLITE, keyName) end elseif source.node.special == '_G' then name = keyName end if not name then return end local uri = guide.getUri(source) local global = m.declareGlobal('variable', name, uri) global:addSet(uri, source) source._globalNode = global end) : case 'getfield' : case 'getmethod' : case 'getindex' ---@param source parser.object : call(function (source) local name local keyName = guide.getKeyName(source) if not keyName then return end if source.node._globalNode then local parentName = source.node._globalNode:getName() if parentName == '_G' then name = keyName else name = ('%s%s%s'):format(parentName, vm.ID_SPLITE, keyName) end elseif source.node.special == '_G' then name = keyName end local uri = guide.getUri(source) local global = m.declareGlobal('variable', name, uri) global:addGet(uri, source) source._globalNode = global local nxt = source.next if nxt then m.compileObject(nxt) end end) : case 'call' : call(function (source) if source.node.special == 'rawset' or source.node.special == 'rawget' then if not source.args then return end local g = source.args[1] local key = source.args[2] if g and key and g.special == '_G' then local name = guide.getKeyName(key) if name then local uri = guide.getUri(source) local global = m.declareGlobal('variable', name, uri) if source.node.special == 'rawset' then global:addSet(uri, source) source.value = source.args[3] else global:addGet(uri, source) end source._globalNode = global local nxt = source.next if nxt then m.compileObject(nxt) end end end end end) : case 'doc.class' ---@param source parser.object : call(function (source) local uri = guide.getUri(source) local name = guide.getKeyName(source) local class = m.declareGlobal('type', name, uri) class:addSet(uri, source) source._globalNode = class if source.signs then source._sign = signMgr() for _, sign in ipairs(source.signs) do source._sign:addSign(vm.compileNode(sign)) end if source.extends then for _, ext in ipairs(source.extends) do if ext.type == 'doc.type.table' then ext._generic = genericMgr(ext, source._sign) end end end end end) : case 'doc.alias' : call(function (source) local uri = guide.getUri(source) local name = guide.getKeyName(source) local alias = m.declareGlobal('type', name, uri) alias:addSet(uri, source) source._globalNode = alias if source.signs then source._sign = signMgr() for _, sign in ipairs(source.signs) do source._sign:addSign(vm.compileNode(sign)) end source.extends._generic = genericMgr(source.extends, source._sign) end end) : case 'doc.type.name' : call(function (source) local uri = guide.getUri(source) local name = source[1] local type = m.declareGlobal('type', name, uri) type:addGet(uri, source) source._globalNode = type end) : case 'doc.extends.name' : call(function (source) local uri = guide.getUri(source) local name = source[1] local class = m.declareGlobal('type', name, uri) class:addGet(uri, source) source._globalNode = class end) ---@alias vm.global.cate '"variable"' | '"type"' ---@param cate vm.global.cate ---@param name string ---@param uri uri ---@return vm.global function m.declareGlobal(cate, name, uri) local key = cate .. '|' .. name m.globalSubs[uri][key] = true if not m.globals[key] then m.globals[key] = globalBuilder(name, cate) end return m.globals[key] end ---@param cate vm.global.cate ---@param name string ---@param field? string ---@return vm.global? function m.getGlobal(cate, name, field) local key = cate .. '|' .. name if field then key = key .. vm.ID_SPLITE .. field end return m.globals[key] end ---@param cate vm.global.cate ---@param name string ---@return vm.global[] function m.getFields(cate, name) local globals = {} local key = cate .. '|' .. name -- TODO: optimize local clock = os.clock() for gid, global in pairs(m.globals) do if gid ~= key and util.stringStartWith(gid, key) and gid:sub(#key + 1, #key + 1) == vm.ID_SPLITE and not gid:find(vm.ID_SPLITE, #key + 2) then globals[#globals+1] = global end end local cost = os.clock() - clock if cost > 0.1 then log.warn('global-manager getFields cost %.3f', cost) end return globals end ---@param cate vm.global.cate ---@return vm.global[] function m.getGlobals(cate) local globals = {} -- TODO: optimize local clock = os.clock() for gid, global in pairs(m.globals) do if util.stringStartWith(gid, cate) and not gid:find(vm.ID_SPLITE) then globals[#globals+1] = global end end local cost = os.clock() - clock if cost > 0.1 then log.warn('global-manager getGlobals cost %.3f', cost) end return globals end ---@param suri uri ---@param cate vm.global.cate ---@return parser.object[] function m.getGlobalSets(suri, cate) local globals = m.getGlobals(cate) local result = {} for _, global in ipairs(globals) do local sets = global:getSets(suri) for _, set in ipairs(sets) do result[#result+1] = set end end return result end ---@param suri uri ---@param cate vm.global.cate ---@param name string ---@return boolean function m.hasGlobalSets(suri, cate, name) local global = m.getGlobal(cate, name) if not global then return false end local sets = global:getSets(suri) if #sets == 0 then return false end return true end ---@param source parser.object function m.compileObject(source) if source._globalNode ~= nil then return end source._globalNode = false compilerGlobalSwitch(source.type, source) end ---@param source parser.object function m.compileSelf(source) if source.parent.type ~= 'funcargs' then return end ---@type parser.object local node = source.parent.parent and source.parent.parent.parent and source.parent.parent.parent.node if not node then return end local fields = localID.getFields(source) if not fields then return end local nodeLocalID = localID.getID(node) local globalNode = node._globalNode if not nodeLocalID and not globalNode then return end for _, field in ipairs(fields) do if field.type == 'setfield' then local key = guide.getKeyName(field) if key then if nodeLocalID then local myID = nodeLocalID .. vm.ID_SPLITE .. key localID.insertLocalID(myID, field) end if globalNode then local myID = globalNode:getName() .. vm.ID_SPLITE .. key local myGlobal = m.declareGlobal('variable', myID, guide.getUri(node)) myGlobal:addSet(guide.getUri(node), field) end end end end end ---@param source parser.object function m.compileAst(source) local env = guide.getENV(source) m.compileObject(env) guide.eachSpecialOf(source, 'rawset', function (src) m.compileObject(src.parent) end) guide.eachSpecialOf(source, 'rawget', function (src) m.compileObject(src.parent) end) guide.eachSourceTypes(source.docs, { 'doc.class', 'doc.alias', 'doc.type.name', 'doc.extends.name', }, function (src) m.compileObject(src) end) --[[ local mt function mt:xxx() self.a = 1 end mt.a --> find this definition ]] guide.eachSourceType(source, 'self', function (src) m.compileSelf(src) end) end ---@return vm.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 key in pairs(globalSub) do local global = m.globals[key] if global then global:dropUri(uri) if not global:isAlive() then m.globals[key] = nil end end end end return m