diff options
Diffstat (limited to 'script/src/vm')
-rw-r--r-- | script/src/vm/chain.lua | 65 | ||||
-rw-r--r-- | script/src/vm/emmy.lua | 372 | ||||
-rw-r--r-- | script/src/vm/function.lua | 551 | ||||
-rw-r--r-- | script/src/vm/global.lua | 25 | ||||
-rw-r--r-- | script/src/vm/init.lua | 1 | ||||
-rw-r--r-- | script/src/vm/ipairs.lua | 51 | ||||
-rw-r--r-- | script/src/vm/label.lua | 75 | ||||
-rw-r--r-- | script/src/vm/library.lua | 112 | ||||
-rw-r--r-- | script/src/vm/list.lua | 30 | ||||
-rw-r--r-- | script/src/vm/local.lua | 191 | ||||
-rw-r--r-- | script/src/vm/manager.lua | 17 | ||||
-rw-r--r-- | script/src/vm/module.lua | 56 | ||||
-rw-r--r-- | script/src/vm/multi.lua | 83 | ||||
-rw-r--r-- | script/src/vm/pcall.lua | 50 | ||||
-rw-r--r-- | script/src/vm/raw.lua | 30 | ||||
-rw-r--r-- | script/src/vm/source.lua | 183 | ||||
-rw-r--r-- | script/src/vm/special.lua | 130 | ||||
-rw-r--r-- | script/src/vm/value.lua | 634 | ||||
-rw-r--r-- | script/src/vm/vm.lua | 1334 |
19 files changed, 3990 insertions, 0 deletions
diff --git a/script/src/vm/chain.lua b/script/src/vm/chain.lua new file mode 100644 index 00000000..6e7c6ac7 --- /dev/null +++ b/script/src/vm/chain.lua @@ -0,0 +1,65 @@ +local valueMgr = require 'vm.value' +local sourceMgr = require 'vm.source' + +local mt = {} +mt.__index = mt +mt.type = 'chain' + +mt.min = 100 +mt.max = 100 +mt.count = 0 + +function mt:clearCache() + if self.count <= self.max then + return + end + local clock = os.clock() + local n = 0 + for uri, value in pairs(self.cache) do + local ok = value:eachInfo(function () + return true + end) + if ok then + n = n + 1 + else + value:getSource():kill() + self.cache[uri] = nil + end + end + self.count = n + self.max = self.count * 1.1 + 10 + if self.max < self.min then + self.max = self.min + end + local passed = os.clock() - clock + if passed > 0.1 then + log.warn(('chain:clearCache takes: [%.3f]sec, self.count: %d'):format(passed, self.count)) + end +end + +function mt:get(uri) + if not self.cache[uri] then + self.count = self.count + 1 + self:clearCache() + self.cache[uri] = valueMgr.create('any', sourceMgr.dummy()) + self.cache[uri]:markGlobal() + self.cache[uri].uri = uri + end + return self.cache[uri] +end + +function mt:remove() + if self.removed then + return + end + self.removed = true + for _, value in pairs(self.cache) do + value:getSource():kill() + end +end + +return function () + return setmetatable({ + cache = {}, + }, mt) +end diff --git a/script/src/vm/emmy.lua b/script/src/vm/emmy.lua new file mode 100644 index 00000000..9342a851 --- /dev/null +++ b/script/src/vm/emmy.lua @@ -0,0 +1,372 @@ +local functionMgr = require 'vm.function' +local library = require 'vm.library' +local mt = require 'vm.manager' + +function mt:clearEmmy() + self._emmy = nil + self._emmyParams = nil + self._emmyReturns = nil + self._emmyGeneric = nil + self._emmyComment = nil + self._emmyOverLoads = nil +end + +function mt:doEmmy(action) + local tp = action.type + if tp == 'emmyClass' then + self:doEmmyClass(action) + elseif tp == 'emmyType' then + self:doEmmyType(action) + elseif tp == 'emmyAlias' then + self:doEmmyAlias(action) + elseif tp == 'emmyParam' then + self:doEmmyParam(action) + elseif tp == 'emmyReturn' then + self:doEmmyReturn(action) + elseif tp == 'emmyField' then + self:doEmmyField(action) + elseif tp == 'emmyGeneric' then + self:doEmmyGeneric(action) + elseif tp == 'emmyVararg' then + self:doEmmyVararg(action) + elseif tp == 'emmyLanguage' then + elseif tp == 'emmyArrayType' then + self:doEmmyArrayType(action) + elseif tp == 'emmyTableType' then + self:doEmmyTableType(action) + elseif tp == 'emmyFunctionType' then + self:doEmmyFunctionType(action) + elseif tp == 'emmySee' then + self:doEmmySee(action) + elseif tp == 'emmyOverLoad' then + self:doEmmyOverLoad(action) + elseif tp == 'emmyIncomplete' then + self:doEmmyIncomplete(action) + elseif tp == 'emmyComment' then + self:doEmmyComment(action) + end +end + +function mt:getEmmy() + local emmy = self._emmy + self._emmy = nil + return emmy +end + +function mt:addEmmyParam(param) + if not self._emmyParams then + self._emmyParams = {} + end + self._emmyParams[#self._emmyParams+1] = param +end + +function mt:addEmmyReturn(rtn) + if not self._emmyReturns then + self._emmyReturns = {} + end + self._emmyReturns[#self._emmyReturns+1] = rtn +end + +function mt:addEmmyOverLoad(funcObj) + if not self._emmyOverLoads then + self._emmyOverLoads = {} + end + self._emmyOverLoads[#self._emmyOverLoads+1] = funcObj +end + +function mt:getEmmyParams() + local params = self._emmyParams + self._emmyParams = nil + return params +end + +function mt:getEmmyReturns() + local returns = self._emmyReturns + self._emmyReturns = nil + return returns +end + +function mt:getEmmyOverLoads() + local overLoads = self._emmyOverLoads + self._emmyOverLoads = nil + return overLoads +end + +function mt:getEmmyGeneric() + local generic = self._emmyGeneric + self._emmyGeneric = nil + return generic +end + +---@return string +function mt:getEmmyComment() + local comment = self._emmyComment + self._emmyComment = nil + return comment +end + +function mt:doEmmyClass(action) + ---@type emmyMgr + local emmyMgr = self.emmyMgr + self:instantSource(action) + self:instantSource(action[1]) + local class = emmyMgr:addClass(action) + action:set('emmy class', class:getName()) + action[1]:set('emmy class', class:getName()) + local extends = action[2] + if extends then + self:instantSource(extends) + extends:set('emmy class', extends[1]) + end + self._emmy = class + action:set('emmy.class', class) + if self.lsp then + self.lsp.global:markSet(self:getUri()) + end +end + +function mt:buildEmmyType(action) + ---@type emmyMgr + local emmyMgr = self.emmyMgr + self:instantSource(action) + for _, obj in ipairs(action) do + self:instantSource(obj) + obj:set('emmy class', obj[1]) + end + local type = emmyMgr:addType(action) + return type +end + +function mt:doEmmyType(action) + local type = self:buildEmmyType(action) + self._emmy = type + if self.lsp then + self.lsp.global:markGet(self:getUri()) + end + return type +end + +function mt:doEmmyAlias(action) + ---@type emmyMgr + local emmyMgr = self.emmyMgr + self:instantSource(action) + self:instantSource(action[1]) + local type = self:buildEmmyAnyType(action[2]) + local alias = emmyMgr:addAlias(action, type) + action:set('emmy.alias', alias) + action[1]:set('emmy class', alias:getName()) + self._emmy = type + if self.lsp then + self.lsp.global:markSet(self:getUri()) + end +end + +function mt:getGenericByType(type) + local generics = self._emmyGeneric + if not generics then + return + end + if #type > 1 then + return + end + local name = type[1][1] + for _, generic in ipairs(generics) do + if generic:getName() == name then + return generic + end + end + return nil +end + +function mt:doEmmyParam(action) + ---@type emmyMgr + local emmyMgr = self.emmyMgr + self:instantSource(action) + self:instantSource(action[1]) + local type = self:getGenericByType(action[2]) or self:buildEmmyAnyType(action[2]) + local param = emmyMgr:addParam(action, type) + action:set('emmy.param', param) + self:addEmmyParam(param) + if self.lsp then + self.lsp.global:markGet(self:getUri()) + end +end + +function mt:doEmmyReturn(action) + ---@type emmyMgr + local emmyMgr = self.emmyMgr + self:instantSource(action) + local type = action[1] and (self:getGenericByType(action[1]) or self:buildEmmyAnyType(action[1])) + local name = action[2] + local rtn = emmyMgr:addReturn(action, type, name) + action:set('emmy.return', rtn) + self:addEmmyReturn(rtn) + if self.lsp then + self.lsp.global:markGet(self:getUri()) + end +end + +function mt:doEmmyField(action) + ---@type emmyMgr + local emmyMgr = self.emmyMgr + self:instantSource(action) + self:instantSource(action[2]) + local type = self:buildEmmyAnyType(action[3]) + local value = self:createValue('nil', action[2]) + local field = emmyMgr:addField(action, type, value) + value:setEmmy(type) + action:set('emmy.field', field) + + local class = self._emmy + if not self._emmy or self._emmy.type ~= 'emmy.class' then + return + end + class:addField(field) + action:set('target class', class) +end + +function mt:doEmmyGeneric(action) + ---@type emmyMgr + local emmyMgr = self.emmyMgr + self:instantSource(action) + + local defs = {} + for i, obj in ipairs(action) do + defs[i] = {} + defs[i].name = self:instantSource(obj[1]) + if obj[2] then + defs[i].type = self:buildEmmyAnyType(obj[2]) + end + end + + local generic = emmyMgr:addGeneric(defs) + self._emmyGeneric = generic +end + +function mt:doEmmyVararg(action) + ---@type emmyMgr + local emmyMgr = self.emmyMgr + self:instantSource(action) + local type = self:getGenericByType(action[1]) or self:buildEmmyAnyType(action[1]) + local param = emmyMgr:addParam(action, type) + action:set('emmy.param', param) + self:addEmmyParam(param) + if self.lsp then + self.lsp.global:markGet(self:getUri()) + end +end + +function mt:buildEmmyArrayType(action) + ---@type emmyMgr + local emmyMgr = self.emmyMgr + self:instantSource(action) + for _, obj in ipairs(action) do + self:instantSource(obj) + action:set('emmy class', obj[1]) + end + local type = emmyMgr:addArrayType(action) + return type +end + +function mt:doEmmyArrayType(action) + local type = self:buildEmmyArrayType(action) + self._emmy = type + if self.lsp then + self.lsp.global:markGet(self:getUri()) + end + return type +end + +function mt:buildEmmyTableType(action) + ---@type emmyMgr + local emmyMgr = self.emmyMgr + self:instantSource(action) + local keyType = self:buildEmmyAnyType(action[1]) + local valueType = self:buildEmmyAnyType(action[2]) + local type = emmyMgr:addTableType(action, keyType, valueType) + return type +end + +function mt:doEmmyTableType(action) + local type = self:buildEmmyTableType(action) + self._emmy = type + if self.lsp then + self.lsp.global:markGet(self:getUri()) + end + return type +end + +function mt:buildEmmyFunctionType(source) + ---@type emmyMgr + local emmyMgr = self.emmyMgr + self:instantSource(source) + local funcObj = emmyMgr:addFunctionType(source) + ---@type emmyFunction + local func = functionMgr.create(source) + local args = source.args + if args then + for i = 1, #args // 2 do + local nameSource = args[i*2-1] + local typeSource = args[i*2] + local paramType = self:buildEmmyAnyType(typeSource) + funcObj:addParam(nameSource[1], paramType) + local value = self:createValue(paramType:getType(), typeSource) + value:setEmmy(paramType) + self:instantSource(nameSource) + local arg = func:addArg(nameSource[1], nameSource, value) + arg:set('emmy arg', true) + end + end + local returns = source.returns + if returns then + for i = 1, #returns do + local returnSource = returns[i] + local returnType = self:buildEmmyAnyType(returnSource) + funcObj:addReturn(returnType) + local value = self:createValue(returnType:getType(), returnSource) + value:setEmmy(returnType) + func:setReturn(i, value) + end + end + funcObj:bindFunction(func) + return funcObj +end + +function mt:doEmmyFunctionType(action) + local funcObj = self:buildEmmyFunctionType(action) + self._emmy = funcObj + return funcObj +end + +function mt:buildEmmyAnyType(source) + if source.type == 'emmyType' then + return self:buildEmmyType(source) + elseif source.type == 'emmyArrayType' then + return self:buildEmmyArrayType(source) + elseif source.type == 'emmyTableType' then + return self:buildEmmyTableType(source) + elseif source.type == 'emmyFunctionType' then + return self:buildEmmyFunctionType(source) + else + error('Unknown emmy type: ' .. table.dump(source)) + end +end + +function mt:doEmmyIncomplete(action) + self:instantSource(action) +end + +function mt:doEmmyComment(action) + self._emmyComment = action[1] +end + +function mt:doEmmySee(action) + self:instantSource(action) + self:instantSource(action[2]) + action[2]:set('emmy see', action) +end + +function mt:doEmmyOverLoad(action) + local funcObj = self:buildEmmyFunctionType(action) + self:addEmmyOverLoad(funcObj) +end diff --git a/script/src/vm/function.lua b/script/src/vm/function.lua new file mode 100644 index 00000000..1ba01363 --- /dev/null +++ b/script/src/vm/function.lua @@ -0,0 +1,551 @@ +local createMulti = require 'vm.multi' +local valueMgr = require 'vm.value' +local localMgr = require 'vm.local' +local sourceMgr = require 'vm.source' +local listMgr = require 'vm.list' + +local Watch = setmetatable({}, {__mode = 'kv'}) + +---@class emmyFunction +local mt = {} +mt.__index = mt +mt.type = 'function' +mt._runed = 0 +mt._top = 0 + +function mt:getSource() + return listMgr.get(self.source) +end + +function mt:getUri() + local source = self:getSource() + return source and source.uri or '' +end + +function mt:push(source, ischunk) + if self._removed then + return + end + self._top = self._top + 1 + self.locals[self._top] = {} + self.finishs[self._top] = source and source.finish or math.maxinteger +end + +function mt:markChunk() + self.chunk[self._top] = true +end + +function mt:pop() + if self._removed then + return + end + local closed = self.finishs[self._top] + local closedLocals = self.locals[self._top] + self.locals[self._top] = nil + self.chunk[self._top] = nil + for _, loc in pairs(closedLocals) do + loc:close(closed) + end + self._top = self._top - 1 +end + +function mt:saveLocal(name, loc) + if self._removed then + return + end + if loc.type ~= 'local' then + error('saveLocal必须是local') + end + if not loc:getSource() then + return + end + local old = self:loadLocal(name) + if old then + loc:shadow(old) + end + self.locals[self._top][name] = loc +end + +function mt:saveUpvalue(name, loc) + if self._removed then + return + end + if loc.type ~= 'local' then + error('saveLocal必须是local') + end + self.upvalues[name] = loc +end + +function mt:loadLocal(name) + for i = self._top, 1, -1 do + local locals = self.locals[i] + local loc = locals[name] + if loc then + return loc + end + if self.chunk[i] then + break + end + end + local uv = self.upvalues[name] + if uv then + return uv + end + return nil +end + +function mt:eachLocal(callback) + local mark = {} + for i = self._top, 1, -1 do + local locals = self.locals[i] + for name, loc in pairs(locals) do + if not mark[name] then + mark[name] = true + local res = callback(name, loc) + if res ~= nil then + return res + end + end + end + if self.chunk[i] then + break + end + end + for name, loc in pairs(self.upvalues) do + if not mark[name] then + mark[name] = true + local res = callback(name, loc) + if res ~= nil then + return res + end + end + end + return nil +end + +function mt:saveLabel(label) + if self._removed then + return + end + if not self._label then + self._label = {} + end + self._label[#self._label+1] = label +end + +function mt:loadLabel(name) + if not self._label then + return nil + end + for _, label in ipairs(self._label) do + if label:getName() == name then + return label + end + end + return nil +end + +function mt:rawSetReturn(index, value) + if self._removed then + return + end + self:set('hasReturn', true) + if not self.returns then + self.returns = createMulti() + end + if value then + self.returns:set(index, value) + if self._global then + value:markGlobal() + end + end +end + +function mt:setReturn(index, value) + local emmy = self._emmyReturns and self._emmyReturns[index] + if emmy then + if emmy:bindType() or emmy:bindGeneric() then + return + end + end + return self:rawSetReturn(index, value) +end + +function mt:mergeReturn(index, value) + if self._removed then + return + end + local emmy = self._emmyReturns and self._emmyReturns[index] + if emmy then + if emmy:bindType() or emmy:bindGeneric() then + return + end + end + self:set('hasReturn', true) + if not self.returns then + self.returns = createMulti() + end + if value then + if self.returns[index] then + self.returns[index]:mergeValue(value) + self.returns[index] = value + else + self.returns:set(index, value) + end + end + if self._global then + value:markGlobal() + end +end + +function mt:getReturn(index) + if self._removed then + return nil + end + if self.maxReturns and index and self.maxReturns < index then + return nil + end + if not self.returns then + self.returns = createMulti() + end + if index then + return self.returns:get(index) + else + return self.returns + end +end + +function mt:returnDots(index) + if not self.returns then + self.returns = createMulti() + end + --self.returns[index] = createMulti() +end + +function mt:loadDots() + if not self._dots then + self._dots = createMulti() + end + self._dotsLoad = true + return self._dots +end + +function mt:setObject(value, source) + self._objectValue = value + self._objectSource = source +end + +function mt:getObject() + return self._objectSource, self._objectValue +end + +function mt:hasRuned() + return self._runed > 0 +end + +function mt:needSkip() + return self._runed > 3 +end + +---@param vm VM +function mt:run(vm) + if self._removed then + return + end + if not self:getSource() then + return + end + + self._runed = self._runed + 1 + + -- 第一次运行函数时,创建函数的参数 + if self._runed == 1 then + -- 如果是面向对象形式的函数,创建隐藏的参数self + if self._objectSource then + local loc = localMgr.create('self', vm:instantSource(self._objectSource), self._objectValue) + loc:set('hide', true) + loc:set('start', self:getSource().start) + loc:close(self:getSource().finish) + self:saveUpvalue('self', loc) + self.args[#self.args+1] = loc + end + + -- 显性声明的参数 + self:createArgs(vm) + end + + if self:needSkip() then + return + end + + -- 向局部变量中填充参数 + for i, loc in ipairs(self.args) do + loc:setValue(self.argValues[i]) + local emmyParam = self:findEmmyParamByName(loc:getName()) + if emmyParam then + local typeObj = emmyParam:bindType() + if typeObj then + loc:getValue():setEmmy(typeObj) + end + local genericObj = emmyParam:bindGeneric() + if genericObj then + genericObj:setValue(loc:getValue()) + end + end + end + if self._dots then + local emmyParam = self:findEmmyParamByName('...') + self._dots = createMulti() + for i = #self.args + 1, #self.argValues do + local value = self.argValues[i] + self._dots:push(value) + if emmyParam then + local typeObj = emmyParam:bindType() + if typeObj then + value:setEmmy(typeObj) + end + local genericObj = emmyParam:bindGeneric() + if genericObj then + genericObj:setValue(value) + end + end + end + if emmyParam then + local typeObj = emmyParam:bindType() + if typeObj then + self._dots:setEmmy(typeObj) + end + local genericObj = emmyParam:bindGeneric() + if genericObj then + local value = self._dots:first() + if value then + genericObj:setValue(value) + end + end + end + end + + -- 填充返回值 + if self._emmyReturns then + for i, rtn in ipairs(self._emmyReturns) do + local value = vm:createValue('nil', rtn:getSource()) + local typeObj = rtn:bindType() + if typeObj then + value:setEmmy(typeObj) + end + local genericObj = rtn:bindGeneric() + if genericObj then + local destValue = genericObj:getValue() + if destValue then + value:mergeType(destValue) + end + end + self:rawSetReturn(i, value) + end + end +end + +function mt:eachEmmyReturn(callback) + if not self._emmyReturns then + return + end + for _, rtn in ipairs(self._emmyReturns) do + callback(rtn) + end +end + +function mt:setArgs(values) + for i = 1, #self.argValues do + self.argValues[i] = nil + end + for i = 1, #values do + self.argValues[i] = values[i] + end +end + +function mt:findEmmyParamByName(name) + local params = self._emmyParams + if not params then + return nil + end + for i = #params, 1, -1 do + local param = params[i] + if param:getName() == name then + return param + end + end + return nil +end + +function mt:findEmmyParamByIndex(index) + local arg = self.args[index] + if not arg then + return nil + end + local name = arg:getName() + return self:findEmmyParamByName(name) +end + +function mt:addArg(name, source, value, close) + local loc = localMgr.create(name, source, value) + loc:close(close) + self:saveUpvalue(name, loc) + self.args[#self.args+1] = loc + return loc +end + +function mt:createArg(vm, arg, close) + vm:instantSource(arg) + arg:set('arg', self) + if arg.type == 'name' then + vm:instantSource(arg) + local value = valueMgr.create('nil', arg) + self:addArg(arg[1], arg, value, close) + elseif arg.type == '...' then + self._dots = createMulti() + self._dotsSource = arg + end +end + +function mt:createLibArg(arg, source) + if arg.type == '...' then + self._dots = createMulti() + else + local name = arg.name or '_' + local loc = localMgr.create(name, source, valueMgr.create('any', source)) + self:saveUpvalue(name, loc) + self.args[#self.args+1] = loc + end +end + +function mt:hasDots() + return self._dots ~= nil +end + +function mt:createArgs(vm) + if not self:getSource() then + return + end + local args = self:getSource().arg + if not args then + return + end + local close = self:getSource().finish + if args.type == 'list' then + for _, arg in ipairs(args) do + self:createArg(vm, arg, close) + end + else + self:createArg(vm, args, close) + end +end + +function mt:set(name, v) + if not self._flag then + self._flag = {} + end + self._flag[name] = v +end + +function mt:get(name) + if not self._flag then + return nil + end + return self._flag[name] +end + +function mt:getSource() + if self._removed then + return nil + end + return listMgr.get(self.source) +end + +function mt:kill() + if self._removed then + return + end + self._removed = true + listMgr.clear(self.id) +end + +function mt:markGlobal() + if self._global then + return + end + self._global = true + if self.returns then + self.returns:eachValue(function (_, v) + v:markGlobal() + end) + end +end + +function mt:setEmmy(params, returns, overLoads) + if params then + self._emmyParams = params + for _, param in ipairs(params) do + param:getSource():set('emmy function', self) + param:getSource()[1]:set('emmy function', self) + end + end + if returns then + self._emmyReturns = returns + for _, rtn in ipairs(returns) do + rtn:getSource():set('emmy function', self) + end + end + if overLoads then + self._emmyOverLoads = overLoads + for _, ol in ipairs(overLoads) do + ol:getSource():set('emmy function', self) + end + end +end + +---@param comment string +function mt:setComment(comment) + self._comment = comment +end + +---@return string +function mt:getComment() + return self._comment +end + +function mt:getEmmyParams() + return self._emmyParams +end + +function mt:getEmmyOverLoads() + return self._emmyOverLoads +end + +local function create(source) + if not source then + error('Function need source') + end + local id = source.id + if not id then + error('Not instanted source') + end + local self = setmetatable({ + source = id, + locals = {}, + upvalues = {}, + chunk = {}, + finishs = {}, + args = {}, + argValues = {}, + }, mt) + + local id = listMgr.add(self) + self.id = id + Watch[self] = id + return self +end + +return { + create = create, + watch = Watch, +} diff --git a/script/src/vm/global.lua b/script/src/vm/global.lua new file mode 100644 index 00000000..af30ffdd --- /dev/null +++ b/script/src/vm/global.lua @@ -0,0 +1,25 @@ +local library = require 'core.library' +local libraryBuilder = require 'vm.library' +local sourceMgr = require 'vm.source' + +return function (lsp) + local global = lsp and lsp.globalValue + if not global then + libraryBuilder.clear() + local t = {} + for name, lib in pairs(library.global) do + t[name] = libraryBuilder.value(lib) + end + + global = t._G + global:markGlobal() + global:set('ENV', true) + for k, v in pairs(t) do + global:setChild(k, v, sourceMgr.dummy()) + end + end + if lsp then + lsp.globalValue = global + end + return global +end diff --git a/script/src/vm/init.lua b/script/src/vm/init.lua new file mode 100644 index 00000000..87576ba5 --- /dev/null +++ b/script/src/vm/init.lua @@ -0,0 +1 @@ +return require 'vm.vm' diff --git a/script/src/vm/ipairs.lua b/script/src/vm/ipairs.lua new file mode 100644 index 00000000..cb8356da --- /dev/null +++ b/script/src/vm/ipairs.lua @@ -0,0 +1,51 @@ +local mt = require 'vm.manager' +local library = require 'vm.library' + +---@param func emmyFunction +function mt:callIpairs(func, values, source) + local tbl = values[1] + func:setReturn(1, library.special['@ipairs']) + func:setReturn(2, tbl) +end + +---@param func emmyFunction +function mt:callAtIpairs(func, values, source) + local tbl = values[1] + if tbl then + local emmy = tbl:getEmmy() + if emmy then + if emmy.type == 'emmy.arrayType' then + local value = self:createValue(emmy:getName(), source) + func:setReturn(2, value) + end + end + end +end + +---@param func emmyFunction +function mt:callPairs(func, values, source) + local tbl = values[1] + func:setReturn(1, library.special['next']) + func:setReturn(2, tbl) +end + +---@param func emmyFunction +function mt:callNext(func, values, source) + local tbl = values[1] + if tbl then + local emmy = tbl:getEmmy() + if emmy then + if emmy.type == 'emmy.arrayType' then + local key = self:createValue('integer', source) + local value = self:createValue(emmy:getName(), source) + func:setReturn(1, key) + func:setReturn(2, value) + elseif emmy.type == 'emmy.tableType' then + local key = self:createValue(emmy:getKeyType():getType(), source) + local value = self:createValue(emmy:getValueType():getType(), source) + func:setReturn(1, key) + func:setReturn(2, value) + end + end + end +end diff --git a/script/src/vm/label.lua b/script/src/vm/label.lua new file mode 100644 index 00000000..c0e0dfb8 --- /dev/null +++ b/script/src/vm/label.lua @@ -0,0 +1,75 @@ +local listMgr = require 'vm.list' + +local Sort = 0 + +local mt = {} +mt.__index = mt +mt.type = 'label' + +function mt:getName() + return self.name +end + +function mt:addInfo(tp, source) + if not source then + error('No source') + end + local id = source.id + if not id then + error('Not instanted source') + end + if self._info[id] then + return + end + Sort = Sort + 1 + local info = { + type = tp, + source = id, + _sort = Sort, + } + + self._info[id] = info +end + +function mt:eachInfo(callback) + local list = {} + for srcId, info in pairs(self._info) do + local src = listMgr.get(srcId) + if src then + list[#list+1] = info + else + self._info[srcId] = nil + end + end + table.sort(list, function (a, b) + return a._sort < b._sort + end) + for i = 1, #list do + local info = list[i] + local res = callback(info, listMgr.get(info.source)) + if res ~= nil then + return res + end + end + return nil +end + +function mt:getSource() + return listMgr.get(self.source) +end + +return function (name, source) + if not source then + error('No source') + end + local id = source.id + if not id then + error('Not instanted source') + end + local self = setmetatable({ + name = name, + source = id, + _info = {}, + }, mt) + return self +end diff --git a/script/src/vm/library.lua b/script/src/vm/library.lua new file mode 100644 index 00000000..018d69f3 --- /dev/null +++ b/script/src/vm/library.lua @@ -0,0 +1,112 @@ +local sourceMgr = require 'vm.source' + +local valueMgr +local functionMgr + +local CHILD_CACHE = {} +local VALUE_CACHE = {} +local Special = {} + +local buildLibValue +local buildLibChild + +function buildLibValue(lib) + if VALUE_CACHE[lib] then + return VALUE_CACHE[lib] + end + if not valueMgr then + valueMgr = require 'vm.value' + functionMgr = require 'vm.function' + end + local tp = lib.type + local value + if tp == 'table' then + value = valueMgr.create('table', sourceMgr.dummy()) + elseif tp == 'function' then + local dummySource = sourceMgr.dummy() + value = valueMgr.create('function', dummySource) + local func = functionMgr.create(dummySource) + value:setFunction(func) + if lib.args then + for _, arg in ipairs(lib.args) do + func:createLibArg(arg, sourceMgr.dummy()) + end + end + if lib.returns then + for i, rtn in ipairs(lib.returns) do + if rtn.type == '...' then + func:returnDots(i) + else + func:setReturn(i, buildLibValue(rtn)) + end + end + if lib.special == 'pairs' then + func:setReturn(1, Special['next']) + end + if lib.special == 'ipairs' then + func:setReturn(1, Special['@ipairs']) + end + end + elseif tp == 'string' then + value = valueMgr.create('string', sourceMgr.dummy()) + elseif tp == 'boolean' then + value = valueMgr.create('boolean', sourceMgr.dummy()) + elseif tp == 'number' then + value = valueMgr.create('number', sourceMgr.dummy()) + elseif tp == 'integer' then + value = valueMgr.create('integer', sourceMgr.dummy()) + elseif tp == 'nil' then + value = valueMgr.create('nil', sourceMgr.dummy()) + else + value = valueMgr.create(tp or 'any', sourceMgr.dummy()) + end + value:setLib(lib) + VALUE_CACHE[lib] = value + + if lib.child then + for fName, fLib in pairs(lib.child) do + local fValue = buildLibValue(fLib) + value:rawSet(fName, fValue) + value:addInfo('set child', sourceMgr.dummy(), fName, fValue) + end + end + + if lib.special == 'next' then + Special['next'] = value + end + if lib.special == '@ipairs' then + Special['@ipairs'] = value + return nil + end + + return value +end + +function buildLibChild(lib) + if not valueMgr then + valueMgr = require 'vm.value' + functionMgr = require 'vm.function' + end + if CHILD_CACHE[lib] then + return CHILD_CACHE[lib] + end + local child = {} + for fName, fLib in pairs(lib.child) do + local fValue = buildLibValue(fLib) + child[fName] = fValue + end + CHILD_CACHE[lib] = child + return child +end + +local function clearCache() + CHILD_CACHE = {} + VALUE_CACHE = {} +end + +return { + value = buildLibValue, + child = buildLibChild, + clear = clearCache, + special = Special, +} diff --git a/script/src/vm/list.lua b/script/src/vm/list.lua new file mode 100644 index 00000000..234f241f --- /dev/null +++ b/script/src/vm/list.lua @@ -0,0 +1,30 @@ +local Id = 0 +local Version = 0 +local List = {} + +local function get(id) + return List[id] +end + +local function add(obj) + Id = Id + 1 + List[Id] = obj + return Id +end + +local function clear(id) + List[id] = nil + Version = Version + 1 +end + +local function getVersion() + return Version +end + +return { + get = get, + add = add, + clear = clear, + list = List, + getVersion = getVersion, +} diff --git a/script/src/vm/local.lua b/script/src/vm/local.lua new file mode 100644 index 00000000..7e8af0f1 --- /dev/null +++ b/script/src/vm/local.lua @@ -0,0 +1,191 @@ +local listMgr = require 'vm.list' + +local Sort = 0 +local Watch = setmetatable({}, {__mode = 'kv'}) + +---@class Local +local mt = {} +mt.__index = mt +mt.type = 'local' +mt._close = math.maxinteger + +function mt:setValue(value) + if not value then + return + end + if self.value then + --self.value:mergeValue(value) + self.value:mergeType(value) + self.value = value + else + self.value = value + end + if self._emmy then + self.value:setEmmy(self._emmy) + end + return value +end + +function mt:getValue() + return self.value +end + +function mt:addInfo(tp, source) + if not source then + error('No source') + end + local id = source.id + if not id then + error('Not instanted source') + end + if self._info[id] then + return + end + Sort = Sort + 1 + local info = { + type = tp, + source = id, + _sort = Sort, + } + + self._info[id] = info +end + +function mt:eachInfo(callback) + local list = {} + for srcId, info in pairs(self._info) do + local src = listMgr.get(srcId) + if src then + list[#list+1] = info + else + self._info[srcId] = nil + end + end + table.sort(list, function (a, b) + return a._sort < b._sort + end) + for i = 1, #list do + local info = list[i] + local res = callback(info, listMgr.get(info.source)) + if res ~= nil then + return res + end + end + return nil +end + +function mt:set(name, v) + if not self._flag then + self._flag = {} + end + self._flag[name] = v +end + +function mt:get(name) + if not self._flag then + return nil + end + return self._flag[name] +end + +function mt:getName() + return self.name +end + +function mt:shadow(old) + if not old then + if not self._shadow then + return nil + end + for i = #self._shadow, 1, -1 do + local loc = self._shadow[i] + if not loc:getSource() then + table.remove(self._shadow, i) + end + end + return self._shadow + end + local group = old._shadow + if not group then + group = {} + group[#group+1] = old + end + group[#group+1] = self + self._shadow = group + + if not self:getSource() then + log.error('local no source') + return + end + + old:close(self:getSource().start - 1) +end + +function mt:close(pos) + if pos then + if pos <= 0 then + pos = math.maxinteger + end + self._close = pos + else + return self._close + end +end + +function mt:getSource() + return listMgr.get(self.source) +end + +local EMMY_TYPE = { + ['emmy.class'] = true, + ['emmy.type'] = true, + ['emmy.arrayType'] = true, + ['emmy.tableType'] = true, + ['emmy.functionType'] = true, +} + +function mt:setEmmy(emmy) + if not emmy then + return + end + if self.value and EMMY_TYPE[emmy.type] then + self.value:setEmmy(emmy) + end +end + +---@param comment string +function mt:setComment(comment) + self._comment = comment +end + +---@return string +function mt:getComment() + return self._comment +end + +local function create(name, source, value, tags) + if not value then + error('Local must has a value') + end + if not source then + error('No source') + end + local id = source.id + if not id then + error('Not instanted source') + end + local self = setmetatable({ + name = name, + source = id, + value = value, + tags = tags, + _info = {}, + }, mt) + Watch[self] = true + return self +end + +return { + create = create, + watch = Watch, +} diff --git a/script/src/vm/manager.lua b/script/src/vm/manager.lua new file mode 100644 index 00000000..b9762d2e --- /dev/null +++ b/script/src/vm/manager.lua @@ -0,0 +1,17 @@ +---@class VM +local mt = {} +mt.__index = mt +mt.type = 'vm' +mt._version = -1 + +---@param version integer +function mt:setVersion(version) + self._version = version +end + +---@return integer +function mt:getVersion() + return self._version +end + +return mt diff --git a/script/src/vm/module.lua b/script/src/vm/module.lua new file mode 100644 index 00000000..60191bf3 --- /dev/null +++ b/script/src/vm/module.lua @@ -0,0 +1,56 @@ +local mt = require 'vm.manager' +local createMulti = require 'vm.multi' + +--[[ +function module(name, ...) + local env = {} + for _, opt in ipairs {...} do + opt(env) + end + @ENV = env +end +--]] +function mt:callModuel(func, values) + local envLoc = self:loadLocal('@ENV') + if not envLoc then + return + end + local source = self:getDefaultSource() + local newEnvValue = self:createValue('table', source) + local args = createMulti() + + args:push(newEnvValue) + + for i = 2, #values do + local value = values[i] + -- opt(env) + self:call(value, args, source) + end + + -- @ENV = env + envLoc:setValue(newEnvValue) +end + +--[[ +function package.seeall(env) + setmetatable(env, { __index = @ENV }) +end +--]] +function mt:callSeeAll(func, values) + local newEnv = values[1] + if not newEnv then + return + end + local envLoc = self:loadLocal('@ENV') + if not envLoc then + return + end + local oldEnv = envLoc:getValue() + if not oldEnv then + return + end + local source = self:getDefaultSource() + local meta = self:createValue('table', source) + meta:setChild('__index', oldEnv, source) + newEnv:setMetaTable(meta) +end diff --git a/script/src/vm/multi.lua b/script/src/vm/multi.lua new file mode 100644 index 00000000..4b27b8cf --- /dev/null +++ b/script/src/vm/multi.lua @@ -0,0 +1,83 @@ +local mt = {} +mt.__index = mt +mt.type = 'multi' +mt.len = 0 + +function mt:push(value, isLast) + if value and value.type == 'list' then + if isLast then + for _, v in ipairs(value) do + self.len = self.len + 1 + self[self.len] = v + end + else + self.len = self.len + 1 + self[self.len] = value[1] + end + else + self.len = self.len + 1 + self[self.len] = value + end +end + +function mt:get(index) + return self[index] +end + +function mt:set(index, value) + if index > self.len then + self.len = index + end + self[index] = value +end + +function mt:first() + local value = self[1] + if not value then + return nil + end + if value.type == 'multi' then + return value:first() + else + return value + end +end + +function mt:eachValue(callback) + local i = 0 + for n, value in ipairs(self) do + if value.type == 'multi' then + if n == self.len then + value:eachValue(function (_, nvalue) + i = i + 1 + callback(i, nvalue) + end) + else + i = i + 1 + value:first() + end + else + i = i + 1 + callback(i, value) + end + end +end + +function mt:merge(other) + other:eachValue(function (_, value) + self:push(value) + end) +end + +function mt:setEmmy(emmy) + self._emmy = emmy +end + +function mt:getEmmy() + return self._emmy +end + +return function () + local self = setmetatable({}, mt) + return self +end diff --git a/script/src/vm/pcall.lua b/script/src/vm/pcall.lua new file mode 100644 index 00000000..e5d1e26f --- /dev/null +++ b/script/src/vm/pcall.lua @@ -0,0 +1,50 @@ +local mt = require 'vm.manager' +local multi = require 'vm.multi' + +function mt:callPcall(func, values, source) + local funcValue = values:first() + if not funcValue then + return + end + local realFunc = funcValue:getFunction() + if not realFunc then + return + end + local argList = multi() + values:eachValue(function (i, v) + if i >= 2 then + argList:push(v) + end + end) + self:call(funcValue, argList, source) + if realFunc ~= func then + func:setReturn(1, self:createValue('boolean', source)) + realFunc:getReturn():eachValue(function (i, v) + func:setReturn(i + 1, v) + end) + end +end + +function mt:callXpcall(func, values, source) + local funcValue = values:first() + if not funcValue then + return + end + local realFunc = funcValue:getFunction() + if not realFunc then + return + end + local argList = multi() + values:eachValue(function (i, v) + if i >= 3 then + argList:push(v) + end + end) + self:call(funcValue, argList, source) + if realFunc ~= func then + func:setReturn(1, self:createValue('boolean', source)) + realFunc:getReturn():eachValue(function (i, v) + func:setReturn(i + 1, v) + end) + end +end diff --git a/script/src/vm/raw.lua b/script/src/vm/raw.lua new file mode 100644 index 00000000..f8c35734 --- /dev/null +++ b/script/src/vm/raw.lua @@ -0,0 +1,30 @@ +local mt = require 'vm.manager' + +function mt:callRawSet(func, values, source) + local tbl = values[1] + local index = values[2] + local value = values[3] + if not tbl or not index or not value then + return + end + if index:getLiteral() then + index = index:getLiteral() + end + tbl:addInfo('set child', source, index) + tbl:rawSet(index, value, source) + func:setReturn(1, tbl) +end + +function mt:callRawGet(func, values, source) + local tbl = values[1] + local index = values[2] + if not tbl or not index then + return + end + if index:getLiteral() then + index = index:getLiteral() + end + tbl:addInfo('get child', source, index) + local value = tbl:rawGet(index) + func:setReturn(1, value) +end diff --git a/script/src/vm/source.lua b/script/src/vm/source.lua new file mode 100644 index 00000000..7a10a38e --- /dev/null +++ b/script/src/vm/source.lua @@ -0,0 +1,183 @@ +local listMgr = require 'vm.list' + +---@class source +local mt = {} +mt.__index = mt +mt.type = 'source' +mt.uri = '' +mt.start = 0 +mt.finish = 0 +mt.id = 0 + +local Watch = setmetatable({}, {__mode = 'k'}) + +function mt:bindLocal(loc, action) + if loc then + self._bindLocal = loc + self._bindValue = loc:getValue() + self._action = action + loc:addInfo(action, self) + else + if not self._bindLocal then + return nil + end + if not self._bindLocal:getSource() then + self._bindLocal = nil + return nil + end + return self._bindLocal + end +end + +function mt:bindLabel(label, action) + if label then + self._bindLabel = label + self._action = action + label:addInfo(action, self) + else + return self._bindLabel + end +end + +function mt:bindFunction(func) + if func then + self._bindFunction = func + else + return self._bindFunction + end +end + +function mt:bindValue(value, action) + if value then + self._bindValue = value + self._action = action + value:addInfo(action, self) + else + return self._bindValue + end +end + +function mt:bindCall(args) + if args then + self._bindCallArgs = args + else + return self._bindCallArgs + end +end + +function mt:bindMetatable(meta) + if meta then + self._bindMetatable = meta + else + return self._bindMetatable + end +end + +function mt:action() + return self._action +end + +function mt:setUri(uri) + self.uri = uri +end + +function mt:getUri() + return self.uri +end + +function mt:set(name, v) + if not self._flag then + self._flag = {} + end + self._flag[name] = v +end + +function mt:get(name) + if not self._flag then + return nil + end + return self._flag[name] +end + +function mt:getName() + return self[1] +end + +function mt:kill() + self._dead = true + listMgr.clear(self.id) +end + +function mt:isDead() + return self._dead +end + +function mt:findValue() + local value = self:bindValue() + if not value then + return nil + end + if not value:isGlobal() then + return value + end + if self.type ~= 'name' then + return value + end + local parent = self:get 'parent' + if not parent then + return value + end + local name = self[1] + if type(name) ~= 'string' then + return value + end + return parent:getChild(name) or value +end + +function mt:findCallFunction() + local simple = self:get 'simple' + if not simple then + return nil + end + local source + for i = 1, #simple do + if simple[i] == self then + source = simple[i-1] + end + end + if not source then + return nil + end + local value = source:bindValue() + if value and value:getFunction() then + return value + end + value = source:findValue() + if value and value:getFunction() then + return value + end + return nil +end + +local function instant(source) + if source.id then + return false + end + local id = listMgr.add(source) + source.id = id + Watch[source] = id + setmetatable(source, mt) + return true +end + +local function dummy() + local src = {} + instant(src) + return src +end + +return { + instant = instant, + watch = Watch, + dummy = dummy, +} diff --git a/script/src/vm/special.lua b/script/src/vm/special.lua new file mode 100644 index 00000000..e93c4445 --- /dev/null +++ b/script/src/vm/special.lua @@ -0,0 +1,130 @@ +local mt = require 'vm.manager' +local multi = require 'vm.multi' +local library = require 'core.library' +local libraryBuilder = require 'vm.library' +local plugin = require 'plugin' + +---@param func emmyFunction +---@param values table +function mt:callEmmySpecial(func, values, source) + local emmyParams = func:getEmmyParams() + for index, param in ipairs(emmyParams) do + local option = param:getOption() + if option and type(option.special) == 'string' then + self:checkEmmyParam(func, values, index, option.special, source) + end + end +end + +---@param func emmyFunction +---@param values table +---@param index integer +---@param special string +function mt:checkEmmyParam(func, values, index, special, source) + if special == 'dofile:1' then + self:callEmmyDoFile(func, values, index) + elseif special == 'loadfile:1' then + self:callEmmyLoadFile(func, values, index) + elseif special == 'pcall:1' then + self:callEmmyPCall(func, values, index, source) + elseif special == 'require:1' then + self:callEmmyRequire(func, values, index) + end +end + +---@param func emmyFunction +---@param values table +---@param index integer +function mt:callEmmyDoFile(func, values, index) + if not values[index] then + values[index] = self:createValue('any', self:getDefaultSource()) + end + local str = values[index]:getLiteral() + if type(str) ~= 'string' then + return + end + local requireValue = self:tryRequireOne(str, values[index], 'dofile') + if not requireValue then + requireValue = self:createValue('any', self:getDefaultSource()) + requireValue.isRequire = true + end + func:setReturn(1, requireValue) +end + +---@param func emmyFunction +---@param values table +---@param index integer +function mt:callEmmyLoadFile(func, values, index) + if not values[index] then + values[index] = self:createValue('any', self:getDefaultSource()) + end + local str = values[index]:getLiteral() + if type(str) ~= 'string' then + return + end + local requireValue = self:tryRequireOne(str, values[index], 'loadfile') + if not requireValue then + requireValue = self:createValue('any', self:getDefaultSource()) + requireValue:set('cross file', true) + end + func:setReturn(1, requireValue) +end + +---@param func emmyFunction +---@param values table +---@param index integer +---@param source source +function mt:callEmmyPCall(func, values, index, source) + local funcValue = values[index] + if not funcValue then + return + end + local realFunc = funcValue:getFunction() + if not realFunc then + return + end + local argList = multi() + values:eachValue(function (i, v) + if i > index then + argList:push(v) + end + end) + self:call(funcValue, argList, source) + if realFunc ~= func then + func:setReturn(1, self:createValue('boolean', source)) + realFunc:getReturn():eachValue(function (i, v) + func:setReturn(i + 1, v) + end) + end +end + +---@param func emmyFunction +---@param values table +---@param index integer +function mt:callEmmyRequire(func, values, index) + if not values[index] then + values[index] = self:createValue('any', self:getDefaultSource()) + end + local strValue = values[index] + local strSource = strValue:getSource() + if not strSource then + return nil + end + local str = strValue:getLiteral() + local raw = self.text:sub(strSource.start, strSource.finish) + str = plugin.call('OnRequirePath', str, raw) or str + local lib = library.library[str] + if lib then + local value = libraryBuilder.value(lib) + value:markGlobal() + func:setReturn(1, value) + return + else + local requireValue = self:tryRequireOne(str, strValue, 'require') + if not requireValue then + requireValue = self:createValue('any', self:getDefaultSource()) + requireValue:set('cross file', true) + end + func:setReturn(1, requireValue) + end +end diff --git a/script/src/vm/value.lua b/script/src/vm/value.lua new file mode 100644 index 00000000..5de0d8e8 --- /dev/null +++ b/script/src/vm/value.lua @@ -0,0 +1,634 @@ +local libraryBuilder = require 'vm.library' +local library = require 'core.library' +local listMgr = require 'vm.list' +local config = require 'config' + +local Sort = 0 +local Watch = setmetatable({}, {__mode = 'kv'}) +local TypeLevel = { + ['table'] = 1.0, + ['function'] = 0.9, + ['string'] = 0.8, + ['integer'] = 0.7, + ['number'] = 0.6, +} + +---@class value +local mt = {} +mt.__index = mt +mt.type = 'value' +mt.uri = '' +mt._global = false + +local function create (tp, source, literal) + if tp == '...' then + error('Value type cant be ...') + end + if not source then + error('No source') + end + local id = source.id + if not id then + error('Not instanted source') + end + local self = setmetatable({ + source = id, + _type = {}, + _literal = literal, + _info = {}, + }, mt) + if type(tp) == 'table' then + for i = 1, #tp do + self:setType(tp[i], 1.0 / #tp) + end + else + self:setType(tp, 1.0) + end + Watch[self] = true + return self +end + +function mt:setType(tp, rate) + if type(tp) == 'table' then + for _, ctp in ipairs(tp) do + self:setType(ctp, rate) + end + return + end + if tp == '...' then + error('Value type cant be ...') + end + if not tp then + tp = 'nil' + end + if tp == 'any' or tp == 'nil' then + rate = 0.0 + end + if tp == 'integer' then + local version = config.config.runtime.version + if version ~= 'Lua 5.3' and version ~= 'Lua 5.4' then + tp = 'number' + end + end + local current = self._type[tp] or 0.0 + if rate > current then + self._type[tp] = rate + end +end + +function mt:getType() + if self:getEmmy() then + return self:getEmmy():getType(), 1.0 + end + if not self._type then + return 'nil', 0.0 + end + local mRate = 0.0 + local mType + for tp, rate in pairs(self._type) do + if rate > mRate then + mRate = rate + mType = tp + elseif rate == mRate then + local level1 = TypeLevel[tp] or 0.0 + local level2 = TypeLevel[mType] or 0.0 + if level1 > level2 then + mRate = rate + mType = tp + end + end + end + return mType or 'any', mRate +end + +function mt:rawSet(index, value, source) + if index == nil then + return + end + if not self._child then + self._child = {} + end + if self._child[index] then + if self._global then + self._child[index]:mergeValue(value) + else + self._child[index]:mergeType(value) + self._child[index]:mergeInfo(value) + end + self._child[index] = value + else + self._child[index] = value + end + self:addInfo('set child', source, index, self._child[index]) + if self._global then + self._child[index]:markGlobal() + end +end + +function mt:rawGet(index) + if not self._child then + return nil + end + self:flushChild() + local child = self._child[index] + if not child then + return nil + end + return child +end + +function mt:setChild(index, value, source) + if index == nil then + return + end + self:setType('table', 0.5) + self:rawSet(index, value, source) + return value +end + +function mt:getLibChild(index) + local tp = self:getType() + local lib = library.object[tp] + if lib then + local childs = libraryBuilder.child(lib) + return childs[index] + end + return nil +end + +function mt:eachLibChild(callback) + local tp = self:getType() + local lib = library.object[tp] + if lib then + local childs = libraryBuilder.child(lib) + for k, v in pairs(childs) do + callback(k, v) + end + end +end + +function mt:getChild(index, source) + self:setType('table', 0.5) + local parent = self + local value + -- 最多检查3层 __index + for _ = 1, 3 do + value = parent:rawGet(index) + if value then + break + end + local method = parent:getMetaMethod('__index') + if not method then + value = parent:getLibChild(index) + break + end + parent = method + end + if not value and source then + local emmy = self:getEmmy() + if emmy then + if emmy.type == 'emmy.arrayType' then + if type(index) == 'number' then + value = create(emmy:getName(), source) + end + elseif emmy.type == 'emmy.tableType' then + value = create(emmy:getValueType():getType(), source) + end + end + if not value then + value = create('any', source) + end + self:setChild(index, value) + value.uri = self.uri + end + return value +end + +function mt:setMetaTable(metatable) + local source = metatable:getSource() + if not source then + return + end + source:bindMetatable(metatable) + self._meta = metatable.source +end + +function mt:getMetaTable() + if not self._meta then + return nil + end + local metaSource = listMgr.get(self._meta) + if not metaSource then + self._meta = nil + return nil + end + return metaSource:bindMetatable() +end + +function mt:getMetaMethod(name) + local meta = self:getMetaTable() + if not meta then + return nil + end + return meta:rawGet(name) +end + +function mt:flushChild() + if not self._child then + return nil + end + -- 非全局值不会出现dead child + if not self._global then + return + end + local listVersion = listMgr.getVersion() + if self._flushVersion == listVersion then + return + end + self._flushVersion = listVersion + local alived = {} + local infos = self._info + local count = 0 + for srcId, info in pairs(infos) do + local src = listMgr.get(srcId) + if src then + if info.type == 'set child' or info.type == 'get child' then + if info[1] then + alived[info[1]] = true + end + end + count = count + 1 + else + infos[srcId] = nil + end + end + infos._count = count + infos._limit = count * 1.1 + 10 + infos._version = listMgr.getVersion() + for index in pairs(self._child) do + if not alived[index] then + self._child[index] = nil + end + end +end + +function mt:rawEach(callback, mark) + if not self._child then + return nil + end + self:flushChild() + for index, value in pairs(self._child) do + if mark then + if mark[index] then + goto CONTINUE + end + mark[index] = true + end + local res = callback(index, value) + if res ~= nil then + return res + end + ::CONTINUE:: + end + return nil +end + +function mt:eachChild(callback) + local mark = {} + local parent = self + -- 最多检查3层 __index + for _ = 1, 3 do + local res = parent:rawEach(callback, mark) + if res ~= nil then + return res + end + local method = parent:getMetaMethod('__index') + if not method then + return parent:eachLibChild(callback) + end + parent = method + end +end + +function mt:mergeType(value) + if self == value then + return + end + if not value then + return + end + if self._emmy and not value._emmy then + value._emmy = self._emmy + return + elseif not self._emmy and value._emmy then + self._emmy = value._emmy + return + end + if value._type then + for tp, rate in pairs(value._type) do + self:setType(tp, rate) + end + end + value._type = self._type +end + +function mt:mergeInfo(value) + if self == value then + return + end + if not value then + return + end + local infos = self._info + for srcId, info in pairs(value._info) do + local src = listMgr.get(srcId) + if src and not infos[srcId] then + infos[srcId] = info + infos._count = (infos._count or 0) + 1 + end + end + value._info = infos +end + +function mt:mergeValue(value) + if self == value then + return + end + if not value then + return + end + local list = {self, value} + local pos = 1 + while true do + local a, b = list[pos], list[pos+1] + if not a then + break + end + pos = pos + 2 + list[a] = true + list[b] = true + a:mergeType(b) + a:mergeInfo(b) + + a:flushChild() + b:flushChild() + local global = a._global or b._global + if b._child then + if not a._child then + a._child = {} + end + for k, bc in pairs(b._child) do + local ac = a._child[k] + if ac and ac ~= bc and global then + if list[ac] and list[bc] then + else + list[#list+1] = ac + list[#list+1] = bc + end + end + a._child[k] = bc + end + end + b._child = a._child + if global then + a:markGlobal() + b:markGlobal() + end + + if b._meta then + a._meta = b._meta + end + if b._func then + a._func = b._func + end + if b._lib then + a._lib = b._lib + end + if b.uri then + a.uri = b.uri + end + end +end + +function mt:addInfo(tp, source, ...) + if not source then + return + end + if not source.start then + error('Miss start: ' .. table.dump(source)) + end + local id = source.id + if not id then + error('Not instanted source') + end + if not tp then + error('Miss info type') + end + + local infos = self._info + if infos[id] then + return + end + Sort = Sort + 1 + local info = { + type = tp, + source = id, + _sort = Sort, + ... + } + infos[id] = info + infos._count = (infos._count or 0) + 1 + local version = listMgr.getVersion() + -- 只有全局值需要压缩info + if self._global and infos._count > (infos._limit or 10) and infos._version ~= version then + local count = 0 + for srcId in pairs(infos) do + local src = listMgr.get(srcId) + if src then + count = count + 1 + else + infos[srcId] = nil + end + end + infos._count = count + infos._limit = count * 1.1 + 10 + infos._version = version + end +end + +function mt:eachInfo(callback) + local clock = os.clock() + local infos = self._info + local list = {} + for srcId, info in pairs(infos) do + local src = listMgr.get(srcId) + if src then + list[#list+1] = info + else + infos[srcId] = nil + end + end + infos._count = #list + infos._limit = infos._count * 1.1 + 10 + infos._version = listMgr.getVersion() + --local clock2 = os.clock() + --table.sort(list, function (a, b) + -- return a._sort < b._sort + --end) + local passed = os.clock() - clock + if passed > 0.1 then + log.warn(('eachInfo takes: [%.3f]sec, #list: %d'):format(passed, #list)) + end + for i = 1, #list do + local info = list[i] + local res = callback(info, listMgr.get(info.source)) + if res ~= nil then + return res + end + end + return nil +end + +function mt:setFunction(func) + self._func = func.id + if self._global then + func:markGlobal() + end +end + +function mt:getFunction() + local id = self._func + local func = listMgr.get(id) + if not func then + return nil + end + if func._removed then + return nil + end + if not func:getSource() then + func = nil + listMgr.clear(id) + end + return func +end + +function mt:setLib(lib) + self._lib = lib +end + +function mt:getLib() + return self._lib +end + +function mt:getLiteral() + return self._literal +end + +function mt:set(name, v) + if not self._flag then + self._flag = {} + end + self._flag[name] = v +end + +function mt:get(name) + if not self._flag then + return nil + end + return self._flag[name] +end + +function mt:getSource() + return listMgr.get(self.source) +end + +function mt:markGlobal() + if self._global then + return + end + self._global = true + self:rawEach(function (index, value) + value:markGlobal() + end) + local func = self:getFunction() + if func then + func:markGlobal() + end +end + +function mt:isGlobal() + return self._global +end + +function mt:setEmmy(emmy) + if not emmy then + return + end + if emmy.type == 'emmy.class' then + ---@type EmmyClass + local emmyClass = emmy + emmyClass:setValue(self) + emmyClass:eachChild(function (obj) + local value = obj:getValue() + if value then + value:mergeValue(self) + end + end) + emmyClass:eachField(function (field) + local name = field:getName() + local value = field:bindValue() + self:setChild(name, value, field:getSource()) + end) + elseif emmy.type == 'emmy.type' then + ---@type EmmyType + local emmyType = emmy + emmyType:setValue(self) + emmyType:eachClass(function (class) + if class then + self:mergeValue(class:getValue()) + end + end) + elseif emmy.type == 'emmy.arrayType' then + ---@type EmmyArrayType + local emmyArrayType = emmy + emmyArrayType:setValue(self) + elseif emmy.type == 'emmy.tableType' then + ---@type EmmyTableType + local emmyTableType = emmy + emmyTableType:setValue(self) + elseif emmy.type == 'emmy.functionType' then + ---@type EmmyFunctionType + local emmyFuncType = emmy + emmyFuncType:setValue(self) + self:setFunction(emmyFuncType:bindFunction()) + else + return + end + self._emmy = emmy + self:markGlobal() +end + +function mt:getEmmy() + if not self._emmy then + return nil + end + local source = self._emmy.source + if not listMgr.get(source) then + self._emmy = nil + return nil + end + return self._emmy +end + +function mt:setComment(comment) + self._comment = comment +end + +function mt:getComment(comment) + return self._comment +end + +return { + create = create, + watch = Watch, +} diff --git a/script/src/vm/vm.lua b/script/src/vm/vm.lua new file mode 100644 index 00000000..36ad78c9 --- /dev/null +++ b/script/src/vm/vm.lua @@ -0,0 +1,1334 @@ +local library = require 'core.library' +local valueMgr = require 'vm.value' +local localMgr = require 'vm.local' +local createLabel = require 'vm.label' +local functionMgr = require 'vm.function' +local sourceMgr = require 'vm.source' +local buildGlobal = require 'vm.global' +local createMulti = require 'vm.multi' +local libraryBuilder = require 'vm.library' +local emmyMgr = require 'emmy.manager' +local config = require 'config' +local mt = require 'vm.manager' +local plugin = require 'plugin' + +require 'vm.module' +require 'vm.raw' +require 'vm.pcall' +require 'vm.ipairs' +require 'vm.emmy' +require 'vm.special' + +-- TODO source测试 +--rawset(_G, 'CachedSource', setmetatable({}, { __mode = 'kv' })) + +function mt:getDefaultSource() + return self:instantSource { + start = 0, + finish = 0, + } +end + +function mt:scopePush(source) + self.currentFunction:push(source) +end + +function mt:scopePop() + self.currentFunction:pop() +end + +function mt:buildTable(source) + local tbl = self:createValue('table', source) + if not source then + return tbl + end + local n = 0 + for index, obj in ipairs(source) do + local emmy = self:getEmmy() + if obj.type == 'pair' then + local value = self:getFirstInMulti(self:getExp(obj[2])) + if value then + local key = obj[1] + self:instantSource(obj) + self:instantSource(key) + key:bindValue(value, 'set') + value:setEmmy(emmy) + if key.type == 'index' then + local index = self:getIndex(key) + key:set('parent', tbl) + tbl:setChild(index, value, key) + else + if key.type == 'name' then + key:set('parent', tbl) + key:set('table index', true) + tbl:setChild(key[1], value, key) + end + end + end + elseif obj.type:sub(1, 4) == 'emmy' then + self:doEmmy(obj) + else + local value = self:getExp(obj) + if value.type == 'multi' then + if index == #source then + value:eachValue(function (_, v) + n = n + 1 + tbl:setChild(n, v, obj) + end) + else + n = n + 1 + local v = self:getFirstInMulti(value) + tbl:setChild(n, v, obj) + end + else + n = n + 1 + tbl:setChild(n, value, obj) + end + -- 处理写了一半的 key = value,把name类的数组元素视为哈希键 + if obj.type == 'name' then + obj:set('table index', true) + end + end + end + return tbl +end + +function mt:runFunction(func) + func:run(self) + + if not func:getSource() then + return + end + + if func:needSkip() then + return + end + + -- 暂时使用这种方式激活参数的source + for _, arg in ipairs(func.args) do + if arg:getSource() ~= func:getObject() then + self:bindLocal(arg:getSource(), arg, 'local') + end + end + + local originFunction = self:getCurrentFunction() + self:setCurrentFunction(func) + func:push(func:getSource()) + func:markChunk() + + self:doActions(func:getSource()) + + func:pop() + self:setCurrentFunction(originFunction) +end + +function mt:buildFunction(exp) + if exp and exp:bindFunction() then + return exp:bindFunction() + end + + local value = self:createFunction(exp) + + if not exp then + return value + end + + exp:bindFunction(value) + local func = value:getFunction() + + self:eachLocal(function (name, loc) + func:saveUpvalue(name, loc) + end) + + return value +end + +function mt:forList(list, callback) + if not list then + return + end + if list.type == 'list' then + for i = 1, #list do + callback(list[i]) + end + else + callback(list) + end +end + +function mt:callSetMetaTable(func, values, source) + if not values[1] then + values[1] = self:createValue('any', self:getDefaultSource()) + end + if not values[2] then + values[2] = self:createValue('any', self:getDefaultSource()) + end + func:setReturn(1, values[1]) + values[1]:setMetaTable(values[2]) +end + +function mt:tryRequireOne(str, strValue, mode) + if not self.lsp or not self.lsp.workspace then + return nil + end + local strSource = strValue:getSource() + if not strSource then + return nil + end + if type(str) == 'string' then + -- 支持 require 'xxx' 的转到定义 + self:instantSource(strSource) + local uri + if mode == 'require' then + uri = self.lsp.workspace:searchPath(self:getUri(), str) + elseif mode == 'loadfile' then + uri = self.lsp.workspace:loadPath(self:getUri(), str) + elseif mode == 'dofile' then + uri = self.lsp.workspace:loadPath(self:getUri(), str) + end + if not uri then + return nil + end + + strSource:set('target uri', uri) + self.lsp:compileChain(self:getUri(), uri) + return self.lsp.chain:get(uri) + end + return nil +end + +function mt:callRequire(func, values) + if not values[1] then + values[1] = self:createValue('any', self:getDefaultSource()) + end + local strValue = values[1] + local strSource = strValue:getSource() + if not strSource then + return nil + end + local str = strValue:getLiteral() + local raw = self.text:sub(strSource.start, strSource.finish) + str = plugin.call('OnRequirePath', str, raw) or str + local lib = library.library[str] + if lib then + local value = libraryBuilder.value(lib) + value:markGlobal() + func:setReturn(1, value) + return + else + local requireValue = self:tryRequireOne(str, values[1], 'require') + if not requireValue then + requireValue = self:createValue('any', self:getDefaultSource()) + requireValue:set('cross file', true) + end + func:setReturn(1, requireValue) + end +end + +function mt:callLoadFile(func, values) + if not values[1] then + values[1] = self:createValue('any', self:getDefaultSource()) + end + local strValue = values[1] + local requireValue = self:tryRequireOne(strValue:getLiteral(), values[1], 'loadfile') + if not requireValue then + requireValue = self:createValue('any', self:getDefaultSource()) + requireValue:set('cross file', true) + end + func:setReturn(1, requireValue) +end + +function mt:callDoFile(func, values) + if not values[1] then + values[1] = self:createValue('any', self:getDefaultSource()) + end + local strValue = values[1] + local requireValue = self:tryRequireOne(strValue:getLiteral(), values[1], 'dofile') + if not requireValue then + requireValue = self:createValue('any', self:getDefaultSource()) + requireValue.isRequire = true + end + func:setReturn(1, requireValue) +end + +function mt:callLibrary(func, values, source, lib) + if lib.args then + for i, arg in ipairs(lib.args) do + local value = values[i] + if value and arg.type ~= '...' then + value:setType(arg.type, 0.6) + end + end + end + if lib.returns then + for i, rtn in ipairs(lib.returns) do + if rtn.type == '...' then + --func:getReturn(i):setType('any', 0.0) + else + if rtn.type == 'boolean' or rtn.type == 'number' or rtn.type == 'integer' or rtn.type == 'string' then + func:setReturn(i, self:createValue(rtn.type, self:getDefaultSource())) + end + local value = func:getReturn(i) + if value then + value:setType(rtn.type or 'any', 0.6) + end + end + end + end + if lib.special then + if lib.special == 'setmetatable' then + self:callSetMetaTable(func, values, source) + elseif lib.special == 'require' then + self:callRequire(func, values) + elseif lib.special == 'loadfile' then + self:callLoadFile(func, values) + elseif lib.special == 'dofile' then + self:callDoFile(func, values) + elseif lib.special == 'module' then + self:callModuel(func, values) + elseif lib.special == 'seeall' then + self:callSeeAll(func, values) + elseif lib.special == 'rawset' then + self:callRawSet(func, values, source) + elseif lib.special == 'rawget' then + self:callRawGet(func, values, source) + elseif lib.special == 'pcall' then + self:callPcall(func, values, source) + elseif lib.special == 'xpcall' then + self:callXpcall(func, values, source) + elseif lib.special == 'ipairs' then + self:callIpairs(func, values, source) + elseif lib.special == '@ipairs' then + self:callAtIpairs(func, values, source) + elseif lib.special == 'pairs' then + self:callPairs(func, values, source) + elseif lib.special == 'next' then + self:callNext(func, values, source) + end + else + -- 如果lib的参数中有function,则立即执行function + if lib.args then + local args + for i = 1, #lib.args do + local value = values[i] + if value and value:getFunction() then + if not args then + args = createMulti() + end + self:call(value, args, source) + end + end + end + end +end + +function mt:call(value, values, source) + local lib = value:getLib() + ---@type emmyFunction + local func = value:getFunction() + value:setType('function', 0.5) + if not func then + return + end + self:instantSource(source) + if lib then + self:callLibrary(func, values, source, lib) + else + if func:getSource() then + if not source:get 'called' then + source:set('called', true) + func:setArgs(values) + self:runFunction(func) + end + else + func:mergeReturn(1, self:createValue('any', source)) + end + if func:getEmmyParams() then + self:callEmmySpecial(func, values, source) + end + end + + return func:getReturn() +end + +function mt:createValue(tp, source, literal) + local value = valueMgr.create(tp, source, literal) + value.uri = self:getUri() + return value +end + +function mt:getName(name, source) + if source then + self:instantSource(source) + if source:bindLocal() then + local loc = source:bindLocal() + return loc:getValue() + end + end + local loc = self:loadLocal(name) + if loc then + source:bindLocal(loc, 'get') + return loc:getValue() + end + local global = source:bindValue() + if global then + return global + end + local ENV + if self.envType == '_ENV' then + ENV = self:loadLocal('_ENV') + else + ENV = self:loadLocal('@ENV') + end + local ENVValue = ENV:getValue() + ENVValue:addInfo('get child', source, name) + global = ENVValue:getChild(name, source) + source:bindValue(global, 'get') + source:set('global', true) + source:set('parent', ENVValue) + if not global:getLib() then + if self.lsp then + self.lsp.global:markGet(self:getUri()) + end + end + return global +end + +function mt:setName(name, source, value) + self:instantSource(source) + local loc = self:loadLocal(name) + if loc then + loc:setValue(value) + source:bindLocal(loc, 'set') + return + end + local global = source:bindValue() + if global then + return global + end + local ENV + if self.envType == '_ENV' then + ENV = self:loadLocal('_ENV') + else + ENV = self:loadLocal('@ENV') + end + local ENVValue = ENV:getValue() + source:bindValue(value, 'set') + ENVValue:setChild(name, value, source) + source:set('global', true) + source:set('parent', ENVValue) + if self.lsp then + self.lsp.global:markSet(self:getUri()) + end +end + +function mt:getIndex(source) + local child = source[1] + if child.type == 'name' then + local value = self:getName(child[1], child) + child:set('in index', source) + return value + elseif child.type == 'string' or child.type == 'number' or child.type == 'boolean' then + self:instantSource(child) + child:set('in index', source) + return child[1] + else + local index = self:getExp(child) + return self:getFirstInMulti(index) + end +end + +function mt:unpackList(list) + local values = createMulti() + local exps = createMulti() + if not list then + return values + end + if list.type == 'list' or list.type == 'call' or list.type == 'return' then + for i, exp in ipairs(list) do + self:instantSource(exp) + exps:push(exp) + if exp.type == '...' then + values:merge(self:loadDots()) + break + end + local value = self:getExp(exp) + if value.type == 'multi' then + if i == #list then + value:eachValue(function (_, v) + values:push(v) + end) + else + values:push(self:getFirstInMulti(value)) + end + else + values:push(value) + end + end + elseif list.type == '...' then + self:instantSource(list) + exps:push(list) + values:merge(self:loadDots()) + else + self:instantSource(list) + exps:push(list) + local value = self:getExp(list) + if value.type == 'multi' then + value:eachValue(function (_, v) + values:push(v) + end) + else + values:push(value) + end + end + return values, exps +end + +function mt:getFirstInMulti(multi) + if not multi then + return multi + end + if multi.type == 'multi' then + return self:getFirstInMulti(multi[1]) + else + return multi + end +end + +function mt:getSimple(simple, max) + self:instantSource(simple) + local first = simple[1] + self:instantSource(first) + local value = self:getExp(first) + value = self:getFirstInMulti(value) or valueMgr.create('nil', self:getDefaultSource()) + first:bindValue(value, 'get') + if not max then + max = #simple + elseif max < 0 then + max = #simple + 1 + max + end + local object + for i = 2, max do + local source = simple[i] + self:instantSource(source) + source:set('simple', simple) + value = self:getFirstInMulti(value) or valueMgr.create('nil', self:getDefaultSource()) + + if source.type == 'call' then + local values, args = self:unpackList(source) + local func = value + if object then + table.insert(values, 1, object) + table.insert(args, 1, simple[i-3]) + source:set('has object', true) + end + object = nil + source:bindCall(args) + value = self:call(func, values, source) or valueMgr.create('any', self:getDefaultSource()) + elseif source.type == 'index' then + local child = source[1] + local index = self:getIndex(source) + child:set('parent', value) + value:addInfo('get child', source, index) + value = value:getChild(index, source) + source:bindValue(value, 'get') + elseif source.type == 'name' then + source:set('parent', value) + source:set('object', object) + value:addInfo('get child', source, source[1]) + value = value:getChild(source[1], source) + source:bindValue(value, 'get') + elseif source.type == ':' then + object = value + source:set('parent', value) + source:set('object', object) + elseif source.type == '.' then + source:set('parent', value) + end + end + return value +end + +function mt:isTrue(v) + if v:getType() == 'nil' then + return false + end + if v:getType() == 'boolean' and not v:getLiteral() then + return false + end + return true +end + +function mt:getBinary(exp) + self:instantSource(exp) + local v1 = self:getExp(exp[1]) + local v2 = self:getExp(exp[2]) + v1 = self:getFirstInMulti(v1) or valueMgr.create('nil', exp[1]) + v2 = self:getFirstInMulti(v2) or valueMgr.create('nil', exp[2]) + local op = exp.op + -- TODO 搜索元方法 + if op == 'or' then + if self:isTrue(v1) then + return v1 + else + return v2 + end + elseif op == 'and' then + if self:isTrue(v1) then + return v2 + else + return v1 + end + elseif op == '<=' + or op == '>=' + or op == '<' + or op == '>' + then + v1:setType('number', 0.5) + v2:setType('number', 0.5) + v1:setType('string', 0.1) + v2:setType('string', 0.1) + return self:createValue('boolean', exp) + elseif op == '~=' + or op == '==' + then + return self:createValue('boolean', exp) + elseif op == '|' + or op == '~' + or op == '&' + or op == '<<' + or op == '>>' + then + v1:setType('integer', 0.5) + v2:setType('integer', 0.5) + v1:setType('number', 0.5) + v2:setType('number', 0.5) + v1:setType('string', 0.1) + v2:setType('string', 0.1) + if math.type(v1:getLiteral()) == 'integer' and math.type(v2:getLiteral()) == 'integer' then + if op == '|' then + return self:createValue('integer', exp, v1:getLiteral() | v2:getLiteral()) + elseif op == '~' then + return self:createValue('integer', exp, v1:getLiteral() ~ v2:getLiteral()) + elseif op == '&' then + return self:createValue('integer', exp, v1:getLiteral() &v2:getLiteral()) + elseif op == '<<' then + return self:createValue('integer', exp, v1:getLiteral() << v2:getLiteral()) + elseif op == '>>' then + return self:createValue('integer', exp, v1:getLiteral() >> v2:getLiteral()) + end + end + return self:createValue('integer', exp) + elseif op == '..' then + v1:setType('string', 0.5) + v2:setType('string', 0.5) + v1:setType('number', 0.1) + v2:setType('number', 0.1) + if type(v1:getLiteral()) == 'string' and type(v2:getLiteral()) == 'string' then + return self:createValue('string', exp, v1:getLiteral() .. v2:getLiteral()) + end + return self:createValue('string', exp) + elseif op == '+' + or op == '-' + or op == '*' + or op == '/' + or op == '^' + or op == '%' + or op == '//' + then + v1:setType('number', 0.5) + v2:setType('number', 0.5) + if type(v1:getLiteral()) == 'number' and type(v2:getLiteral()) == 'number' then + if op == '+' then + return self:createValue('number', exp, v1:getLiteral() + v2:getLiteral()) + elseif op == '-' then + return self:createValue('number', exp, v1:getLiteral() - v2:getLiteral()) + elseif op == '*' then + return self:createValue('number', exp, v1:getLiteral() * v2:getLiteral()) + elseif op == '/' then + if v2:getLiteral() ~= 0 then + return self:createValue('number', exp, v1:getLiteral() / v2:getLiteral()) + end + elseif op == '^' then + return self:createValue('number', exp, v1:getLiteral() ^ v2:getLiteral()) + elseif op == '%' then + if v2:getLiteral() ~= 0 then + return self:createValue('number', exp, v1:getLiteral() % v2:getLiteral()) + end + elseif op == '//' then + if v2:getLiteral() ~= 0 then + return self:createValue('number', exp, v1:getLiteral() // v2:getLiteral()) + end + end + end + return self:createValue('number', exp) + end + return nil +end + +function mt:getUnary(exp) + self:instantSource(exp) + local v1 = self:getExp(exp[1]) + v1 = self:getFirstInMulti(v1) or self:createValue('nil', exp[1]) + local op = exp.op + -- TODO 搜索元方法 + if op == 'not' then + return self:createValue('boolean', exp) + elseif op == '#' then + v1:setType('table', 0.5) + v1:setType('string', 0.5) + if type(v1:getLiteral()) == 'string' then + return self:createValue('integer', exp, #v1:getLiteral()) + end + return self:createValue('integer', exp) + elseif op == '-' then + v1:setType('number', 0.5) + if type(v1:getLiteral()) == 'number' then + return self:createValue('number', exp, -v1:getLiteral()) + end + return self:createValue('number', exp) + elseif op == '~' then + v1:setType('integer', 0.5) + if math.type(v1:getLiteral()) == 'integer' then + return self:createValue('integer', exp, ~v1:getLiteral()) + end + return self:createValue('integer', exp) + end + return nil +end + +function mt:getExp(exp) + self:instantSource(exp) + local tp = exp.type + if tp == 'nil' then + return self:createValue('nil', exp) + elseif tp == 'string' then + return self:createValue('string', exp, exp[1]) + elseif tp == 'boolean' then + return self:createValue('boolean', exp, exp[1]) + elseif tp == 'number' then + return self:createValue('number', exp, exp[1]) + elseif tp == 'name' then + local value = self:getName(exp[1], exp) + return value + elseif tp == 'simple' then + return self:getSimple(exp) + elseif tp == 'index' then + return self:getIndex(exp) + elseif tp == 'binary' then + return self:getBinary(exp) + elseif tp == 'unary' then + return self:getUnary(exp) + elseif tp == 'function' then + return self:buildFunction(exp) + elseif tp == 'table' then + return self:buildTable(exp) + elseif tp == '...' then + return self:loadDots() + elseif tp == 'list' then + return self:getMultiByList(exp) + end + error('Unkown exp type: ' .. tostring(tp)) +end + +function mt:getMultiByList(list) + local multi = createMulti() + for i, exp in ipairs(list) do + multi:push(self:getExp(exp), i == #list) + end + return multi +end + +function mt:doDo(action) + self:instantSource(action) + self:scopePush(action) + self:doActions(action) + self:scopePop() +end + +function mt:doReturn(action) + if #action == 0 then + return + end + self:instantSource(action) + local values = self:unpackList(action) + local func = self:getCurrentFunction() + values:eachValue(function (n, value) + value.uri = self:getUri() + func:mergeReturn(n, value) + local source = action[n] or value:getSource() + if not source or source.start == 0 then + source = self:getDefaultSource() + end + value:addInfo('return', source) + end) +end + +function mt:doLabel(source) + local name = source[1] + local label = self:loadLabel(name) + if label then + self:bindLabel(source, label, 'set') + else + label = self:createLabel(name, source, 'set') + end +end + +function mt:createLabel(name, source, action) + local label = self:bindLabel(source) + if label then + self:saveLabel(label) + return label + end + + label = createLabel(name, source) + self:saveLabel(label) + self:bindLabel(source, label, action) + return label +end + +function mt:doGoTo(source) + local name = source[1] + local label = self:loadLabel(name) + if label then + self:bindLabel(source, label, 'get') + else + label = self:createLabel(name, source, 'get') + end +end + +function mt:setOne(var, value, emmy, comment) + if not value then + value = valueMgr.create('nil', self:getDefaultSource()) + end + value:setEmmy(emmy) + value:setComment(comment) + self:instantSource(var) + if var.type == 'name' then + self:setName(var[1], var, value) + elseif var.type == 'simple' then + local parent = self:getSimple(var, -2) + parent = self:getFirstInMulti(parent) + local key = var[#var] + self:instantSource(key) + key:set('simple', var) + if key.type == 'index' then + local index = self:getIndex(key) + key[1]:set('parent', parent) + parent:setChild(index, value, key[1]) + elseif key.type == 'name' then + local index = key[1] + key:set('parent', parent) + parent:setChild(index, value, key) + end + key:bindValue(value, 'set') + end +end + +function mt:doSet(action) + local emmy = self:getEmmy() + local comment = self:getEmmyComment() + if not action[2] then + return + end + self:instantSource(action) + -- 要先计算值 + local vars = action[1] + local exps = action[2] + local value = self:getExp(exps) + local values = {} + if value.type == 'multi' then + if not emmy then + emmy = value:getEmmy() + end + value:eachValue(function (i, v) + values[i] = v + end) + else + values[1] = value + end + local i = 0 + self:forList(vars, function (var) + i = i + 1 + self:setOne(var, values[i], emmy, comment) + end) +end + +function mt:doLocal(action) + local emmy = self:getEmmy() + local comment = self:getEmmyComment() + self:instantSource(action) + local vars = action[1] + local exps = action[2] + local values + if exps then + local value = self:getExp(exps) + values = {} + if value.type == 'multi' then + if not emmy then + emmy = value:getEmmy() + end + value:eachValue(function (i, v) + values[i] = v + end) + else + values[1] = value + end + end + local i = 0 + self:forList(vars, function (key) + i = i + 1 + local value + if values then + value = values[i] + end + self:createLocal(key[1], key, value, emmy, comment) + end) +end + +function mt:doIf(action) + self:instantSource(action) + for _, block in ipairs(action) do + if block.filter then + self:getExp(block.filter) + end + + self:scopePush(block) + self:doActions(block) + self:scopePop() + end +end + +function mt:doLoop(action) + self:instantSource(action) + local min = self:getFirstInMulti(self:getExp(action.min)) + self:getExp(action.max) + if action.step then + self:getExp(action.step) + end + + self:scopePush(action) + self:createLocal(action.arg[1], action.arg, min) + self:doActions(action) + self:scopePop() +end + +function mt:doIn(action) + local emmyParams = self:getEmmyParams() + self:instantSource(action) + local args = self:unpackList(action.exp) + + self:scopePush(action) + local func = table.remove(args, 1) or valueMgr.create('any', self:getDefaultSource()) + local values = self:call(func, args, action) or createMulti() + self:forList(action.arg, function (arg) + self:instantSource(arg) + local value = table.remove(values, 1) or self:createValue('nil', arg) + if emmyParams then + for i = #emmyParams, 1, -1 do + local emmyParam = emmyParams[i] + if emmyParam and emmyParam:getName() == arg[1] then + value:setEmmy(emmyParam:bindType()) + end + end + end + self:createLocal(arg[1], arg, value) + end) + + self:doActions(action) + + self:scopePop() +end + +function mt:doWhile(action) + self:instantSource(action) + self:getExp(action.filter) + + self:scopePush(action) + self:doActions(action) + self:scopePop() +end + +function mt:doRepeat(action) + self:instantSource(action) + self:scopePush(action) + self:doActions(action) + self:getExp(action.filter) + self:scopePop() +end + +function mt:doFunction(action) + self:instantSource(action) + local name = action.name + if name then + self:instantSource(name) + if name.type == 'simple' then + local parent = self:getSimple(name, -2) + if name[#name-1].type == ':' then + local value = self:buildFunction(action) + local source = name[#name] + self:instantSource(source) + source:set('simple', name) + source:set('parent', parent) + source:set('object', parent) + if source.type == 'index' then + local index = self:getIndex(source) + parent:setChild(index, value, source[1]) + elseif source.type == 'name' then + local index = source[1] + parent:setChild(index, value, source) + end + source:bindValue(value, 'set') + + local func = value:getFunction() + if func then + if #name == 3 then + -- function x:b() + local loc = self:loadLocal(name[1][1]) + if loc then + func:setObject(parent, loc:getSource()) + else + func:setObject(parent, name[#name-2]) + end + else + func:setObject(parent, name[#name-2]) + end + end + else + local value = self:buildFunction(action) + local source = name[#name] + self:instantSource(source) + source:set('simple', name) + source:set('parent', parent) + if source.type == 'index' then + local index = self:getIndex(source) + parent:setChild(index, value, source[1]) + elseif source.type == 'name' then + local index = source[1] + parent:setChild(index, value, source) + end + source:bindValue(value, 'set') + end + else + local value = self:buildFunction(action) + self:setName(name[1], name, value) + end + else + self:buildFunction(action) + end +end + +function mt:doLocalFunction(action) + self:instantSource(action) + local name = action.name + if name then + self:instantSource(name) + if name.type == 'simple' then + self:doFunction(action) + else + local loc = self:createLocal(name[1], name) + local func = self:buildFunction(action) + func:addInfo('local', name) + loc:setValue(func) + name:bindValue(func, 'local') + end + end +end + +function mt:doAction(action) + if not action then + -- Skip + return + end + if coroutine.isyieldable() then + if self.lsp:isNeedCompile(self.uri) then + coroutine.yield() + if self._removed then + coroutine.yield('stop') + return + end + else + self:remove() + coroutine.yield('stop') + return + end + end + local tp = action.type + if tp:sub(1, 4) == 'emmy' then + self:doEmmy(action) + return + end + if tp == 'do' then + self:doDo(action) + elseif tp == 'break' then + elseif tp == 'return' then + self:doReturn(action) + elseif tp == 'label' then + self:doLabel(action) + elseif tp == 'goto' then + self:doGoTo(action) + elseif tp == 'set' then + self:doSet(action) + elseif tp == 'local' then + self:doLocal(action) + elseif tp == 'simple' then + -- call + self:getSimple(action) + action:set('as action', true) + elseif tp == 'if' then + self:doIf(action) + elseif tp == 'loop' then + self:doLoop(action) + elseif tp == 'in' then + self:doIn(action) + elseif tp == 'while' then + self:doWhile(action) + elseif tp == 'repeat' then + self:doRepeat(action) + elseif tp == 'function' then + self:doFunction(action) + elseif tp == 'localfunction' then + self:doLocalFunction(action) + else + self:getExp(action) + action:set('as action', true) + end + self:clearEmmy() +end + +function mt:doActions(actions) + for _, action in ipairs(actions) do + self:doAction(action) + end +end + +function mt:createFunction(source) + local value = self:createValue('function', source) + local func = functionMgr.create(source) + func:setEmmy(self:getEmmyParams(), self:getEmmyReturns(), self:getEmmyOverLoads()) + func:setComment(self:getEmmyComment()) + value:setFunction(func) + value:setType('function', 1.0) + if source:getUri() == self.uri then + self.funcs[#self.funcs+1] = func + end + return value +end + +function mt:callLeftFuncions() + for _, func in ipairs(self.funcs) do + if not func:hasRuned() then + self:runFunction(func) + end + end +end + +function mt:setCurrentFunction(func) + self.currentFunction = func +end + +function mt:getCurrentFunction() + return self.currentFunction +end + +function mt:saveLocal(name, loc) + self.currentFunction:saveLocal(name, loc) +end + +function mt:saveUpvalue(name, loc) + self.currentFunction:saveUpvalue(name, loc) +end + +function mt:loadLocal(name) + return self.currentFunction:loadLocal(name) +end + +function mt:eachLocal(callback) + return self.currentFunction:eachLocal(callback) +end + +function mt:saveLabel(label) + self.currentFunction:saveLabel(label) +end + +function mt:loadLabel(name) + return self.currentFunction:loadLabel(name) +end + +function mt:loadDots() + return self.currentFunction:loadDots() +end + +function mt:getUri() + return self.currentFunction and self.currentFunction:getUri() or self.uri +end + +function mt:instantSource(source) + if self:isRemoved() then + error('dead vm') + return nil + end + if sourceMgr.instant(source) then + source:setUri(self:getUri()) + self.sources[#self.sources+1] = source + --CachedSource[source] = true + end + return source +end + +function mt:bindLocal(source, loc, action) + if not source then + return + end + self:instantSource(source) + if loc then + source:bindLocal(loc, action) + else + return source:bindLocal() + end +end + +function mt:bindLabel(source, label, action) + self:instantSource(source) + if label then + source:bindLabel(label, action) + else + return source:bindLabel() + end +end + +function mt:createLocal(key, source, value, emmy, comment) + local loc = self:bindLocal(source) + if not value then + value = self:createValue('nil', source) + end + if loc then + loc:setValue(value) + loc:setEmmy(emmy) + self:saveLocal(key, loc) + return loc + end + + loc = localMgr.create(key, source, value, source.tags) + loc:setEmmy(emmy) + loc:setComment(comment) + self:saveLocal(key, loc) + self:bindLocal(source, loc, 'local') + loc:close(self:getCurrentFunction():getSource().finish) + value:addInfo('local', source) + return loc +end + +function mt:createUpvalue(key, source, value) + local loc = self:bindLocal(source) + if not value then + value = self:createValue('nil', source) + end + if loc then + loc:setValue(value) + self:saveUpvalue(key, loc) + return loc + end + + loc = localMgr.create(key, source, value) + self:saveUpvalue(key, loc) + self:bindLocal(source, loc, 'local') + value:addInfo('local', source) + return loc +end + +function mt:createEnvironment(ast) + -- 整个文件是一个函数 + self.main = self:createFunction(ast) + self:setCurrentFunction(self.main:getFunction()) + if self.lsp then + self.main:getFunction():mergeReturn(1, self.lsp.chain:get(self.uri)) + end + -- 全局变量`_G` + local global = buildGlobal(self.lsp) + local env + if self.envType == '_ENV' then + -- 隐藏的上值`_ENV` + env = self:createUpvalue('_ENV', self:getDefaultSource(), global) + else + -- 为了实现方便,fenv也使用隐藏上值来实现 + -- 使用了非法标识符保证用户无法访问 + env = self:createUpvalue('@ENV', self:getDefaultSource(), global) + end + env:set('hide', true) + self.env = env +end + +function mt:eachSource(callback) + if self._removed then + return + end + local sources = self.sources + for i = 1, #sources do + local res = callback(sources[i]) + if res ~= nil then + return res + end + end +end + +function mt:isRemoved() + return self._removed == true +end + +function mt:remove() + if self._removed then + return + end + self._removed = true + for _, source in ipairs(self.sources) do + source:kill() + end + self.sources = nil + for _, func in ipairs(self.funcs) do + func:kill() + end + self.funcs = nil +end + +local function compile(vm, ast, lsp, uri) + -- 创建初始环境 + ast.uri = vm.uri + -- 根据运行版本决定环境实现方式 + if config.config.runtime.version == 'Lua 5.1' or config.config.runtime.version == 'LuaJIT' then + vm.envType = 'fenv' + else + vm.envType = '_ENV' + end + vm:instantSource(ast) + vm:createEnvironment(ast) + + -- 检查所有没有调用过的函数,调用一遍 + vm:callLeftFuncions() + + return vm +end + +return function (ast, lsp, uri, text) + if not ast then + return nil, 'Ast failed' + end + local vm = setmetatable({ + funcs = {}, + sources = {}, + main = nil, + env = nil, + emmy = nil, + ---@type emmyMgr + emmyMgr = lsp and lsp.emmy or emmyMgr(), + lsp = lsp, + uri = uri or '', + text = text or '', + }, mt) + local suc, res = xpcall(compile, log.error, vm, ast, lsp, uri) + if not suc then + vm:remove() + return nil, res + end + return res +end |