diff options
Diffstat (limited to 'script/vm/global.lua')
-rw-r--r-- | script/vm/global.lua | 431 |
1 files changed, 425 insertions, 6 deletions
diff --git a/script/vm/global.lua b/script/vm/global.lua index 1c46c9a3..a54ab552 100644 --- a/script/vm/global.lua +++ b/script/vm/global.lua @@ -1,5 +1,9 @@ -local util = require 'utility' -local scope= require 'workspace.scope' +local util = require 'utility' +local scope = require 'workspace.scope' +local guide = require 'parser.guide' +local files = require 'files' +---@class vm +local vm = require 'vm.vm' ---@class vm.global.link ---@field gets parser.object[] @@ -15,8 +19,6 @@ mt.__index = mt mt.type = 'global' mt.name = '' -local ID_SPLITE = '\x1F' - ---@param uri uri ---@param source parser.object function mt:addSet(uri, source) @@ -106,7 +108,7 @@ end ---@return string function mt:getKeyName() - return self.name:match('[^' .. ID_SPLITE .. ']+$') + return self.name:match('[^' .. vm.ID_SPLITE .. ']+$') end ---@return boolean @@ -116,10 +118,427 @@ end ---@param cate vm.global.cate ---@return vm.global -return function (name, cate) +local function createGlobal(name, cate) return setmetatable({ name = name, cate = cate, links = util.multiTable(2), }, mt) end + +---@class parser.object +---@field _globalNode vm.global + +---@type table<string, vm.global> +local allGlobals = {} +---@type table<uri, table<string, boolean>> +local globalSubs = util.multiTable(2) + +local compileObject +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 + compileObject(ref) + end + end + end) + : case 'getlocal' + : call(function (source) + if source.special ~= '_G' then + return + end + if not source.next then + return + end + compileObject(source.next) + end) + : case 'setglobal' + : call(function (source) + local uri = guide.getUri(source) + local name = guide.getKeyName(source) + local global = vm.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 = vm.declareGlobal('variable', name, uri) + global:addGet(uri, source) + source._globalNode = global + + local nxt = source.next + if nxt then + 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 = vm.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 = vm.declareGlobal('variable', name, uri) + global:addGet(uri, source) + source._globalNode = global + + local nxt = source.next + if nxt then + 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 = vm.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 + 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 = vm.declareGlobal('type', name, uri) + class:addSet(uri, source) + source._globalNode = class + + if source.signs then + source._sign = vm.createSign() + 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 = vm.createGeneric(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 = vm.declareGlobal('type', name, uri) + alias:addSet(uri, source) + source._globalNode = alias + + if source.signs then + source._sign = vm.createSign() + for _, sign in ipairs(source.signs) do + source._sign:addSign(vm.compileNode(sign)) + end + source.extends._generic = vm.createGeneric(source.extends, source._sign) + end + end) + : case 'doc.type.name' + : call(function (source) + local uri = guide.getUri(source) + local name = source[1] + local type = vm.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 = vm.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 vm.declareGlobal(cate, name, uri) + local key = cate .. '|' .. name + if uri then + globalSubs[uri][key] = true + end + if not allGlobals[key] then + allGlobals[key] = createGlobal(name, cate) + end + return allGlobals[key] +end + +---@param cate vm.global.cate +---@param name string +---@param field? string +---@return vm.global? +function vm.getGlobal(cate, name, field) + local key = cate .. '|' .. name + if field then + key = key .. vm.ID_SPLITE .. field + end + return allGlobals[key] +end + +---@param cate vm.global.cate +---@param name string +---@return vm.global[] +function vm.getGlobalFields(cate, name) + local globals = {} + local key = cate .. '|' .. name + + local clock = os.clock() + for gid, global in pairs(allGlobals) 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 vm.getGlobals(cate) + local globals = {} + + local clock = os.clock() + for gid, global in pairs(allGlobals) 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 vm.getGlobalSets(suri, cate) + local globals = vm.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 vm.hasGlobalSets(suri, cate, name) + local global = vm.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 compileObject(source) + if source._globalNode ~= nil then + return + end + source._globalNode = false + compilerGlobalSwitch(source.type, source) +end + +---@param source parser.object +local function 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 = vm.getLocalFields(source) + if not fields then + return + end + local nodeLocalID = vm.getLocalID(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 + vm.insertLocalID(myID, field) + end + if globalNode then + local myID = globalNode:getName() .. vm.ID_SPLITE .. key + local myGlobal = vm.declareGlobal('variable', myID, guide.getUri(node)) + myGlobal:addSet(guide.getUri(node), field) + end + end + end + end +end + +---@param source parser.object +local function compileAst(source) + local env = guide.getENV(source) + if not env then + return + end + compileObject(env) + guide.eachSpecialOf(source, 'rawset', function (src) + compileObject(src.parent) + end) + guide.eachSpecialOf(source, 'rawget', function (src) + compileObject(src.parent) + end) + guide.eachSourceTypes(source.docs, { + 'doc.class', + 'doc.alias', + 'doc.type.name', + 'doc.extends.name', + }, function (src) + compileObject(src) + end) + + --[[ + local mt + function mt:xxx() + self.a = 1 + end + + mt.a --> find this definition + ]] + guide.eachSourceType(source, 'self', function (src) + compileSelf(src) + end) +end + +---@param uri uri +local function dropUri(uri) + local globalSub = globalSubs[uri] + globalSubs[uri] = nil + for key in pairs(globalSub) do + local global = allGlobals[key] + if global then + global:dropUri(uri) + if not global:isAlive() then + allGlobals[key] = nil + end + end + end +end + +for uri in files.eachFile() do + local state = files.getState(uri) + if state then + compileAst(state.ast) + end +end + +files.watch(function (ev, uri) + if ev == 'update' then + dropUri(uri) + local state = files.getState(uri) + if state then + compileAst(state.ast) + end + end + if ev == 'remove' then + dropUri(uri) + end +end) |