minetest_x_bows/bin/lua-language-server-3.5.6-l.../script/vm/compiler.lua
2022-11-03 11:37:33 -04:00

1856 lines
59 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

local guide = require 'parser.guide'
local util = require 'utility'
local config = require 'config'
local rpath = require 'workspace.require-path'
local files = require 'files'
---@class vm
local vm = require 'vm.vm'
local LOCK = {}
---@class parser.object
---@field _compiledNodes boolean
---@field _node vm.node
---@field _globalBase table
---@field cindex integer
---@field func parser.object
---@field operators? parser.object[]
-- 该函数有副作用会给source绑定node
---@param source parser.object
---@return boolean
local function bindDocs(source)
local docs = source.bindDocs
if not docs then
return false
end
for i = #docs, 1, -1 do
local doc = docs[i]
if doc.type == 'doc.type' then
vm.setNode(source, vm.compileNode(doc))
return true
end
if doc.type == 'doc.class' then
vm.setNode(source, vm.compileNode(doc))
return true
end
if doc.type == 'doc.param' then
local node = vm.compileNode(doc)
if doc.optional then
node:addOptional()
end
vm.setNode(source, node)
return true
end
if doc.type == 'doc.module' then
local name = doc.module
if not name then
return true
end
local uri = rpath.findUrisByRequireName(guide.getUri(source), name)[1]
if not uri then
return true
end
local state = files.getState(uri)
local ast = state and state.ast
if not ast then
return true
end
vm.setNode(source, vm.compileNode(ast))
return true
end
if doc.type == 'doc.overload' then
vm.setNode(source, vm.compileNode(doc))
end
end
return false
end
---@param source parser.object
---@param key any
---@param ref boolean
---@param pushResult fun(res: parser.object, markDoc?: boolean)
local function searchFieldByLocalID(source, key, ref, pushResult)
local fields
if key then
fields = vm.getLocalSourcesSets(source, key)
if ref then
local gets = vm.getLocalSourcesGets(source, key)
if gets then
fields = fields or {}
for _, src in ipairs(gets) do
fields[#fields+1] = src
end
end
end
else
fields = vm.getLocalFields(source, false)
end
if not fields then
return
end
local hasMarkDoc = {}
for _, src in ipairs(fields) do
if src.bindDocs then
if bindDocs(src) then
local skey = guide.getKeyName(src)
if skey then
hasMarkDoc[skey] = true
end
pushResult(src, true)
end
end
end
for _, src in ipairs(fields) do
local skey = guide.getKeyName(src)
if not hasMarkDoc[skey] then
pushResult(src)
end
end
end
---@param suri uri
---@param source parser.object
---@param key any
---@param ref boolean
---@param pushResult fun(res: parser.object, markDoc?: boolean)
local function searchFieldByGlobalID(suri, source, key, ref, pushResult)
local node = source._globalNode
if not node then
return
end
if node.cate == 'variable' then
if key then
if type(key) ~= 'string' then
return
end
local global = vm.getGlobal('variable', node.name, key)
if global then
for _, set in ipairs(global:getSets(suri)) do
pushResult(set)
end
for _, get in ipairs(global:getGets(suri)) do
pushResult(get)
end
end
else
local globals = vm.getGlobalFields('variable', node.name)
for _, global in ipairs(globals) do
for _, set in ipairs(global:getSets(suri)) do
pushResult(set)
end
for _, get in ipairs(global:getGets(suri)) do
pushResult(get)
end
end
end
end
if node.cate == 'type' then
vm.getClassFields(suri, node, key, ref, pushResult)
end
end
local searchFieldSwitch = util.switch()
: case 'table'
: call(function (suri, source, key, ref, pushResult)
local hasFiled = false
for _, field in ipairs(source) do
if field.type == 'tablefield'
or field.type == 'tableindex' then
local fieldKey = guide.getKeyName(field)
if key == nil
or key == fieldKey then
hasFiled = true
pushResult(field)
end
end
if field.type == 'tableexp' then
if key == nil
or key == field.tindex then
hasFiled = true
pushResult(field)
end
end
if field.type == 'varargs' then
if not hasFiled
and type(key) == 'number'
and key >= 1
and math.tointeger(key) then
hasFiled = true
pushResult(field)
end
if key == nil then
pushResult(field)
end
end
end
end)
: case 'string'
: case 'doc.type.string'
: call(function (suri, source, key, ref, pushResult)
-- change to `string: stringlib` ?
local stringlib = vm.getGlobal('type', 'stringlib')
if stringlib then
vm.getClassFields(suri, stringlib, key, ref, pushResult)
end
end)
: case 'doc.type.array'
: call(function (suri, source, key, ref, pushResult)
if type(key) == 'number' then
if key < 1
or not math.tointeger(key) then
return
end
pushResult(source.node)
end
if type(key) == 'table' then
if vm.isSubType(suri, key, 'integer') then
pushResult(source.node)
end
end
end)
: case 'doc.type.table'
: call(function (suri, source, key, ref, pushResult)
for _, field in ipairs(source.fields) do
local fieldKey = field.name
if fieldKey.type == 'doc.type' then
local fieldNode = vm.compileNode(fieldKey)
for fn in fieldNode:eachObject() do
if fn.type == 'global' and fn.cate == 'type' then
if key == nil
or fn.name == 'any'
or (fn.name == 'boolean' and type(key) == 'boolean')
or (fn.name == 'number' and type(key) == 'number')
or (fn.name == 'integer' and math.tointeger(key))
or (fn.name == 'string' and type(key) == 'string') then
pushResult(field)
end
end
end
end
if fieldKey.type == 'doc.field.name' then
if key == nil or fieldKey[1] == key then
pushResult(field)
end
end
end
end)
: case 'global'
: call(function (suri, node, key, ref, pushResult)
if node.cate == 'variable' then
if key then
if type(key) ~= 'string' then
return
end
local global = vm.getGlobal('variable', node.name, key)
if global then
for _, set in ipairs(global:getSets(suri)) do
pushResult(set)
end
if ref then
for _, get in ipairs(global:getGets(suri)) do
pushResult(get)
end
end
end
else
local globals = vm.getGlobalFields('variable', node.name)
for _, global in ipairs(globals) do
for _, set in ipairs(global:getSets(suri)) do
pushResult(set)
end
if ref then
for _, get in ipairs(global:getGets(suri)) do
pushResult(get)
end
end
end
end
end
if node.cate == 'type' then
vm.getClassFields(suri, node, key, ref, pushResult)
end
end)
: default(function (suri, source, key, ref, pushResult)
searchFieldByLocalID(source, key, ref, pushResult)
searchFieldByGlobalID(suri, source, key, ref, pushResult)
end)
---@param suri uri
---@param object vm.global
---@param key string|vm.global
---@param ref boolean
---@param pushResult fun(field: vm.object, isMark?: boolean)
function vm.getClassFields(suri, object, key, ref, pushResult)
local mark = {}
local function searchClass(class, searchedFields)
local name = class.name
if mark[name] then
return
end
mark[name] = true
searchedFields = searchedFields or {}
for _, set in ipairs(class:getSets(suri)) do
if set.type == 'doc.class' then
-- check ---@field
local hasFounded = {}
local function copyToSearched()
for fieldKey in pairs(hasFounded) do
searchedFields[fieldKey] = true
hasFounded[fieldKey] = nil
end
end
for _, field in ipairs(set.fields) do
local fieldKey = guide.getKeyName(field)
if fieldKey then
-- ---@field x boolean -> class.x
if key == nil
or fieldKey == key then
if not searchedFields[fieldKey] then
pushResult(field, true)
hasFounded[fieldKey] = true
end
end
elseif key and not hasFounded[key] then
local keyType = type(key)
if keyType == 'table' then
-- ---@field [integer] boolean -> class[integer]
local fieldNode = vm.compileNode(field.field)
if vm.isSubType(suri, key.name, fieldNode) then
local nkey = '|' .. key.name
if not searchedFields[nkey] then
pushResult(field, true)
hasFounded[nkey] = true
end
end
else
local typeName
if keyType == 'number' then
if math.tointeger(key) then
typeName = 'integer'
else
typeName = 'number'
end
elseif keyType == 'boolean'
or keyType == 'string' then
typeName = keyType
end
if typeName and field.field.type ~= 'doc.field.name' then
-- ---@field [integer] boolean -> class[1]
local fieldNode = vm.compileNode(field.field)
if vm.isSubType(suri, typeName, fieldNode) then
local nkey = '|' .. typeName
if not searchedFields[nkey] then
pushResult(field, true)
hasFounded[nkey] = true
end
end
end
end
end
end
copyToSearched()
-- check local field and global field
if not searchedFields[key] and set.bindSource then
local src = set.bindSource
if src.value and src.value.type == 'table' then
searchFieldSwitch('table', suri, src.value, key, ref, function (field)
local fieldKey = guide.getKeyName(field)
if fieldKey then
if not searchedFields[fieldKey]
and guide.isSet(field) then
hasFounded[fieldKey] = true
pushResult(field, true)
end
end
end)
end
copyToSearched()
searchFieldSwitch(src.type, suri, src, key, ref, function (field)
local fieldKey = guide.getKeyName(field)
if fieldKey and not searchedFields[fieldKey] then
if not searchedFields[fieldKey]
and guide.isSet(field)
and field.value then
if vm.getLocalID(field)
and vm.getLocalID(field) == vm.getLocalID(field.value) then
elseif src._globalNode
and src._globalNode == field.value._globalNode then
else
hasFounded[fieldKey] = true
end
pushResult(field, true)
end
end
end)
copyToSearched()
end
-- look into extends(if field not found)
if not searchedFields[key] and set.extends then
for _, extend in ipairs(set.extends) do
if extend.type == 'doc.extends.name' then
local extendType = vm.getGlobal('type', extend[1])
if extendType then
searchClass(extendType, searchedFields)
end
end
end
copyToSearched()
end
end
end
end
local function searchGlobal(class)
if class.cate == 'type' and class.name == '_G' then
if key == nil then
local sets = vm.getGlobalSets(suri, 'variable')
for _, set in ipairs(sets) do
pushResult(set)
end
elseif type(key) == 'string' then
local global = vm.getGlobal('variable', key)
if global then
for _, set in ipairs(global:getSets(suri)) do
pushResult(set)
end
end
end
end
end
searchClass(object)
searchGlobal(object)
end
---@class parser.object
---@field _sign vm.sign|false
---@param source parser.object
---@return vm.sign|false
local function getObjectSign(source)
if source._sign ~= nil then
return source._sign
end
source._sign = false
if source.type == 'function' then
if not source.bindDocs then
return false
end
for _, doc in ipairs(source.bindDocs) do
if doc.type == 'doc.generic' then
if not source._sign then
source._sign = vm.createSign()
break
end
end
end
if not source._sign then
return false
end
if source.args then
for _, arg in ipairs(source.args) do
local argNode = vm.compileNode(arg)
if arg.optional then
argNode:addOptional()
end
source._sign:addSign(argNode)
end
end
end
if source.type == 'doc.type.function'
or source.type == 'doc.type.table'
or source.type == 'doc.type.array' then
local hasGeneric
guide.eachSourceType(source, 'doc.generic.name', function ()
hasGeneric = true
end)
if not hasGeneric then
return false
end
source._sign = vm.createSign()
if source.type == 'doc.type.function' then
for _, arg in ipairs(source.args) do
if arg.extends then
local argNode = vm.compileNode(arg.extends)
if arg.optional then
argNode:addOptional()
end
source._sign:addSign(argNode)
else
source._sign:addSign(vm.createNode())
end
end
end
end
return source._sign
end
---@param func parser.object
---@param index integer
---@return (parser.object|vm.generic)?
function vm.getReturnOfFunction(func, index)
if func.type == 'function' then
if not func._returns then
func._returns = {}
end
if not func._returns[index] then
func._returns[index] = {
type = 'function.return',
parent = func,
returnIndex = index,
}
end
return func._returns[index]
end
if func.type == 'doc.type.function' then
local rtn = func.returns[index]
if not rtn then
local lastReturn = func.returns[#func.returns]
if lastReturn and lastReturn.name and lastReturn.name[1] == '...' then
rtn = lastReturn
else
return nil
end
end
local sign = getObjectSign(func)
if not sign then
return rtn
end
return vm.createGeneric(rtn, sign)
end
return nil
end
---@param args parser.object[]
---@return vm.node
local function getReturnOfSetMetaTable(args)
local tbl = args[1]
local mt = args[2]
local node = vm.createNode()
if tbl then
node:merge(vm.compileNode(tbl))
end
if mt then
vm.compileByParentNode(mt, '__index', false, function (src)
for n in vm.compileNode(src):eachObject() do
if n.type == 'global'
or n.type == 'local'
or n.type == 'table'
or n.type == 'doc.type.table' then
node:merge(n)
end
end
end)
end
return node
end
---@param source parser.object
local function matchCall(source)
local call = source.parent
if not call
or call.type ~= 'call'
or call.node ~= source then
return
end
local funcs = vm.getMatchedFunctions(source, call.args)
local myNode = vm.getNode(source)
if not myNode then
return
end
local needRemove
for n in myNode:eachObject() do
if n.type == 'function'
or n.type == 'doc.type.function' then
if not util.arrayHas(funcs, n) then
if not needRemove then
needRemove = vm.createNode()
end
needRemove:merge(n)
end
end
end
if needRemove then
local newNode = myNode:copy()
newNode:removeNode(needRemove)
newNode:setData('originNode', myNode)
vm.setNode(source, newNode, true)
end
end
---@param func parser.object
---@param index integer
---@param args parser.object[]
---@return vm.node
local function getReturn(func, index, args)
if not func._callReturns then
func._callReturns = {}
end
if not func._callReturns[index] then
local call = func.parent
func._callReturns[index] = {
type = 'call.return',
parent = call,
func = func,
cindex = index,
args = args,
start = call.start,
finish = call.finish,
}
end
return vm.compileNode(func._callReturns[index])
end
---@param source parser.object
---@return boolean
local function bindAs(source)
local root = guide.getRoot(source)
local docs = root.docs
if not docs then
return false
end
local ases = docs._asCache
if not ases then
ases = {}
docs._asCache = ases
for _, doc in ipairs(docs) do
if doc.type == 'doc.as' and doc.as and doc.touch then
ases[#ases+1] = doc
end
end
table.sort(ases, function (a, b)
return a.touch < b.touch
end)
end
if #ases == 0 then
return false
end
local max = #ases
local index
local left = 1
local right = max
for _ = 1, 1000 do
if left == right then
index = left
break
end
index = left + (right - left) // 2
local doc = ases[index]
if doc.touch < source.finish then
left = index + 1
else
right = index
end
end
local doc = ases[index]
if doc and doc.touch == source.finish then
vm.setNode(source, vm.compileNode(doc.as), true)
return true
end
return false
end
---@param source parser.object
---@param key? string|vm.global
---@param pushResult fun(source: parser.object)
function vm.compileByParentNode(source, key, ref, pushResult)
local parentNode = vm.compileNode(source)
local docedResults = {}
local commonResults = {}
local mark = {}
local suri = guide.getUri(source)
local hasClass
for node in parentNode:eachObject() do
if node.type == 'global'
and node.cate == 'type'
---@cast node vm.global
and not guide.isBasicType(node.name) then
hasClass = true
break
end
end
for node in parentNode:eachObject() do
if not hasClass
or (
node.type == 'global'
and node.cate == 'type'
---@cast node vm.global
and not guide.isBasicType(node.name)
)
or guide.isLiteral(node) then
searchFieldSwitch(node.type, suri, node, key, ref, function (res, markDoc)
if mark[res] then
return
end
mark[res] = true
if markDoc then
docedResults[#docedResults+1] = res
else
commonResults[#commonResults+1] = res
end
end)
end
end
if not next(mark) then
searchFieldByLocalID(source, key, ref, function (res, markDoc)
if mark[res] then
return
end
mark[res] = true
if markDoc then
docedResults[#docedResults+1] = res
else
commonResults[#commonResults+1] = res
end
end)
end
if #docedResults > 0 then
for _, res in ipairs(docedResults) do
pushResult(res)
end
end
if #docedResults == 0 or key == nil then
for _, res in ipairs(commonResults) do
pushResult(res)
end
end
end
---@param list parser.object[]
---@param index integer
---@return vm.node
---@return parser.object?
function vm.selectNode(list, index)
local exp
if list[index] then
exp = list[index]
index = 1
else
for i = index, 1, -1 do
if list[i] then
local last = list[i]
if last.type == 'call'
or last.type == 'varargs' then
index = index - i + 1
exp = last
end
break
end
end
end
if not exp then
return vm.createNode(vm.declareGlobal('type', 'nil')), nil
end
---@type vm.node?
local result
if exp.type == 'call' then
result = getReturn(exp.node, index, exp.args)
if result:isEmpty() then
result:merge(vm.declareGlobal('type', 'unknown'))
end
else
---@type vm.node
result = vm.compileNode(exp)
if result:isEmpty() then
result:merge(vm.declareGlobal('type', 'unknown'))
end
end
return result, exp
end
---@param source parser.object
---@param list parser.object[]
---@param index integer
---@return vm.node
local function selectNode(source, list, index)
local result = vm.selectNode(list, index)
if source.type == 'function.return' then
-- remove any for returns
local rtnNode = vm.createNode()
for n in result:eachObject() do
if guide.isLiteral(n) then
rtnNode:merge(n)
end
if n.type == 'global' and n.cate == 'type' then
if n.name ~= 'any' then
rtnNode:merge(n)
end
else
rtnNode:merge(n)
end
end
vm.setNode(source, rtnNode)
return rtnNode
end
vm.setNode(source, result)
return result
end
---@param source parser.object
---@param node vm.node.object
---@return boolean
local function isValidCallArgNode(source, node)
if source.type == 'function' then
return node.type == 'doc.type.function'
end
if source.type == 'table' then
return node.type == 'doc.type.table' or node.type == 'doc.type.array'
or ( node.type == 'global'
and node.cate == 'type'
---@cast node vm.global
and not guide.isBasicType(node.name)
)
end
if source.type == 'dummyarg' then
return true
end
return false
end
---@param func parser.object
---@param index integer
---@return parser.object?
local function getFuncArg(func, index)
local args = func.args
if not args then
return nil
end
if args[index] then
return args[index]
end
local lastArg = args[#args]
if lastArg and lastArg.type == '...' then
return lastArg
end
return nil
end
---@param arg parser.object
---@param call parser.object
---@param callNode vm.node
---@param fixIndex integer
---@param myIndex integer
local function compileCallArgNode(arg, call, callNode, fixIndex, myIndex)
local eventIndex, eventMap
if call.args then
for i = 1, 2 do
local eventArg = call.args[i + fixIndex]
if not eventArg then
break
end
eventMap = vm.getLiterals(eventArg)
if eventMap then
eventIndex = i
break
end
end
end
for n in callNode:eachObject() do
if n.type == 'function' then
---@cast n parser.object
local sign = getObjectSign(n)
local farg = getFuncArg(n, myIndex)
if farg then
for fn in vm.compileNode(farg):eachObject() do
if isValidCallArgNode(arg, fn) then
if fn.type == 'doc.type.function' then
---@cast fn parser.object
if sign then
local generic = vm.createGeneric(fn, sign)
local args = {}
for i = fixIndex + 1, myIndex - 1 do
args[#args+1] = call.args[i]
end
fn = generic:resolve(guide.getUri(call), args)
end
end
vm.setNode(arg, fn)
end
end
end
end
if n.type == 'doc.type.function' then
---@cast n parser.object
local myEvent
if n.args[eventIndex] then
local argNode = vm.compileNode(n.args[eventIndex])
myEvent = argNode:get(1)
end
if not myEvent
or not eventMap
or myIndex <= eventIndex
or myEvent.type ~= 'doc.type.string'
or eventMap[myEvent[1]] then
local farg = getFuncArg(n, myIndex)
if farg then
for fn in vm.compileNode(farg):eachObject() do
if isValidCallArgNode(arg, fn) then
vm.setNode(arg, fn)
end
end
end
end
end
end
end
---@param arg parser.object
---@param call parser.object
---@param index? integer
---@return vm.node?
function vm.compileCallArg(arg, call, index)
if not index then
for i, carg in ipairs(call.args) do
if carg == arg then
index = i
break
end
end
if not index then
return nil
end
end
local callNode = vm.compileNode(call.node)
compileCallArgNode(arg, call, callNode, 0, index)
if call.node.special == 'pcall'
or call.node.special == 'xpcall' then
local fixIndex = call.node.special == 'pcall' and 1 or 2
if call.args and call.args[1] then
callNode = vm.compileNode(call.args[1])
compileCallArgNode(arg, call, callNode, fixIndex, index - fixIndex)
end
end
return vm.getNode(arg)
end
---@class parser.object
---@field _iterator? table
---@field _iterArgs? table
---@field _iterVars? table<parser.object, vm.node>
---@param source parser.object
---@param target parser.object
local function compileForVars(source, target)
if not source.exps then
return
end
-- for k, v in pairs(t) do
--> for k, v in iterator, status, initValue do
--> local k, v = iterator(status, initValue)
if not source._iterator then
source._iterator = {
type = 'dummyfunc',
parent = source,
}
source._iterArgs = {{},{}}
source._iterVars = {}
end
-- iterator
if not vm.getNode(source._iterator) then
selectNode(source._iterator, source.exps, 1)
end
-- status
if not vm.getNode(source._iterArgs[1]) then
selectNode(source._iterArgs[1], source.exps, 2)
end
-- initValue
if not vm.getNode(source._iterArgs[2]) then
selectNode(source._iterArgs[2], source.exps, 3)
end
if source.keys then
for i, loc in ipairs(source.keys) do
if loc == target then
local node = getReturn(source._iterator, i, source._iterArgs)
node:removeOptional()
vm.setNode(loc, node)
end
end
end
end
---@param source parser.object
local function compileLocal(source)
vm.setNode(source, source)
local hasMarkDoc
if source.bindDocs then
hasMarkDoc = bindDocs(source)
end
local hasMarkParam
if source.type == 'self' and not hasMarkDoc then
hasMarkParam = true
if source.parent.type == 'callargs' then
-- obj:func(...)
if source.parent.parent and source.parent.parent.node and source.parent.parent.node.node then
vm.setNode(source, vm.compileNode(source.parent.parent.node.node))
end
else
-- function obj:func(...)
if source.parent.parent and source.parent.parent.parent and source.parent.parent.parent.node then
vm.setNode(source, vm.compileNode(source.parent.parent.parent.node))
end
end
vm.getNode(source):remove 'function'
end
local hasMarkValue
if not hasMarkDoc and source.value then
hasMarkValue = true
if source.value.type == 'table' then
vm.setNode(source, source.value)
elseif source.value.type ~= 'nil' then
vm.setNode(source, vm.compileNode(source.value))
end
end
if not hasMarkValue and not hasMarkValue then
if source.ref then
for _, ref in ipairs(source.ref) do
if ref.type == 'setlocal'
and ref.value
and ref.value.type == 'function' then
vm.setNode(source, vm.compileNode(ref.value))
end
end
end
end
-- function x.y(self, ...) --> function x:y(...)
if source[1] == 'self'
and not hasMarkDoc
and source.parent.type == 'funcargs'
and source.parent[1] == source then
local setfield = source.parent.parent.parent
if setfield.type == 'setfield' then
hasMarkParam = true
vm.setNode(source, vm.compileNode(setfield.node))
end
end
if source.parent.type == 'funcargs' and not hasMarkDoc and not hasMarkParam then
local func = source.parent.parent
local funcNode = vm.compileNode(func)
local hasDocArg
for n in funcNode:eachObject() do
if n.type == 'doc.type.function' then
for index, arg in ipairs(n.args) do
if func.args[index] == source then
local argNode = vm.compileNode(arg)
for an in argNode:eachObject() do
if an.type ~= 'doc.generic.name' then
vm.setNode(source, an)
end
end
hasDocArg = true
end
end
end
end
if not hasDocArg then
vm.setNode(source, vm.declareGlobal('type', 'any'))
end
end
-- for x in ... do
if source.parent.type == 'in' then
compileForVars(source.parent, source)
end
-- for x = ... do
if source.parent.type == 'loop' then
if source.parent.loc == source then
if bindDocs(source) then
return
end
vm.setNode(source, vm.declareGlobal('type', 'integer'))
end
end
vm.getNode(source):setData('hasDefined', hasMarkDoc or hasMarkParam or hasMarkValue)
end
local compilerSwitch = util.switch()
: case 'nil'
: case 'boolean'
: case 'integer'
: case 'number'
: case 'string'
: case 'doc.type.function'
: case 'doc.type.table'
: case 'doc.type.array'
: call(function (source)
vm.setNode(source, source)
end)
: case 'table'
: call(function (source)
vm.setNode(source, source)
if source.parent.type == 'callargs' then
local call = source.parent.parent
vm.compileCallArg(source, call)
end
if source.parent.type == 'setglobal'
or source.parent.type == 'local'
or source.parent.type == 'setlocal'
or source.parent.type == 'tablefield'
or source.parent.type == 'tableindex'
or source.parent.type == 'tableexp'
or source.parent.type == 'setfield'
or source.parent.type == 'setindex' then
local parentNode = vm.compileNode(source.parent)
for _, pn in ipairs(parentNode) do
if pn.type == 'global'
and pn.cate == 'type' then
---@cast pn vm.global
if not guide.isBasicType(pn.name) then
vm.setNode(source, pn)
end
elseif pn.type == 'doc.type.table' or pn.type == 'doc.type.array' then
vm.setNode(source, pn)
end
end
end
end)
: case 'function'
: call(function (source)
vm.setNode(source, source)
if source.bindDocs then
for _, doc in ipairs(source.bindDocs) do
if doc.type == 'doc.overload' then
vm.setNode(source, vm.compileNode(doc))
end
end
end
-- table.sort(string[], function (<?x?>) end)
if source.parent.type == 'callargs' then
local call = source.parent.parent
vm.compileCallArg(source, call)
end
end)
: case 'paren'
: call(function (source)
if bindAs(source) then
return
end
if source.exp then
vm.setNode(source, vm.compileNode(source.exp))
end
end)
: case 'local'
: case 'self'
---@param source parser.object
: call(function (source)
compileLocal(source)
local refs = source.ref
if not refs then
return
end
local hasMark = vm.getNode(source):getData 'hasDefined'
vm.launchRunner(source, function (src, node)
if src.type == 'setlocal' then
if src.bindDocs then
for _, doc in ipairs(src.bindDocs) do
if doc.type == 'doc.type' then
vm.setNode(src, vm.compileNode(doc), true)
return vm.getNode(src)
end
end
end
if src.value then
if src.value.type == 'table' then
vm.setNode(src, vm.createNode(src.value))
vm.setNode(src, node:copy():asTable())
else
local function clearLockedNode(child)
if not child then
return
end
if child.type == 'function' then
return
end
if child.type == 'setlocal'
or child.type == 'getlocal' then
if child.node == source then
return
end
end
if LOCK[child] then
vm.removeNode(child)
end
guide.eachChild(child, clearLockedNode)
end
clearLockedNode(src.value)
vm.setNode(src, vm.compileNode(src.value), true)
end
else
vm.setNode(src, node, true)
end
return vm.getNode(src)
elseif src.type == 'getlocal' then
if bindAs(src) then
return
end
vm.setNode(src, node, true)
matchCall(src)
end
end)
if not hasMark then
local parentFunc = guide.getParentFunction(source)
for _, ref in ipairs(source.ref) do
if ref.type == 'setlocal'
and guide.getParentFunction(ref) == parentFunc then
local refNode = vm.getNode(ref)
if refNode then
vm.setNode(source, refNode)
end
end
end
end
end)
: case 'setlocal'
: call(function (source)
vm.compileNode(source.node)
end)
: case 'getlocal'
: call(function (source)
if bindAs(source) then
return
end
vm.compileNode(source.node)
end)
: case 'setfield'
: case 'setmethod'
: case 'setindex'
: case 'getfield'
: case 'getmethod'
: case 'getindex'
: call(function (source)
if guide.isGet(source) and bindAs(source) then
return
end
---@type (string|vm.node)?
local key = guide.getKeyName(source)
if key == nil and source.index then
key = vm.compileNode(source.index)
end
if key == nil then
return
end
if type(key) == 'table' then
---@cast key vm.node
local uri = guide.getUri(source)
local value = vm.getTableValue(uri, vm.compileNode(source.node), key)
if value then
vm.setNode(source, value)
end
for k in key:eachObject() do
if k.type == 'global' and k.cate == 'type' then
---@cast k vm.global
vm.compileByParentNode(source.node, k, false, function (src)
vm.setNode(source, vm.compileNode(src))
if src.value then
vm.setNode(source, vm.compileNode(src.value))
end
end)
end
end
else
---@cast key string
vm.compileByParentNode(source.node, key, false, function (src)
if src.value then
if bindDocs(src) then
vm.setNode(source, vm.compileNode(src))
elseif src.value.type ~= 'nil' then
vm.setNode(source, vm.compileNode(src.value))
local node = vm.getNode(src)
if node then
vm.setNode(source, node)
end
end
else
vm.setNode(source, vm.compileNode(src))
end
end)
end
end)
: case 'setglobal'
: call(function (source)
if source.node[1] ~= '_ENV' then
return
end
local key = guide.getKeyName(source)
vm.compileByParentNode(source.node, key, false, function (src)
if src.type == 'doc.type.field'
or src.type == 'doc.field' then
vm.setNode(source, vm.compileNode(src))
end
end)
end)
: case 'getglobal'
: call(function (source)
if bindAs(source) then
return
end
if source.node[1] ~= '_ENV' then
return
end
local key = guide.getKeyName(source)
vm.compileByParentNode(source.node, key, false, function (src)
vm.setNode(source, vm.compileNode(src))
end)
end)
: case 'tablefield'
: case 'tableindex'
: call(function (source)
local hasMarkDoc
if source.bindDocs then
hasMarkDoc = bindDocs(source)
end
if not hasMarkDoc then
vm.compileByParentNode(source.node, guide.getKeyName(source), false, function (src)
if src.type == 'doc.field'
or src.type == 'doc.type.field' then
hasMarkDoc = true
vm.setNode(source, vm.compileNode(src))
end
end)
end
if not hasMarkDoc and source.value then
vm.setNode(source, vm.compileNode(source.value))
end
end)
: case 'field'
: case 'method'
: call(function (source)
vm.setNode(source, vm.compileNode(source.parent))
end)
: case 'tableexp'
: call(function (source)
if (source.parent.type == 'table') then
local node = vm.compileNode(source.parent)
for n in node:eachObject() do
if n.type == 'doc.type.array' then
vm.setNode(source, vm.compileNode(n.node))
end
end
end
vm.setNode(source, vm.compileNode(source.value))
end)
: case 'function.return'
---@param source parser.object
: call(function (source)
local func = source.parent
local index = source.returnIndex
local hasMarkDoc
if func.bindDocs then
local sign = getObjectSign(func)
local lastReturn
for _, doc in ipairs(func.bindDocs) do
if doc.type == 'doc.return' then
for _, rtn in ipairs(doc.returns) do
lastReturn = rtn
if rtn.returnIndex == index then
hasMarkDoc = true
local hasGeneric
if sign then
guide.eachSourceType(rtn, 'doc.generic.name', function (src)
hasGeneric = true
end)
end
if hasGeneric then
---@cast sign -false
vm.setNode(source, vm.createGeneric(rtn, sign))
else
vm.setNode(source, vm.compileNode(rtn))
end
end
end
end
end
if lastReturn
and not hasMarkDoc then
if lastReturn.name and lastReturn.name[1] == '...' then
hasMarkDoc = true
vm.setNode(source, vm.compileNode(lastReturn))
end
end
end
local hasReturn
if func.returns and not hasMarkDoc then
for _, rtn in ipairs(func.returns) do
if selectNode(source, rtn, index) then
hasReturn = true
end
end
if hasReturn then
local hasKnownType
local hasUnknownType
for n in vm.getNode(source):eachObject() do
if guide.isLiteral(n) then
if n.type ~= 'nil' then
hasKnownType = true
break
end
goto CONTINUE
end
if n.type == 'global' and n.cate == 'type' then
if n.name ~= 'nil' then
hasKnownType = true
break
end
goto CONTINUE
end
hasUnknownType = true
::CONTINUE::
end
if not hasKnownType and hasUnknownType then
vm.setNode(source, vm.declareGlobal('type', 'unknown'))
end
end
end
if not hasMarkDoc and not hasReturn then
vm.setNode(source, vm.declareGlobal('type', 'nil'))
end
end)
: case 'call.return'
---@param source parser.object
: call(function (source)
if bindAs(source) then
return
end
local func = source.func
local args = source.args
local index = source.cindex
if func.special == 'setmetatable' then
if not args then
return
end
vm.setNode(source, getReturnOfSetMetaTable(args))
return
end
if func.special == 'pcall' and index > 1 then
if not args then
return
end
local newArgs = {}
for i = 2, #args do
newArgs[#newArgs+1] = args[i]
end
local node = getReturn(args[1], index - 1, newArgs)
if node then
vm.setNode(source, node)
end
return
end
if func.special == 'xpcall' and index > 1 then
if not args then
return
end
local newArgs = {}
for i = 3, #args do
newArgs[#newArgs+1] = args[i]
end
local node = getReturn(args[1], index - 1, newArgs)
if node then
vm.setNode(source, node)
end
return
end
if func.special == 'require' then
if not args then
return
end
local nameArg = args[1]
if not nameArg or nameArg.type ~= 'string' then
return
end
local name = nameArg[1]
if not name or type(name) ~= 'string' then
return
end
local uri = rpath.findUrisByRequireName(guide.getUri(func), name)[1]
if not uri then
return
end
local state = files.getState(uri)
local ast = state and state.ast
if not ast then
return
end
vm.setNode(source, vm.compileNode(ast))
return
end
local funcNode = vm.compileNode(func)
---@type vm.node?
for mfunc in funcNode:eachObject() do
if mfunc.type == 'function'
or mfunc.type == 'doc.type.function' then
---@cast mfunc parser.object
local returnObject = vm.getReturnOfFunction(mfunc, index)
if returnObject then
local returnNode = vm.compileNode(returnObject)
for rnode in returnNode:eachObject() do
if rnode.type == 'generic' then
returnNode = rnode:resolve(guide.getUri(func), args)
break
end
end
if returnNode then
for rnode in returnNode:eachObject() do
-- TODO: narrow type
if rnode.type ~= 'doc.generic.name' then
vm.setNode(source, rnode)
end
end
if returnNode:isOptional() then
vm.getNode(source):addOptional()
end
end
end
end
end
end)
: case 'main'
: call(function (source)
if source.returns then
for _, rtn in ipairs(source.returns) do
if rtn[1] then
vm.setNode(source, vm.compileNode(rtn[1]))
end
end
end
end)
: case 'select'
: call(function (source)
local vararg = source.vararg
if vararg.type == 'call' then
local node = getReturn(vararg.node, source.sindex, vararg.args)
if not node then
return
end
if node:isEmpty() then
node = vm.runOperator('call', vararg.node) or node
end
vm.setNode(source, node)
end
if vararg.type == 'varargs' then
vm.setNode(source, vm.compileNode(vararg))
end
end)
: case 'varargs'
: call(function (source)
if source.node then
vm.setNode(source, vm.compileNode(source.node))
end
end)
: case 'call'
: call(function (source)
local node = getReturn(source.node, 1, source.args)
if not node then
return
end
if node:isEmpty() then
node = vm.runOperator('call', source.node) or node
end
vm.setNode(source, node)
end)
: case 'doc.type'
: call(function (source)
for _, typeUnit in ipairs(source.types) do
vm.setNode(source, vm.compileNode(typeUnit))
end
if source.optional then
vm.getNode(source):addOptional()
end
end)
: case 'doc.type.integer'
: case 'doc.type.string'
: case 'doc.type.boolean'
: case 'doc.type.code'
: call(function (source)
vm.setNode(source, source)
end)
: case 'doc.generic.name'
: call(function (source)
vm.setNode(source, source)
end)
: case 'doc.type.sign'
: call(function (source)
local uri = guide.getUri(source)
vm.setNode(source, source)
if not source.node[1] then
return
end
local global = vm.getGlobal('type', source.node[1])
if not global then
return
end
for _, set in ipairs(global:getSets(uri)) do
if set.type == 'doc.class' then
if set.extends then
for _, ext in ipairs(set.extends) do
if ext.type == 'doc.type.table' then
if ext._generic then
local resolved = ext._generic:resolve(uri, source.signs)
vm.setNode(source, resolved)
end
end
end
end
end
if set.type == 'doc.alias' then
if set.extends._generic then
local resolved = set.extends._generic:resolve(uri, source.signs)
vm.setNode(source, resolved)
end
end
end
end)
: case 'doc.class.name'
: call(function (source)
vm.setNode(source, vm.compileNode(source.parent))
end)
: case 'doc.enum.name'
: call(function (source)
vm.setNode(source, vm.compileNode(source.parent))
end)
: case 'doc.field'
: call(function (source)
if not source.extends then
return
end
local fieldNode = vm.compileNode(source.extends)
if source.optional then
fieldNode:addOptional()
end
vm.setNode(source, fieldNode)
end)
: case 'doc.type.field'
: call(function (source)
if not source.extends then
return
end
local fieldNode = vm.compileNode(source.extends)
if source.optional then
fieldNode:addOptional()
end
vm.setNode(source, fieldNode)
end)
: case 'doc.param'
: call(function (source)
if not source.extends then
return
end
vm.setNode(source, vm.compileNode(source.extends))
end)
: case 'doc.vararg'
: call(function (source)
if not source.vararg then
return
end
vm.setNode(source, vm.compileNode(source.vararg))
end)
: case '...'
: call(function (source)
if not source.bindDocs then
return
end
for _, doc in ipairs(source.bindDocs) do
if doc.type == 'doc.vararg' then
vm.setNode(source, vm.compileNode(doc))
end
if doc.type == 'doc.param' then
vm.setNode(source, vm.compileNode(doc))
end
end
end)
: case 'doc.overload'
: call(function (source)
vm.setNode(source, vm.compileNode(source.overload))
end)
: case 'doc.see.name'
: call(function (source)
local type = vm.getGlobal('type', source[1])
if type then
vm.setNode(source, type)
end
end)
: case 'doc.type.arg'
: call(function (source)
if source.extends then
vm.setNode(source, vm.compileNode(source.extends))
else
vm.setNode(source, vm.declareGlobal('type', 'any'))
end
if source.optional then
vm.getNode(source):addOptional()
end
end)
: case 'unary'
: call(function (source)
if bindAs(source) then
return
end
if not source[1] then
return
end
vm.unarySwich(source.op.type, source)
end)
: case 'binary'
: call(function (source)
if bindAs(source) then
return
end
if not source[1] or not source[2] then
return
end
vm.binarySwitch(source.op.type, source)
end)
---@param source parser.object
local function compileByNode(source)
compilerSwitch(source.type, source)
end
---@param source parser.object
local function compileByGlobal(source)
local global = source._globalNode
if not global then
return
end
---@cast source parser.object
local root = guide.getRoot(source)
local uri = guide.getUri(source)
if not root._globalBase then
root._globalBase = {}
end
local name = global:asKeyName()
if not root._globalBase[name] then
root._globalBase[name] = {
type = 'globalbase',
parent = root,
}
end
local globalNode = vm.getNode(root._globalBase[name])
if globalNode then
vm.setNode(source, globalNode, true)
return
end
---@type vm.node
globalNode = vm.createNode(global)
vm.setNode(root._globalBase[name], globalNode, true)
vm.setNode(source, globalNode, true)
-- TODO:don't mix
--local sets = global.links[uri].sets or {}
--local gets = global.links[uri].gets or {}
--for _, set in ipairs(sets) do
-- vm.setNode(set, globalNode, true)
--end
--for _, get in ipairs(gets) do
-- vm.setNode(get, globalNode, true)
--end
if global.cate == 'variable' then
local hasMarkDoc
for _, set in ipairs(global:getSets(uri)) do
if set.bindDocs and set.parent.type == 'main' then
if bindDocs(set) then
globalNode:merge(vm.compileNode(set))
hasMarkDoc = true
end
if vm.getNode(set) then
globalNode:merge(vm.compileNode(set))
end
end
end
-- Set all globals node first to avoid recursive
for _, set in ipairs(global:getSets(uri)) do
vm.setNode(set, globalNode, true)
end
for _, set in ipairs(global:getSets(uri)) do
if set.value and set.value.type ~= 'nil' and set.parent.type == 'main' then
if not hasMarkDoc or guide.isLiteral(set.value) then
globalNode:merge(vm.compileNode(set.value))
end
end
end
for _, set in ipairs(global:getSets(uri)) do
vm.setNode(set, globalNode, true)
end
end
if global.cate == 'type' then
for _, set in ipairs(global:getSets(uri)) do
if set.type == 'doc.class' then
if set.extends then
for _, ext in ipairs(set.extends) do
if ext.type == 'doc.type.table' then
if not ext._generic then
globalNode:merge(vm.compileNode(ext))
end
end
end
end
end
if set.type == 'doc.alias' then
if not set.extends._generic then
globalNode:merge(vm.compileNode(set.extends))
end
end
end
end
end
---@param source vm.object
---@return vm.node
function vm.compileNode(source)
if not source then
if TEST then
error('Can not compile nil source')
else
log.error('Can not compile nil source')
end
end
local cache = vm.getNode(source)
if cache ~= nil then
return cache
end
if source.type == 'generic' then
vm.setNode(source, source)
local node = vm.getNode(source)
---@cast node -?
return node
end
---@cast source parser.object
vm.setNode(source, vm.createNode(), true)
LOCK[source] = true
compileByGlobal(source)
compileByNode(source)
matchCall(source)
LOCK[source] = nil
local node = vm.getNode(source)
---@cast node -?
return node
end