diff options
38 files changed, 1000 insertions, 3983 deletions
diff --git a/script/core/completion/completion.lua b/script/core/completion/completion.lua index 2c3a354d..569847a4 100644 --- a/script/core/completion/completion.lua +++ b/script/core/completion/completion.lua @@ -19,7 +19,6 @@ local lang = require 'language' local lookBackward = require 'core.look-backward' local guide = require 'parser.guide' local infer = require 'core.infer' -local noder = require 'core.noder' local await = require 'await' local postfix = require 'core.completion.postfix' diff --git a/script/core/definition.lua b/script/core/definition.lua index b08b7706..9270367f 100644 --- a/script/core/definition.lua +++ b/script/core/definition.lua @@ -1,4 +1,3 @@ -local searcher = require 'core.searcher' local workspace = require 'workspace' local files = require 'files' local vm = require 'vm' @@ -132,21 +131,11 @@ return function (uri, offset) end local defs = vm.getAllDefs(source) - local values = {} - for _, src in ipairs(defs) do - local value = searcher.getObjectValue(src) - if value and value ~= src and guide.isLiteral(value) then - values[value] = true - end - end for _, src in ipairs(defs) do if src.dummy then goto CONTINUE end - if values[src] then - goto CONTINUE - end local root = guide.getRoot(src) if not root then goto CONTINUE diff --git a/script/core/diagnostics/deprecated.lua b/script/core/diagnostics/deprecated.lua index 649f4dab..bd2a95b0 100644 --- a/script/core/diagnostics/deprecated.lua +++ b/script/core/diagnostics/deprecated.lua @@ -5,7 +5,6 @@ local guide = require 'parser.guide' local config = require 'config' local define = require 'proto.define' local await = require 'await' -local noder = require 'core.noder' local types = {'getglobal', 'getfield', 'getindex', 'getmethod'} ---@async diff --git a/script/core/diagnostics/duplicate-doc-field.lua b/script/core/diagnostics/duplicate-doc-field.lua index 71610ef5..0ad2045e 100644 --- a/script/core/diagnostics/duplicate-doc-field.lua +++ b/script/core/diagnostics/duplicate-doc-field.lua @@ -1,6 +1,5 @@ local files = require 'files' local lang = require 'language' -local noder = require 'core.noder' return function (uri, callback) local state = files.getState(uri) diff --git a/script/core/diagnostics/duplicate-index.lua b/script/core/diagnostics/duplicate-index.lua index d1141901..bf46996e 100644 --- a/script/core/diagnostics/duplicate-index.lua +++ b/script/core/diagnostics/duplicate-index.lua @@ -2,8 +2,6 @@ local files = require 'files' local guide = require 'parser.guide' local lang = require 'language' local define = require 'proto.define' -local vm = require 'vm' -local noder = require 'core.noder' return function (uri, callback) local ast = files.getState(uri) diff --git a/script/core/diagnostics/undefined-field.lua b/script/core/diagnostics/undefined-field.lua index 7d309096..ea15dcda 100644 --- a/script/core/diagnostics/undefined-field.lua +++ b/script/core/diagnostics/undefined-field.lua @@ -2,7 +2,6 @@ local files = require 'files' local vm = require 'vm' local lang = require 'language' local guide = require 'parser.guide' -local noder = require 'core.noder' local await = require 'await' local skipCheckClass = { diff --git a/script/core/diagnostics/undefined-global.lua b/script/core/diagnostics/undefined-global.lua index b570ca65..cf31ec4e 100644 --- a/script/core/diagnostics/undefined-global.lua +++ b/script/core/diagnostics/undefined-global.lua @@ -3,7 +3,6 @@ local vm = require 'vm' local lang = require 'language' local config = require 'config' local guide = require 'parser.guide' -local noder = require 'core.noder' local collector = require 'core.collector' 'searcher' local await = require 'await' diff --git a/script/core/generic.lua b/script/core/generic.lua deleted file mode 100644 index f260dc0b..00000000 --- a/script/core/generic.lua +++ /dev/null @@ -1,283 +0,0 @@ -local guide = require 'parser.guide' -local noder = require "core.noder" - ----@class generic.value: parser.guide.object ----@field type string ----@field closure generic.closure ----@field proto parser.guide.object ----@field parent parser.guide.object - ----@class generic.closure: parser.guide.object ----@field type string ----@field proto parser.guide.object ----@field upvalues table<string, generic.value[]> ----@field params generic.value[] ----@field returns generic.value[] - -local m = {} - ----@param closure generic.closure ----@param proto parser.guide.object -local function instantValue(closure, proto) - ---@type generic.value - local value = { - type = 'generic.value', - closure = closure, - proto = proto, - parent = proto.parent, - } - closure.values[#closure.values+1] = value - return value -end - ----递归实例化对象 ----@param proto parser.guide.object ----@return generic.value -local function createValue(closure, proto, callback, road) - road = road or {} - if proto.type == 'doc.type' then - local types = {} - local hasGeneric - for i, tp in ipairs(proto.types) do - local genericValue = createValue(closure, tp, callback, road) - if genericValue then - hasGeneric = true - types[i] = genericValue - else - types[i] = tp - end - end - if not hasGeneric then - return nil - end - local value = instantValue(closure, proto) - value.types = types - noder.compileNode(noder.getNoders(proto), value) - return value - end - if proto.type == 'doc.type.name' then - if not proto.typeGeneric then - return nil - end - local key = proto[1] - local value = instantValue(closure, proto) - if callback then - callback(road, key, proto) - end - noder.compileNode(noder.getNoders(proto), value) - return value - end - if proto.type == 'doc.type.function' then - local hasGeneric - local args = {} - local returns = {} - for i, arg in ipairs(proto.args) do - local value = createValue(closure, arg, callback, road) - if value then - hasGeneric = true - end - args[i] = value or arg - end - for i, rtn in ipairs(proto.returns) do - local value = createValue(closure, rtn, callback, road) - if value then - hasGeneric = true - end - returns[i] = value or rtn - end - if not hasGeneric then - return nil - end - local value = instantValue(closure, proto) - value.args = args - value.returns = returns - value.isGeneric = true - noder.pushSource(noder.getNoders(proto), value) - return value - end - if proto.type == 'doc.type.array' then - road[#road+1] = noder.WEAK_ANY_FIELD - local node = createValue(closure, proto.node, callback, road) - road[#road] = nil - if not node then - return nil - end - local value = instantValue(closure, proto) - value.node = node - noder.compileNode(noder.getNoders(proto), value) - return value - end - if proto.type == 'doc.type.table' then - road[#road+1] = noder.WEAK_TABLE_KEY - local tkey = createValue(closure, proto.tkey, callback, road) - road[#road] = nil - - road[#road+1] = noder.WEAK_ANY_FIELD - local tvalue = createValue(closure, proto.tvalue, callback, road) - road[#road] = nil - - if not tkey and not tvalue then - return nil - end - local value = instantValue(closure, proto) - value.tkey = tkey or proto.tkey - value.tvalue = tvalue or proto.tvalue - noder.compileNode(noder.getNoders(proto), value) - return value - end - if proto.type == 'doc.type.ltable' then - local fields = {} - for i, field in ipairs(proto.fields) do - fields[i] = createValue(closure, field, callback, road) or field - end - if #fields == 0 then - return nil - end - local value = instantValue(closure, proto) - value.fields = fields - noder.compileNode(noder.getNoders(proto), value) - return value - end - if proto.type == 'doc.type.field' then - local name = proto.name[1] - if type(name) == 'string' then - road[#road+1] = ('%s%s'):format( - noder.STRING_FIELD, - name - ) - else - road[#road+1] = ('%s%s'):format( - noder.SPLIT_CHAR, - name - ) - end - local typeUnit = createValue(closure, proto.extends, callback, road) - road[#road] = nil - if not typeUnit then - return nil - end - local value = instantValue(closure, proto) - value.name = proto.name - value.extends = typeUnit - noder.compileNode(noder.getNoders(proto), value) - return value - end -end - -local function buildValue(road, key, proto, param, upvalues) - local paramID - if proto.literal then - local str = param.type == 'string' and param[1] - if not str then - return - end - paramID = 'dn:' .. str - else - paramID = noder.getID(param) - end - if not paramID then - return - end - local myUri = guide.getUri(param) - local myHead = noder.URI_CHAR .. myUri .. noder.URI_CHAR - paramID = myHead .. paramID - if not upvalues[key] then - upvalues[key] = {} - end - upvalues[key][#upvalues[key]+1] = paramID .. table.concat(road) -end - --- 为所有的 param 与 return 创建副本 ----@param closure generic.closure -local function buildValues(closure) - local protoFunction = closure.proto - local upvalues = closure.upvalues - local params = closure.call.args - local args = protoFunction.args - local paramMap = {} - if params then - for i, param in ipairs(params) do - local arg = args and args[i] - if arg then - if arg.type == 'local' then - paramMap[arg[1]] = param - elseif arg.type == 'doc.type.arg' then - paramMap[arg.name[1]] = param - end - end - end - end - - if protoFunction.type == 'function' then - for _, doc in ipairs(protoFunction.bindDocs) do - if doc.type == 'doc.param' then - local name = doc.param[1] - local extends = doc.extends - if name and extends then - local param = paramMap[name] - closure.params[name] = param and createValue(closure, extends, function (road, key, proto) - buildValue(road, key, proto, param, upvalues) - end) or extends - end - end - end - for _, doc in ipairs(protoFunction.bindDocs) do - if doc.type == 'doc.return' then - for _, rtn in ipairs(doc.returns) do - closure.returns[rtn.returnIndex] = createValue(closure, rtn) or rtn - end - end - end - end - if protoFunction.type == 'doc.type.function' then - for index, arg in ipairs(protoFunction.args) do - local name = arg.name[1] - local extends = arg.extends - local param = paramMap[name] - - closure.params[name] = param and createValue(closure, extends, function (road, key, proto) - buildValue(road, key, proto, param, upvalues) - end) or extends - end - for index, rtn in ipairs(protoFunction.returns) do - closure.returns[index] = createValue(closure, rtn) or rtn - end - end -end - ----创建一个闭包 ----@param proto parser.guide.object|generic.value # 原型函数|泛型值 ----@return generic.closure -function m.createClosure(proto, call) - local protoFunction, parentClosure - if proto.type == 'function' then - protoFunction = proto - elseif proto.type == 'doc.type.function' then - protoFunction = proto - elseif proto.type == 'generic.value' then - protoFunction = proto.proto - parentClosure = proto.closure - end - ---@type generic.closure - local closure = { - type = 'generic.closure', - parent = protoFunction.parent, - proto = protoFunction, - call = call, - upvalues = parentClosure and parentClosure.upvalues or {}, - params = {}, - returns = {}, - values = {}, - } - buildValues(closure) - - if #closure.returns == 0 then - return nil - end - - noder.compileNode(noder.getNoders(proto), closure) - - return closure -end - -return m diff --git a/script/core/hover/description.lua b/script/core/hover/description.lua index d694660b..a5b30cf3 100644 --- a/script/core/hover/description.lua +++ b/script/core/hover/description.lua @@ -6,7 +6,6 @@ local config = require 'config' local lang = require 'language' local util = require 'utility' local guide = require 'parser.guide' -local noder = require 'core.noder' local rpath = require 'workspace.require-path' local function collectRequire(mode, literal, uri) diff --git a/script/core/infer.lua b/script/core/infer.lua index 88028a6c..35a054ca 100644 --- a/script/core/infer.lua +++ b/script/core/infer.lua @@ -1,6 +1,4 @@ -local searcher = require 'core.searcher' local config = require 'config' -local noder = require 'core.noder' local util = require 'utility' local vm = require "vm.vm" local guide = require "parser.guide" @@ -398,7 +396,7 @@ function m.viewDocFunction(doc) end ---显示对象的推断类型 ----@param source parser.guide.object +---@param source parser.object ---@param mark table ---@return string local function searchInfer(source, infers, mark) @@ -469,7 +467,7 @@ local function getCachedInfers(source, field) end ---搜索对象的推断类型 ----@param source parser.guide.object +---@param source parser.object ---@param field? string ---@param mark? table ---@return string[] @@ -513,7 +511,7 @@ function m.searchInfers(source, field, mark) end ---搜索对象的字面量值 ----@param source parser.guide.object +---@param source parser.object ---@param field? string ---@param mark? table ---@return table @@ -534,7 +532,7 @@ function m.searchLiterals(source, field, mark) end ---搜索并显示推断值 ----@param source parser.guide.object +---@param source parser.object ---@param field? string ---@return string function m.searchAndViewLiterals(source, field, mark) @@ -550,7 +548,7 @@ function m.searchAndViewLiterals(source, field, mark) end ---判断对象的推断值是否是 true ----@param source parser.guide.object +---@param source parser.object ---@param mark? table function m.isTrue(source, mark) if not source then @@ -596,7 +594,7 @@ function m.hasType(source, tp, mark) end ---搜索并显示推断类型 ----@param source parser.guide.object +---@param source parser.object ---@param field? string ---@return string function m.searchAndViewInfers(source, field, mark) @@ -613,7 +611,7 @@ function m.searchAndViewInfers(source, field, mark) end ---搜索并显示推断的class ----@param source parser.guide.object +---@param source parser.object ---@return string? function m.getClass(source) if not source then diff --git a/script/core/noder.lua b/script/core/noder.lua deleted file mode 100644 index fcc6b6f4..00000000 --- a/script/core/noder.lua +++ /dev/null @@ -1,1914 +0,0 @@ -local util = require 'utility' -local guide = require 'parser.guide' -local collector = require 'core.collector' 'searcher' -local files = require 'files' -local config = require 'config' - -local tostring = tostring -local error = error -local ipairs = ipairs -local type = type -local next = next -local log = log -local ssub = string.sub -local sformat = string.format -local sgsub = string.gsub -local smatch = string.match -local sfind = string.find - -_ENV = nil - -local SPLIT_CHAR = '\x1F' -local LAST_REGEX = SPLIT_CHAR .. '[^' .. SPLIT_CHAR .. ']*$' -local FIRST_REGEX = '^[^' .. SPLIT_CHAR .. ']*' -local HEAD_REGEX = '^' .. SPLIT_CHAR .. '?[^' .. SPLIT_CHAR .. ']*' -local STRING_CHAR = '.' -local ANY_FIELD_CHAR = '*' -local INDEX_CHAR = '[' -local RETURN_INDEX = SPLIT_CHAR .. '#' -local PARAM_INDEX = SPLIT_CHAR .. '&' -local EVENT_ENUM = SPLIT_CHAR .. '>' -local TABLE_KEY = SPLIT_CHAR .. '<' -local WEAK_TABLE_KEY = SPLIT_CHAR .. '<<' -local STRING_FIELD = SPLIT_CHAR .. STRING_CHAR -local INDEX_FIELD = SPLIT_CHAR .. INDEX_CHAR -local ANY_FIELD = SPLIT_CHAR .. ANY_FIELD_CHAR -local WEAK_ANY_FIELD = SPLIT_CHAR .. ANY_FIELD_CHAR .. ANY_FIELD_CHAR -local URI_CHAR = '@' -local URI_REGEX = URI_CHAR .. '([^' .. URI_CHAR .. ']*)' .. URI_CHAR .. '(.*)' - -local INFO_DEEP = { - deep = true, -} -local INFO_REJECT_SET = { - reject = 'set', -} -local INFO_DEEP_AND_REJECT_SET = { - reject = 'set', - deep = true, -} -local INFO_META_INDEX = { - filter = function (id, field) - if field then - return true - end - return ssub(id, 1, 2) ~= 'f:' - end, - filterValid = function (id, field) - return not field - end -} -local INFO_CLASS_TO_EXNTENDS = { - filter = function (_, field, mode) - return field ~= nil - or mode == 'field' - or mode == 'allfield' - end, - filterValid = function (_, field) - return not field - end, - reject = 'set', -} -local INFO_DEEP_AND_DONT_CROSS = { - deep = true, - dontCross = true, -} - ----@alias node.id string ----@alias node.filter fun(id: string, field?: string):boolean - ----@class noders --- 使用该ID的单元 ----@field source table<node.id, parser.guide.object> --- 使用该ID的单元 ----@field sources table<node.id, parser.guide.object[]> --- 前进的关联ID ----@field forward table<node.id, node.id> --- 第一个前进关联的info ----@field finfo? table<node.id, node.info> --- 前进的关联ID与info ----@field forwards table<node.id, node.id[]|table<node.id, node.info>> --- 后退的关联ID ----@field backward table<node.id, node.id> --- 第一个后退关联的info ----@field binfo? table<node.id, node.info> --- 后退的关联ID与info ----@field backwards table<node.id, node.id[]|table<node.id, node.info>> --- 第一个继承 ----@field extend table<node.id, node.id> --- 其他继承 ----@field extends table<node.id, node.id[]> --- 函数调用参数信息(用于泛型) ----@field call table<node.id, parser.guide.object> ----@field require table<node.id, string> ----@field skip table<node.id, boolean> - ----@class node.info ----@field reject? string ----@field deep? boolean ----@field filter? node.filter ----@field filterValid? node.filter ----@field dontCross? boolean - ----如果对象是 arg self, 则认为 id 是 method 的 node ----@param source parser.guide.object ----@return nil -local function getMethodNode(source) - if source.type ~= 'local' or source[1] ~= 'self' then - return nil - end - if source._mnode ~= nil then - return source._mnode or nil - end - source._mnode = false - local func = guide.getParentFunction(source) - if not func then - return - end - if func.isGeneric then - return - end - if source.parent.type ~= 'funcargs' then - return - end - local setmethod = func.parent - if setmethod and ( setmethod.type == 'setmethod' - or setmethod.type == 'setfield' - or setmethod.type == 'setindex') then - source._mnode = setmethod.node - return setmethod.node - end -end - -local function getFieldEventName(field) - if field._eventName then - return field._eventName or nil - end - field._eventName = false - local fieldType = field.extends - if not fieldType then - return nil - end - local docFunc = fieldType.types[1] - if not docFunc or docFunc.type ~= 'doc.type.function' then - return nil - end - local firstArg = docFunc.args and docFunc.args[1] - if not firstArg then - return nil - end - local secondArg - if firstArg.name[1] == 'self' then - firstArg = docFunc.args[2] - if not firstArg then - return nil - end - secondArg = docFunc.args[3] - else - secondArg = docFunc.args[2] - end - if not secondArg then - return - end - local firstType = firstArg.extends - if not firstType then - return nil - end - local firstEnum = firstType.types[1] - if not firstEnum then - return nil - end - local secondType = secondArg.extends - if not secondType then - return nil - end - local secondTypeUnit = secondType.types[1] - if not secondTypeUnit or secondTypeUnit.type ~= 'doc.type.function' then - return nil - end - local enmuStr = firstEnum[1] - if type(enmuStr) ~= 'string' then - return nil - end - local eventName = enmuStr:match [[^['"](.+)['"]$]] - field._eventName = eventName - return eventName -end - -local getKey, getID -local getKeyMap = util.switch() - : case 'local' - : call(function (source) - if source.parent.type == 'funcargs' then - return 'p:' .. source.start, nil - end - return 'l:' .. source.start, nil - end) - : case 'setlocal' - : case 'getlocal' - : call(function (source) - return getKey(source.node) - end) - : case 'setglobal' - : case 'getglobal' - : call(function (source) - local node = source.node - if node.tag == '_ENV' then - return STRING_CHAR .. (source[1] or ''), nil - else - return STRING_CHAR .. (source[1] or ''), node - end - end) - : case 'getfield' - : case 'setfield' - : call(function (source) - return STRING_CHAR .. (source.field and source.field[1] or ''), source.node - end) - : case 'tablefield' - : call(function (source) - local t = source.parent - local parent = t.parent - local node - if parent.value == t then - node = parent - else - node = t - end - return STRING_CHAR .. (source.field and source.field[1] or ''), node - end) - : case 'getmethod' - : case 'setmethod' - : call(function (source) - return STRING_CHAR .. (source.method and source.method[1] or ''), source.node - end) - : case 'setindex' - : case 'getindex' - : call(function (source) - local index = source.index - if not index then - return INDEX_CHAR, source.node - end - if index.type == 'string' then - return STRING_CHAR .. (index[1] or ''), source.node - elseif index.type == 'boolean' - or index.type == 'integer' - or index.type == 'number' then - return tostring(index[1] or ''), source.node - else - return INDEX_CHAR, source.node - end - end) - : case 'tableindex' - : call(function (source) - local t = source.parent - local parent = t.parent - local node - if parent.value == t then - node = parent - else - node = t - end - local index = source.index - if not index then - return ANY_FIELD_CHAR, node - end - if index.type == 'string' then - return STRING_CHAR .. (index[1] or ''), node - elseif index.type == 'boolean' - or index.type == 'integer' - or index.type == 'number' then - return tostring(index[1] or ''), node - elseif index.type ~= 'function' - and index.type ~= 'table' then - return ANY_FIELD_CHAR, node - end - end) - : case 'tableexp' - : call(function (source) - local t = source.parent - local parent = t.parent - local node - if parent.value == t then - node = parent - else - node = t - end - return tostring(source.tindex), node - end) - : case 'table' - : call(function (source) - return 't:' .. source.start, nil - end) - : case 'label' - : call(function (source) - return 'l:' .. source.start, nil - end) - : case 'goto' - : call(function (source) - if source.node then - return 'l:' .. source.node.start, nil - end - return nil, nil - end) - : case 'function' - : call(function (source) - return 'f:' .. source.start, nil - end) - : case 'string' - : call(function (source) - return 'str:', nil - end) - : case 'integer' - : call(function (source) - return 'int:', nil - end) - : case 'number' - : call(function (source) - return 'num:', nil - end) - : case 'boolean' - : call(function (source) - return 'bool:', nil - end) - : case 'nil' - : call(function (source) - return 'nil:', nil - end) - : case '...' - : call(function (source) - return 'va:' .. source.start, nil - end) - : case 'varargs' - : call(function (source) - if source.node then - return 'va:' .. source.node.start, nil - end - end) - : case 'select' - : call(function (source) - return sformat('s:%d%s%d', source.start, RETURN_INDEX, source.sindex) - end) - : case 'call' - : call(function (source) - local node = source.node - if node.special == 'rawget' - or node.special == 'rawset' then - if not source.args then - return nil, nil - end - local tbl, key = source.args[1], source.args[2] - if not tbl or not key then - return nil, nil - end - if key.type == 'string' then - return STRING_CHAR .. (key[1] or ''), tbl - else - return '', tbl - end - end - return 'c:' .. source.finish, nil - end) - : case 'doc.class.name' - : case 'doc.alias.name' - : case 'doc.extends.name' - : call(function (source) - local name = source[1] - return 'dn:' .. name, nil - end) - : case 'doc.type.name' - : call(function (source) - local name = source[1] - if source.typeGeneric then - local first = source.typeGeneric[name][1] - if first then - return 'dg:' .. first.start, nil - end - else - return 'dn:' .. name, nil - end - end) - : case 'doc.see.name' - : call(function (source) - local name = source[1] - return 'dsn:' .. name, nil - end) - : case 'doc.class' - : call(function (source) - return 'dc:' .. source.start - end) - : case 'doc.type' - : call(function (source) - return 'dt:' .. source.start - end) - : case 'doc.param' - : call(function (source) - return 'dp:' .. source.start - end) - : case 'doc.vararg' - : call(function (source) - return 'dv:' .. source.start - end) - : case 'doc.field.name' - : call(function (source) - return 'dfn:' .. source.start - end) - : case 'doc.type.enum' - : case 'doc.resume' - : call(function (source) - return 'de:' .. source.start - end) - : case 'doc.type.table' - : call(function (source) - return 'dtable:' .. source.start - end) - : case 'doc.type.ltable' - : call(function (source) - return 'dltable:' .. source.start - end) - : case 'doc.type.field' - : call(function (source) - return 'dfield:' .. source.start - end) - : case 'doc.type.array' - : call(function (source) - return 'darray:' .. source.finish - end) - : case 'doc.type.function' - : call(function (source) - return 'dfun:' .. source.start, nil - end) - : case 'doc.see.field' - : call(function (source) - return STRING_CHAR .. (source[1]), source.parent.name - end) - : case 'generic.closure' - : call(function (source) - return 'gc:' .. source.call.start, nil - end) - : case 'generic.value' - : call(function (source) - local tail = '' - if guide.getUri(source.closure.call) ~= guide.getUri(source.proto) then - tail = URI_CHAR .. guide.getUri(source.closure.call) - end - return sformat('gv:%s|%s%s' - , source.closure.call.start - , getKey(source.proto) - , tail - ) - end) - : getMap() - ----获取语法树单元的key ----@param source parser.guide.object ----@return string? key ----@return parser.guide.object? node -function getKey(source) - local f = getKeyMap[source.type] - if f then - return f(source) - end - return nil -end - -local function getLocalValueID(source) - if source.type ~= 'local' then - return nil - end - local value = source.value - if not value then - return nil - end - local id = getID(value) - if not id then - return nil - end - local ct = id:sub(1, 2) - if ct == 'g:' - or ct == 'p:' - or ct == 'l:' then - return id - end - return nil -end - -local function getNodeKey(source) - if source.type == 'getlocal' - or source.type == 'setlocal' then - source = source.node - end - local methodNode = getMethodNode(source) - if methodNode then - return getNodeKey(methodNode) - end - if config.get(guide.getUri(source), 'Lua.IntelliSense.traceFieldInject') then - local localValueID = getLocalValueID(source) - if localValueID then - return localValueID - end - end - local key, node = getKey(source) - if key and guide.isGlobal(source) then - return 'g:' .. key, nil - end - return key, node -end - ----获取语法树单元的字符串ID ----@param source parser.guide.object ----@return string? id -function getID(source) - if not source then - return nil - end - if source._id ~= nil then - return source._id or nil - end - if source.type == 'field' - or source.type == 'method' then - source._id = false - return nil - end - local current = source - while current.type == 'paren' do - current = current.exp - if not current then - source._id = false - return nil - end - end - local id, node = getNodeKey(current) - if not id then - source._id = false - return nil - end - if node then - local pid = getID(node) - if not pid then - source._id = false - return nil - end - id = pid .. SPLIT_CHAR .. id - end - source._id = id - return id -end - ----添加关联的前进ID ----@param noders noders ----@param id node.id ----@param forwardID node.id ----@param info? node.info -local function pushForward(noders, id, forwardID, info) - if not id - or not forwardID - or forwardID == '' - or id == forwardID then - return - end - if not noders.forward[id] then - noders.forward[id] = forwardID - noders.finfo[id] = info - return - end - if noders.forward[id] == forwardID then - return - end - local forwards = noders.forwards[id] - if not forwards then - forwards = {} - noders.forwards[id] = forwards - end - if forwards[forwardID] ~= nil then - return - end - forwards[forwardID] = info or false - forwards[#forwards+1] = forwardID -end - ----添加关联的后退ID ----@param noders noders ----@param id node.id ----@param backwardID node.id ----@param info? node.info -local function pushBackward(noders, id, backwardID, info) - if not id - or not backwardID - or backwardID == '' - or id == backwardID then - return - end - if not noders.backward[id] then - noders.backward[id] = backwardID - noders.binfo[id] = info - return - end - if noders.backward[id] == backwardID then - return - end - local backwards = noders.backwards[id] - if not backwards then - backwards = {} - noders.backwards[id] = backwards - end - if backwards[backwardID] ~= nil then - return - end - backwards[backwardID] = info or false - backwards[#backwards+1] = backwardID -end - ----添加继承的关联ID ----@param noders noders ----@param id node.id ----@param extendID node.id -local function pushExtend(noders, id, extendID) - if not id - or not extendID - or extendID == '' - or id == extendID then - return - end - if not noders.extend[id] then - noders.extend[id] = extendID - return - end - if noders.extend[id] == extendID then - return - end - local extends = noders.extends[id] - if not extends then - extends = {} - noders.extends[id] = extends - end - if extends[extendID] ~= nil then - return - end - extends[extendID] = false - extends[#extends+1] = extendID -end - ----@class noder -local m = {} - -m.SPLIT_CHAR = SPLIT_CHAR -m.STRING_CHAR = STRING_CHAR -m.STRING_FIELD = STRING_FIELD -m.RETURN_INDEX = RETURN_INDEX -m.PARAM_INDEX = PARAM_INDEX -m.TABLE_KEY = TABLE_KEY -m.ANY_FIELD = ANY_FIELD -m.URI_CHAR = URI_CHAR -m.INDEX_FIELD = INDEX_FIELD -m.WEAK_TABLE_KEY = WEAK_TABLE_KEY -m.WEAK_ANY_FIELD = WEAK_ANY_FIELD - ---- 寻找doc的主体 ----@param obj parser.guide.object ----@return parser.guide.object -local function getDocStateWithoutCrossFunction(obj) - for _ = 1, 1000 do - local parent = obj.parent - if not parent then - return obj - end - if parent.type == 'doc' then - return obj - end - if parent.type == 'doc.type.function' then - return nil - end - obj = parent - end - error('guide.getDocState overstack') -end - -local dontPushSourceMap = util.arrayToHash { - 'str:', 'nil:', 'num:', 'int:', 'bool:' -} - ----添加关联单元 ----@param noders noders ----@param source parser.guide.object -function m.pushSource(noders, source, id) - id = id or getID(source) - if not id then - return - end - if dontPushSourceMap[id] then - return - end - if not noders.source[id] then - noders.source[id] = source - return - end - local sources = noders.sources[id] - if not sources then - sources = {} - noders.sources[id] = sources - end - sources[#sources+1] = source -end - -local DUMMY_FUNCTION = function () end - ----遍历关联单元 ----@param noders noders ----@param id node.id ----@return fun():parser.guide.object -function m.eachSource(noders, id) - local source = noders.source[id] - if not source then - return DUMMY_FUNCTION - end - local index - local sources = noders.sources[id] - return function () - if not index then - index = 0 - return source - end - if not sources then - return nil - end - index = index + 1 - return sources[index] - end -end - ----遍历forward ----@param noders noders ----@param id node.id ----@return fun():string, node.info -function m.eachForward(noders, id) - local forward = noders.forward[id] - if not forward then - return DUMMY_FUNCTION - end - local index - local forwards = noders.forwards[id] - return function () - if not index then - index = 0 - return forward, noders.finfo[id] - end - if not forwards then - return nil - end - index = index + 1 - local id = forwards[index] - local tag = forwards[id] - return id, tag - end -end - ----遍历backward ----@param noders noders ----@param id node.id ----@return fun():string, node.info -function m.eachBackward(noders, id) - local backward = noders.backward[id] - if not backward then - return DUMMY_FUNCTION - end - local index - local backwards = noders.backwards[id] - return function () - if not index then - index = 0 - return backward, noders.binfo[id] - end - if not backwards then - return nil - end - index = index + 1 - local id = backwards[index] - local tag = backwards[id] - return id, tag - end -end - ----遍历extend ----@param noders noders ----@param id node.id ----@return fun():string, node.info -function m.eachExtend(noders, id) - local extend = noders.extend[id] - if not extend then - return DUMMY_FUNCTION - end - local index - local extends = noders.extends[id] - return function () - if not index then - index = 0 - return extend - end - if not extends then - return nil - end - index = index + 1 - local id = extends[index] - return id - end -end - -local function bindValue(noders, source, id) - local value = source.value - if not value then - return - end - local valueID = getID(value) - if not valueID then - return - end - - local bindDocs = source.bindDocs - if source.type == 'getlocal' - or source.type == 'setlocal' then - if not config.get(guide.getUri(source), 'Lua.IntelliSense.traceLocalSet') then - return - end - bindDocs = source.node.bindDocs - end - if bindDocs and value.type ~= 'table' then - for _, doc in ipairs(bindDocs) do - if doc.type == 'doc.class' - or doc.type == 'doc.type' then - return - end - end - end - -- x = y : x -> y - pushForward(noders, id, valueID, INFO_REJECT_SET) - if not config.get(guide.getUri(source), 'Lua.IntelliSense.traceBeSetted') - and source.type ~= 'local' then - return - end - -- 参数/call禁止反向查找赋值 - local valueType = smatch(valueID, '^(.-:).') - if not valueType then - return - end - pushBackward(noders, valueID, id, INFO_DEEP_AND_REJECT_SET) -end - -local function compileCallParam(noders, call, sourceID) - if not sourceID then - return - end - if not call.args then - return - end - local node = call.node - local fixIndex = 0 - if call.node.special == 'pcall' then - fixIndex = 1 - node = call.args[1] - elseif call.node.special == 'xpcall' then - fixIndex = 2 - node = call.args[1] - end - local nodeID = getID(node) - if not nodeID then - return - end - local methodIndex = 0 - if node.type == 'getmethod' then - fixIndex = fixIndex + 1 - methodIndex = 1 - end - local eventNodeID - for firstIndex, callArg in ipairs(call.args) do - firstIndex = firstIndex - fixIndex - if firstIndex == 1 and callArg.type == 'string' then - if callArg[1] then - eventNodeID = sformat('%s%s%s' - , nodeID - , EVENT_ENUM - , callArg[1] - ) - end - end - if firstIndex > 0 and callArg.type == 'function' then - if callArg.args then - for secondIndex, funcParam in ipairs(callArg.args) do - local paramID = sformat('%s%s%s%s%s' - , nodeID - , PARAM_INDEX - , firstIndex + methodIndex - , PARAM_INDEX - , secondIndex - ) - pushForward(noders, getID(funcParam), paramID) - if eventNodeID then - local eventParamID = sformat('%s%s%s%s%s' - , eventNodeID - , PARAM_INDEX - , firstIndex + methodIndex - , PARAM_INDEX - , secondIndex - ) - pushForward(noders, getID(funcParam), eventParamID) - end - end - end - end - if callArg.type == 'table' then - local paramID = sformat('%s%s%s' - , nodeID - , PARAM_INDEX - , firstIndex + methodIndex - ) - pushForward(noders, getID(callArg), paramID) - end - end -end - -local function compileCallReturn(noders, call, sourceID, returnIndex) - if not sourceID then - return - end - local node = call.node - local nodeID = getID(node) - if not nodeID then - return - end - local callID = getID(call) - if not callID then - return - end - -- 将setmetatable映射到 param1 以及 param2.__index 上 - if node.special == 'setmetatable' then - local tblID = getID(call.args and call.args[1]) - local metaID = getID(call.args and call.args[2]) - local indexID - if metaID then - indexID = sformat('%s%s%s' - , metaID - , STRING_FIELD - , '__index' - ) - end - pushForward(noders, sourceID, tblID) - pushForward(noders, sourceID, indexID, INFO_META_INDEX) - pushBackward(noders, tblID, sourceID) - --pushBackward(noders, indexID, callID) - return - end - if node.special == 'require' then - local arg1 = call.args and call.args[1] - if arg1 and arg1.type == 'string' then - noders.require[sourceID] = arg1[1] - end - pushBackward(noders, callID, sourceID, INFO_DEEP) - return - end - if node.special == 'pcall' - or node.special == 'xpcall' then - local index = returnIndex - 1 - if index <= 0 then - return - end - local funcID = call.args and getID(call.args[1]) - if not funcID then - return - end - local pfuncXID = sformat('%s%s%s' - , funcID - , RETURN_INDEX - , index - ) - pushForward(noders, sourceID, pfuncXID) - pushBackward(noders, pfuncXID, sourceID, INFO_DEEP) - return - end - local funcXID = sformat('%s%s%s' - , nodeID - , RETURN_INDEX - , returnIndex - ) - noders.call[sourceID] = call - pushForward(noders, sourceID, funcXID) - pushBackward(noders, funcXID, sourceID, INFO_DEEP) -end - -local specialMap = util.arrayToHash { - 'require', 'dofile', 'loadfile', - 'rawset', 'rawget', 'setmetatable', -} - -local compileNodeMap -compileNodeMap = util.switch() - : case 'string' - : call(function (noders, id, source) - pushForward(noders, id, 'str:') - end) - : case 'boolean' - : call(function (noders, id, source) - pushForward(noders, id, 'dn:boolean') - end) - : case 'number' - : call(function (noders, id, source) - pushForward(noders, id, 'dn:number') - end) - : case 'integer' - : call(function (noders, id, source) - pushForward(noders, id, 'dn:integer') - end) - : case 'nil' - : call(function (noders, id, source) - pushForward(noders, id, 'dn:nil') - end) - : case 'doc.type' - : call(function (noders, id, source) - if source.bindSources then - for _, src in ipairs(source.bindSources) do - if src.parent.type ~= 'funcargs' - and not src.dummy then - pushForward(noders, getID(src), id) - end - end - end - for _, typeUnit in ipairs(source.types) do - local unitID = getID(typeUnit) - pushForward(noders, id, unitID) - if source.bindSources then - for _, src in ipairs(source.bindSources) do - if src.parent.type ~= 'funcargs' - and not src.dummy then - pushBackward(noders, unitID, getID(src)) - end - end - end - end - end) - : case 'doc.type.table' - : call(function (noders, id, source) - if source.node then - pushForward(noders, id, getID(source.node), INFO_CLASS_TO_EXNTENDS) - end - if source.tkey then - local keyID = id .. TABLE_KEY - pushForward(noders, keyID, getID(source.tkey)) - end - if source.tvalue then - local valueID = id .. ANY_FIELD - pushForward(noders, valueID, getID(source.tvalue)) - end - end) - : case 'doc.type.ltable' - : call(function (noders, id, source) - local firstField = source.fields[1] - if not firstField then - return - end - local keyID = id .. WEAK_TABLE_KEY - local valueID = id .. WEAK_ANY_FIELD - pushForward(noders, keyID, 'dn:string') - pushForward(noders, valueID, getID(firstField.extends)) - for _, field in ipairs(source.fields) do - local fname = field.name[1] - local extendsID - if type(fname) == 'string' then - extendsID = sformat('%s%s%s' - , id - , STRING_FIELD - , fname - ) - else - extendsID = sformat('%s%s%s' - , id - , SPLIT_CHAR - , fname - ) - end - pushForward(noders, extendsID, getID(field)) - pushForward(noders, extendsID, getID(field.extends)) - end - end) - : case 'doc.type.array' - : call(function (noders, id, source) - if source.node then - local nodeID = id .. ANY_FIELD - pushForward(noders, nodeID, getID(source.node)) - end - local keyID = id .. TABLE_KEY - pushForward(noders, keyID, 'dn:integer') - end) - : case 'doc.alias' - : call(function (noders, id, source) - pushForward(noders, getID(source.alias), getID(source.extends)) - end) - : case 'doc.class' - : call(function (noders, id, source) - pushForward(noders, id, getID(source.class)) - pushForward(noders, getID(source.class), id) - if source.extends then - for _, ext in ipairs(source.extends) do - pushExtend(noders, id, getID(ext)) - end - end - if source.bindSources then - for _, src in ipairs(source.bindSources) do - if src.parent.type ~= 'funcargs' - and src.type ~= 'setmethod' - and not src.dummy then - pushForward(noders, getID(src), id) - pushForward(noders, id, getID(src)) - end - end - end - for _, field in ipairs(source.fields) do - local key = field.field[1] - if key then - local keyID - if type(key) == 'string' then - keyID = sformat('%s%s%s' - , id - , STRING_FIELD - , key - ) - local eventName = getFieldEventName(field) - if eventName then - keyID = sformat('%s%s%s' - , keyID - , EVENT_ENUM - , eventName - ) - end - else - keyID = sformat('%s%s%s' - , id - , SPLIT_CHAR - , key - ) - end - pushForward(noders, keyID, getID(field.field)) - pushForward(noders, getID(field.field), keyID) - pushForward(noders, keyID, getID(field.extends)) - end - end - end) - : case 'doc.module' - : call(function (noders, id, source) - if not source.module then - return - end - for _, src in ipairs(source.bindSources) do - if guide.isSet(src) then - local sourceID = getID(src) - if sourceID then - noders.require[sourceID] = source.module - end - end - end - end) - : case 'doc.param' - : call(function (noders, id, source) - pushForward(noders, id, getID(source.extends)) - for _, src in ipairs(source.bindSources) do - if src.type == 'local' and src.parent.type == 'in' then - pushForward(noders, getID(src), id) - end - end - if source.bindSources then - for _, src in ipairs(source.bindSources) do - if src.type == 'function' and src.args then - for _, arg in ipairs(src.args) do - if arg[1] == source.param[1] then - pushForward(noders, getID(arg), id) - end - end - end - end - end - end) - : case 'doc.vararg' - : call(function (noders, id, source) - pushForward(noders, getID(source), getID(source.vararg)) - end) - : case 'doc.see' - : call(function (noders, id, source) - local nameID = getID(source.name) - local classID = sgsub(nameID, '^dsn:', 'dn:') - pushForward(noders, nameID, classID) - if source.field then - local fieldID = getID(source.field) - local fieldClassID = sgsub(fieldID, '^dsn:', 'dn:') - pushForward(noders, fieldID, fieldClassID) - end - end) - : case 'call' - : call(function (noders, id, source) - if source.parent.type ~= 'select' then - compileCallReturn(noders, source, id, 1) - end - compileCallParam(noders, source, id) - end) - : case 'select' - : call(function (noders, id, source) - if source.vararg.type == 'call' then - local call = source.vararg - compileCallReturn(noders, call, id, source.sindex) - end - if source.vararg.type == 'varargs' then - pushForward(noders, id, getID(source.vararg)) - end - end) - : case 'doc.type.function' - : call(function (noders, id, source) - if source.args then - for index, param in ipairs(source.args) do - local indexID = sformat('%s%s%s' - , id - , PARAM_INDEX - , index - ) - pushForward(noders, indexID, getID(param.extends)) - end - end - if source.returns then - for index, rtn in ipairs(source.returns) do - local returnID = sformat('%s%s%s' - , id - , RETURN_INDEX - , index - ) - pushForward(noders, returnID, getID(rtn)) - end - end - -- @type fun(x: T):T 的情况 - local docType = getDocStateWithoutCrossFunction(source) - if docType and docType.type == 'doc.type' then - guide.eachSourceType(source, 'doc.type.name', function (typeName) - if typeName.typeGeneric then - source.isGeneric = true - return false - end - end) - end - end) - : case 'doc.type.name' - : call(function (noders, id, source) - local uri = guide.getUri(source) - collector:subscribe(uri, id, noders) - end) - : case 'doc.class.name' - : case 'doc.alias.name' - : call(function (noders, id, source) - local uri = guide.getUri(source) - collector:subscribe(uri, id, noders) - - local defID = 'def:' .. id - collector:subscribe(uri, defID, noders) - - local defAnyID = 'def:dn:' - collector:subscribe(uri, defAnyID, noders) - end) - : case 'function' - : call(function (noders, id, source) - local hasDocReturn - -- 检查 luadoc - if source.bindDocs then - for _, doc in ipairs(source.bindDocs) do - if doc.type == 'doc.return' then - hasDocReturn = true - end - if doc.type == 'doc.vararg' then - if source.args then - for _, param in ipairs(source.args) do - if param.type == '...' then - pushForward(noders, getID(param), getID(doc)) - end - end - end - end - if doc.type == 'doc.generic' then - source.isGeneric = true - end - if doc.type == 'doc.overload' then - pushForward(noders, id, getID(doc.overload)) - end - end - end - if source.args then - local parent = source.parent - local parentID = guide.isSet(parent) and getID(parent) - for i, arg in ipairs(source.args) do - if arg[1] == 'self' then - goto CONTINUE - end - local indexID = sformat('%s%s%s' - , id - , PARAM_INDEX - , i - ) - pushForward(noders, indexID, getID(arg)) - if arg.type ~= 'local' then - for j = i + 1, i + 10 do - pushForward(noders, sformat('%s%s%s' - , id - , PARAM_INDEX - , j - ), getID(arg)) - end - end - ::CONTINUE:: - end - end - -- 检查实体返回值 - if source.returns and not hasDocReturn then - for _, rtn in ipairs(source.returns) do - for index, rtnObj in ipairs(rtn) do - local returnID = sformat('%s%s%s' - , id - , RETURN_INDEX - , index - ) - pushForward(noders, returnID, getID(rtnObj)) - if config.get(guide.getUri(source), 'Lua.IntelliSense.traceReturn') then - pushBackward(noders, getID(rtnObj), returnID, INFO_DEEP_AND_DONT_CROSS) - end - end - end - end - end) - : case 'table' - : call(function (noders, id, source) - local firstField = source[1] - if firstField then - if firstField.type == 'varargs' then - local keyID = id .. TABLE_KEY - local valueID = id .. ANY_FIELD - source.array = firstField - pushForward(noders, keyID, 'dn:integer') - pushForward(noders, valueID, getID(firstField)) - else - local keyID = id .. WEAK_TABLE_KEY - local valueID = id .. WEAK_ANY_FIELD - if firstField.type == 'tablefield' then - pushForward(noders, keyID, 'dn:string') - pushForward(noders, valueID, getID(firstField.value)) - elseif firstField.type == 'tableindex' then - pushForward(noders, keyID, getID(firstField.index)) - pushForward(noders, valueID, getID(firstField.value)) - elseif firstField.type == 'tableexp' then - pushForward(noders, keyID, 'dn:integer') - pushForward(noders, valueID, getID(firstField)) - end - end - end - local parent = source.parent - if guide.isSet(parent) then - pushForward(noders, id, getID(parent)) - end - end) - : case 'loop' - : call(function (noders, id, source) - local loc = source.loc - if loc then - pushForward(noders, getID(loc), 'dn:integer') - end - end) - : case 'in' - : call(function (noders, id, source) - local keys = source.keys - ---@type parser.guide.object[] - local exps = source.exps - if not keys or not exps then - return - end - local node = exps[1] - local param1 = exps[2] - local param2 = exps[3] - if node.type == 'call' then - if not param1 then - param1 = { - type = 'select', - dummy = true, - sindex = 2, - start = node.start, - finish = node.finish, - vararg = node, - parent = source, - } - compileCallReturn(noders, node, getID(param1), 2) - if not param2 then - param2 = { - type = 'select', - dummy = true, - sindex = 3, - start = node.start, - finish = node.finish, - vararg = node, - parent = source, - } - compileCallReturn(noders, node, getID(param2), 3) - end - end - end - local call = { - type = 'call', - dummy = true, - start = source.keyword[3], - finish = exps[#exps].finish, - node = node, - args = { param1, param2 }, - parent = source, - } - for i = 1, #keys do - compileCallReturn(noders, call, getID(keys[i]), i) - end - end) - : case 'main' - : call(function (noders, id, source) - if source.returns then - for _, rtn in ipairs(source.returns) do - local rtnObj = rtn[1] - if rtnObj then - pushForward(noders, 'mainreturn', getID(rtnObj), INFO_REJECT_SET) - pushBackward(noders, getID(rtnObj), 'mainreturn', INFO_DEEP_AND_REJECT_SET) - end - end - end - end) - : case 'doc.return' - : call(function (noders, id, source) - if not source.bindSources then - return - end - for _, rtn in ipairs(source.returns) do - for _, src in ipairs(source.bindSources) do - if src.type == 'function' then - local fullID = sformat('%s%s%s' - , getID(src) - , RETURN_INDEX - , rtn.returnIndex - ) - pushForward(noders, fullID, getID(rtn)) - for _, typeUnit in ipairs(rtn.types) do - pushBackward(noders, getID(typeUnit), fullID, INFO_DEEP_AND_DONT_CROSS) - end - end - end - end - end) - : case 'generic.closure' - : call(function (noders, id, source) - for i, rtn in ipairs(source.returns) do - local closureID = sformat('%s%s%s' - , id - , RETURN_INDEX - , i - ) - local returnID = getID(rtn) - pushForward(noders, closureID, returnID) - end - end) - : case 'generic.value' - : call(function (noders, id, source) - local proto = source.proto - local closure = source.closure - local upvalues = closure.upvalues - if proto.type == 'doc.type.name' then - local key = proto[1] - if upvalues[key] then - for _, paramID in ipairs(upvalues[key]) do - pushForward(noders, id, paramID) - end - end - end - local f = compileNodeMap[proto.type] - if f then - f(noders, id, source) - end - end) - : getMap() - ----@param noders noders ----@param source parser.guide.object ----@return parser.guide.object[] -function m.compileNode(noders, source) - if source._noded then - return - end - source._noded = true - m.pushSource(noders, source) - local id = getID(source) - bindValue(noders, source, id) - - if id and specialMap[source.special] then - noders.skip[id] = true - end - - local f = compileNodeMap[source.type] - if f then - f(noders, id, source) - end - - if id and ssub(id, 1, 2) == 'g:' then - local uri = guide.getUri(source) - collector:subscribe(uri, id, noders) - if guide.isSet(source) - -- local t = Global --> t: g:.Global - and source.type ~= 'local' - and source.type ~= 'setlocal' then - - local defID = 'def:' .. id - collector:subscribe(uri, defID, noders) - - local fieldID = m.getLastID(id) - if fieldID then - local defNodeID = 'field:' .. fieldID - collector:subscribe(uri, defNodeID, noders) - end - - if guide.isGlobal(source) then - local defAnyID = 'def:g:' - collector:subscribe(uri, defAnyID, noders) - end - end - end -end - ----根据ID来获取第一个节点的ID ----@param id string ----@return string -function m.getFirstID(id) - local firstID, count = smatch(id, FIRST_REGEX) - if count == 0 then - return nil - end - if firstID == '' then - return nil - end - return firstID -end - ----根据ID来获取第一个节点的ID或field ----@param id string ----@return string -function m.getHeadID(id) - local headID, count = smatch(id, HEAD_REGEX) - if count == 0 then - return nil - end - if headID == '' then - return nil - end - return headID -end - ----根据ID来获取上个节点的ID ----@param id string ----@return string -function m.getLastID(id) - local lastID, count = sgsub(id, LAST_REGEX, '') - if count == 0 then - return nil - end - if lastID == '' then - return nil - end - return lastID -end - -function m.getFieldID(id) - local fieldID = smatch(id, LAST_REGEX) - return fieldID -end - ----获取ID的长度 ----@param id string ----@return integer -function m.getIDLength(id) - if not id then - return 0 - end - local _, count = sgsub(id, SPLIT_CHAR, SPLIT_CHAR) - return count + 1 -end - ----测试id是否包含field,如果遇到函数调用则中断 ----@param id string ----@return boolean -function m.hasField(id) - local firstID = m.getFirstID(id) - if firstID == id or not firstID then - return false - end - local nextChar = ssub(id, #firstID + 1, #firstID + 1) - if nextChar ~= SPLIT_CHAR then - return false - end - local next2Char = ssub(id, #firstID + 2, #firstID + 2) - if next2Char == RETURN_INDEX - or next2Char == PARAM_INDEX then - return false - end - return true -end - ----把形如 `@file:\\\XXXXX@gv:1|1`拆分成uri与id ----@param id string ----@return uri? string ----@return string id -function m.getUriAndID(id) - local uri, newID = smatch(id, URI_REGEX) - return uri, newID -end - ----是否是普通的field,例如数字或字符串,而不是函数返回值等 ----@param field any -function m.isCommonField(field) - if not field then - return false - end - if ssub(field, 1, #RETURN_INDEX) == RETURN_INDEX then - return false - end - return true -end - ----是否是普通的field,例如数字或字符串,而不是函数返回值等 -function m.hasCall(field) - if not field then - return false - end - if sfind(field, RETURN_INDEX, 1, true) then - return true - end - return false -end - -function m.isGlobalID(id) - return ssub(id, 1, 2) == 'g:' - or ssub(id, 1, 3) == 'dn:' -end - ----获取source的ID ----@param source parser.guide.object ----@return string -function m.getID(source) - return getID(source) -end - ----获取source的key ----@param source parser.guide.object ----@return string -function m.getKey(source) - return getKey(source) -end - ----清除临时id(用于泛型的临时对象) ----@param noders noders ----@param id string -function m.removeID(noders, id) - if not id then - return - end - for _, t in next, noders do - t[id] = nil - end -end - ----寻找doc的主体 ----@param doc parser.guide.object -function m.getDocState(doc) - return getDocStateWithoutCrossFunction(doc) -end - ----@param noders noders ----@return fun():node.id -function m.eachID(noders) - return next, noders.source -end - -m.getFieldEventName = getFieldEventName - ----获取对象的noders ----@param source parser.guide.object ----@return noders -function m.getNoders(source) - local root = guide.getRoot(source) - if not root._noders then - ---@type noders - root._noders = { - source = {}, - sources = {}, - forward = {}, - finfo = {}, - forwards = {}, - backward = {}, - binfo = {}, - backwards = {}, - extend = {}, - extends = {}, - call = {}, - require = {}, - skip = {}, - } - end - return root._noders -end - ----获取对象的noders ----@param uri uri ----@return noders -function m.getNodersByUri(uri) - local state = files.getState(uri) - if not state then - return nil - end - return m.getNoders(state.ast) -end - ----编译整个文件的node ----@param source parser.guide.object ----@return table -function m.compileAllNodes(source) - local root = guide.getRoot(source) - local noders = m.getNoders(source) - if root._initedNoders then - return noders - end - root._initedNoders = true - if not root._compiledGlobals then - collector:dropUri(guide.getUri(root)) - end - root._compiledGlobals = true - --log.debug('compileNodes:', guide.getUri(root)) - guide.eachSource(root, function (src) - m.compileNode(noders, src) - end) - --log.debug('compileNodes finish:', guide.getUri(root)) - return noders -end - -local partNodersMap = util.switch() - : case 'local' - : call(function (noders, source) - local refs = source.ref - if refs then - for i = 1, #refs do - local ref = refs[i] - m.compilePartNodes(noders, ref) - end - end - end) - : case 'setlocal' - : case 'getlocal' - : call(function (noders, source) - m.compilePartNodes(noders, source.node) - - local nxt = source.next - if nxt then - m.compilePartNodes(noders, nxt) - end - - local parent = source.parent - if parent.value == source then - m.compilePartNodes(noders, parent) - end - - if parent.type == 'call' then - local node = parent.node - if node.special == 'rawset' - or node.special == 'rawget' then - m.compilePartNodes(noders, parent) - end - end - end) - : case 'setfield' - : case 'getfield' - : case 'setmethod' - : case 'getmethod' - : call(function (noders, source) - local node = source.node - m.compilePartNodes(noders, node) - - local nxt = source.next - if nxt then - m.compilePartNodes(noders, nxt) - end - - local parent = source.parent - if parent.value == source then - m.compilePartNodes(noders, parent) - end - end) - : case 'setglobal' - : case 'getglobal' - : call(function (noders, source) - local nxt = source.next - if nxt then - m.compilePartNodes(noders, nxt) - end - - local parent = source.parent - if parent.value == source then - m.compilePartNodes(noders, parent) - end - - if parent.type == 'call' then - local node = parent.node - if node.special == 'rawset' - or node.special == 'rawget' then - m.compilePartNodes(noders, parent) - end - end - end) - : case 'label' - : call(function (noders, source) - local refs = source.ref - if not refs then - return - end - for i = 1, #refs do - local ref = refs[i] - m.compilePartNodes(noders, ref) - end - end) - : case 'goto' - : call(function (noders, source) - m.compilePartNodes(noders, source.node) - end) - : case 'table' - : call(function (noders, source) - for i = 1, #source do - local field = source[i] - m.compilePartNodes(noders, field) - end - end) - : case 'tablefield' - : case 'tableindex' - : call(function (noders, source) - m.compilePartNodes(noders, source.parent) - end) - : getMap() - ----编译Class的node ----@param noders noders ----@param source parser.guide.object ----@return table -function m.compilePartNodes(noders, source) - if not source then - return - end - if source._noded then - return - end - m.compileNode(noders, source) - local f = partNodersMap[source.type] - if f then - f(noders, source) - end -end - ----编译全局变量的node ----@param root parser.guide.object ----@return table -function m.compileGlobalNodes(root) - if root._initedNoders then - return - end - if not root._compiledGlobals then - collector:dropUri(guide.getUri(root)) - end - root._compiledGlobals = true - local noders = m.getNoders(root) - local env = guide.getENV(root) - - m.compilePartNodes(noders, env) - - local docs = root.docs - guide.eachSourceTypes(docs, { - 'doc.class.name', - 'doc.alias.name', - 'doc.type.name', - }, function (doc) - m.compileNode(noders, doc) - end) -end - -for uri in files.eachFile() do - local state = files.getState(uri) - if state then - m.compileGlobalNodes(state.ast) - end -end - -files.watch(function (ev, uri) - if ev == 'update' then - local state = files.getState(uri) - if state then - m.compileGlobalNodes(state.ast) - end - end - if ev == 'remove' then - collector:dropUri(uri) - end -end) - -return m diff --git a/script/core/reference.lua b/script/core/reference.lua index 5e4a4cbf..60a25940 100644 --- a/script/core/reference.lua +++ b/script/core/reference.lua @@ -1,4 +1,3 @@ -local searcher = require 'core.searcher' local guide = require 'parser.guide' local files = require 'files' local vm = require 'vm' @@ -66,22 +65,12 @@ return function (uri, position) local metaSource = vm.isMetaFile(uri) local refs = vm.getAllRefs(source) - local values = {} - for _, src in ipairs(refs) do - local value = searcher.getObjectValue(src) - if value and value ~= src and guide.isLiteral(value) then - values[value] = true - end - end local results = {} for _, src in ipairs(refs) do if src.dummy then goto CONTINUE end - if values[src] then - goto CONTINUE - end local root = guide.getRoot(src) if not root then goto CONTINUE diff --git a/script/core/rename.lua b/script/core/rename.lua index 69021197..c8e011b9 100644 --- a/script/core/rename.lua +++ b/script/core/rename.lua @@ -1,11 +1,8 @@ local files = require 'files' local vm = require 'vm' -local proto = require 'proto' -local define = require 'proto.define' local util = require 'utility' local findSource = require 'core.find-source' local guide = require 'parser.guide' -local noder = require 'core.noder' local Forcing diff --git a/script/core/searcher.lua b/script/core/searcher.lua deleted file mode 100644 index eb70c349..00000000 --- a/script/core/searcher.lua +++ /dev/null @@ -1,1350 +0,0 @@ -local noder = require 'core.noder' -local guide = require 'parser.guide' -local files = require 'files' -local generic = require 'core.generic' -local rpath = require 'workspace.require-path' -local vm = require 'vm.vm' -local collector = require 'core.collector' 'searcher' -local util = require 'utility' - -local TRACE = TRACE -local FOOTPRINT = FOOTPRINT -local TEST = TEST -local log = log -local select = select -local tostring = tostring -local next = next -local error = error -local type = type -local setmetatable = setmetatable -local ipairs = ipairs -local tconcat = table.concat -local ssub = string.sub -local sfind = string.find -local sformat = string.format - -local getUri = guide.getUri - -local getState = files.getState - -local getNoders = noder.getNoders -local getID = noder.getID -local getLastID = noder.getLastID -local removeID = noder.removeID -local getNodersByUri = noder.getNodersByUri -local getFirstID = noder.getFirstID -local getHeadID = noder.getHeadID -local eachForward = noder.eachForward -local getUriAndID = noder.getUriAndID -local eachBackward = noder.eachBackward -local eachExtend = noder.eachExtend -local eachSource = noder.eachSource -local compileAllNodes = noder.compileAllNodes -local hasCall = noder.hasCall - -local SPLIT_CHAR = noder.SPLIT_CHAR -local RETURN_INDEX = noder.RETURN_INDEX -local TABLE_KEY = noder.TABLE_KEY -local STRING_CHAR = noder.STRING_CHAR -local STRING_FIELD = noder.STRING_FIELD -local WEAK_TABLE_KEY = noder.WEAK_TABLE_KEY -local ANY_FIELD = noder.ANY_FIELD -local WEAK_ANY_FIELD = noder.WEAK_ANY_FIELD - -_ENV = nil - -local ignoredSources = { - ['int:'] = true, - ['num:'] = true, - ['str:'] = true, - ['bool:'] = true, - ['nil:'] = true, -} - -local ignoredIDs = { - ['dn:unknown'] = true, - ['dn:nil'] = true, - ['dn:any'] = true, - ['dn:boolean'] = true, - ['dn:table'] = true, - ['dn:number'] = true, - ['dn:integer'] = true, - ['dn:userdata'] = true, - ['dn:lightuserdata'] = true, - ['dn:function'] = true, - ['dn:thread'] = true, -} - ----@class searcher -local m = {} - ----@alias guide.searchmode '"ref"'|'"def"'|'"field"'|'"allref"'|'"alldef"'|'"allfield"' - -local pushDefResultsMap = util.switch() - : case 'local' - : case 'setlocal' - : case 'setglobal' - : call(function (source, status) - if source.type ~= 'local' then - source = source.node - end - if source[1] == 'self' - and source.parent.type == 'funcargs' then - local func = source.parent.parent - if status.source.start < func.start - or status.source.start > func.finish then - return false - end - end - return true - end) - : case 'label' - : case 'setfield' - : case 'setmethod' - : case 'setindex' - : case 'tableindex' - : case 'tablefield' - : case 'tableexp' - : case 'function' - : case 'table' - : case 'doc.class.name' - : case 'doc.alias.name' - : case 'doc.field.name' - : case 'doc.type.enum' - : case 'doc.resume' - : case 'doc.param' - : case 'doc.type.array' - : case 'doc.type.table' - : case 'doc.type.ltable' - : case 'doc.type.field' - : case 'doc.type.function' - : call(function (source, status) - return true - end) - : case 'doc.type.name' - : call(function (source, status) - return source.typeGeneric ~= nil - end) - : case 'call' - : call(function (source, status) - if source.node.special == 'rawset' then - return true - end - end) - : getMap() - -local pushRefResultsMap = util.switch() - : case 'local' - : case 'setlocal' - : case 'getlocal' - : case 'setglobal' - : case 'getglobal' - : case 'label' - : case 'goto' - : case 'setfield' - : case 'getfield' - : case 'setmethod' - : case 'getmethod' - : case 'setindex' - : case 'getindex' - : case 'tableindex' - : case 'tablefield' - : case 'tableexp' - : case 'function' - : case 'table' - : case 'string' - : case 'boolean' - : case 'number' - : case 'integer' - : case 'nil' - : case 'doc.class.name' - : case 'doc.type.name' - : case 'doc.alias.name' - : case 'doc.extends.name' - : case 'doc.field.name' - : case 'doc.type.enum' - : case 'doc.resume' - : case 'doc.type.array' - : case 'doc.type.table' - : case 'doc.type.ltable' - : case 'doc.type.field' - : case 'doc.type.function' - : call(function (source, status) - return true - end) - : case 'call' - : call(function (source, status) - if source.node.special == 'rawset' - or source.node.special == 'rawget' then - return true - end - end) - : getMap() - ----添加结果 ----@param status guide.status ----@param mode guide.searchmode ----@param source parser.guide.object ----@param force boolean -local function pushResult(status, mode, source, force) - if not source then - return false - end - local results = status.results - local mark = status.rmark - if mark[source] then - return true - end - mark[source] = true - if force then - results[#results+1] = source - return true - end - - if mode == 'def' - or mode == 'alldef' then - local f = pushDefResultsMap[source.type] - if f and f(source, status) then - results[#results+1] = source - return true - end - elseif mode == 'ref' - or mode == 'field' - or mode == 'allfield' - or mode == 'allref' then - local f = pushRefResultsMap[source.type] - if f and f(source, status) then - results[#results+1] = source - return true - end - end - - --local parent = source.parent - --if parent.type == 'return' then - -- if source ~= status.source then - -- results[#results+1] = source - -- return true - -- end - --end - - return false -end - ----@param obj parser.guide.object ----@return parser.guide.object? -function m.getObjectValue(obj) - while obj.type == 'paren' do - obj = obj.exp - if not obj then - return nil - end - end - if obj.type == 'boolean' - or obj.type == 'number' - or obj.type == 'integer' - or obj.type == 'string' then - return obj - end - if obj.value then - return obj.value - end - if obj.type == 'field' - or obj.type == 'method' then - return obj.parent and obj.parent.value - end - if obj.type == 'call' then - if obj.node.special == 'rawset' then - return obj.args and obj.args[3] - else - return obj - end - end - if obj.type == 'select' then - return obj - end - return nil -end - -local strs = {} -local function footprint(status, ...) - if TRACE then - log.debug(...) - end - if FOOTPRINT then - local n = select('#', ...) - for i = 1, n do - strs[i] = tostring(select(i, ...)) - end - status.footprint[#status.footprint+1] = tconcat(strs, '\t', 1, n) - end -end - -local function checkCache(status, uri, expect, mode) - local cache = status.cache - local fileCache = cache[uri] - local results = fileCache[expect] - if results then - for i = 1, #results do - local res = results[i] - pushResult(status, mode, res, true) - end - return true - end - return false, function () - fileCache[expect] = status.results - if mode == 'def' - or mode == 'alldef' then - return - end - for id in next, status.ids do - fileCache[id] = status.results - end - end -end - -local function stop(status, msg) - if TEST then - if FOOTPRINT then - log.debug(status.mode) - log.debug(tconcat(status.footprint, '\n')) - end - error(msg) - else - log.warn(msg) - if FOOTPRINT then - FOOTPRINT = false - log.error(status.mode) - log.debug(tconcat(status.footprint, '\n')) - end - return - end -end - -local function isCallID(field) - if not field then - return false - end - if ssub(field, 1, 2) == RETURN_INDEX then - return true - end - return false -end - -local genercCache = { - mark = {}, - genericCallArgs = {}, - closureCache = {}, -} - -local function flushGeneric() - --清除来自泛型的临时对象 - for _, closure in next, genercCache.closureCache do - if closure then - local noders = getNoders(closure) - removeID(noders, getID(closure)) - local values = closure.values - for i = 1, #values do - local value = values[i] - removeID(noders, getID(value)) - end - end - end - genercCache.mark = {} - genercCache.closureCache = {} - genercCache.genericCallArgs = {} -end - -files.watch(function (ev) - if ev == 'version' then - flushGeneric() - end -end) - -local nodersMapMT = {__index = function (self, uri) - local noders = getNodersByUri(uri) - self[uri] = noders or false - return noders -end} - -local uriMapMT = {__index = function (self, uri) - local t = {} - self[uri] = t - return t -end} - -function m.searchRefsByID(status, suri, expect, mode) - local state = getState(suri) - if not state then - return - end - local searchStep - - status.id = expect - - local callStack = status.callStack - local ids = status.ids - local dontCross = 0 - ---@type table<uri, noders> - local nodersMap = setmetatable({}, nodersMapMT) - local frejectMap = setmetatable({}, uriMapMT) - local brejectMap = setmetatable({}, uriMapMT) - local slockMap = setmetatable({}, uriMapMT) - local elockMap = setmetatable({}, uriMapMT) - local ecallMap = setmetatable({}, uriMapMT) - local slockGlobalMap = slockMap['@global'] - - compileAllNodes(state.ast) - - local function lockExpanding(elock, ecall, id, field) - if not field then - field = '' - end - local call = callStack[#callStack] - local locked = elock[id] - local called = ecall[id] - if locked and called == call then - if #locked <= #field then - if ssub(field, -#locked) == locked then - footprint(status, 'elocked:', id, locked, field) - return false - end - end - end - elock[id] = field - ecall[id] = call - return true - end - - local function releaseExpanding(elock, ecall, id, field) - elock[id] = nil - ecall[id] = nil - end - - local function search(uri, id, field) - local firstID = getFirstID(id) - if ignoredIDs[firstID] and (field or firstID ~= id) then - return - end - - footprint(status, 'search:', id, field) - searchStep(uri, id, field) - footprint(status, 'pop:', id, field) - end - - local function splitID(uri, id, field) - if field then - return - end - local leftID = '' - local rightID - - while true do - local firstID = getHeadID(rightID or id) - if not firstID or firstID == id then - break - end - leftID = leftID .. firstID - if leftID == id then - break - end - rightID = ssub(id, #leftID + 1) - search(uri, leftID, rightID) - local isCall = isCallID(firstID) - if isCall then - break - end - end - end - - local function searchID(uri, id, field, sourceUri) - if not id then - return - end - if not nodersMap[uri] then - return - end - if field then - id = id .. field - end - ids[id] = true - if slockMap[uri][id] then - footprint(status, 'slocked:', id) - return - end - slockMap[uri][id] = true - if sourceUri and uri ~= sourceUri then - footprint(status, 'cross uri:', uri) - compileAllNodes(getState(uri).ast) - end - search(uri, id, nil) - if sourceUri and uri ~= sourceUri then - footprint(status, 'back uri:', sourceUri) - end - end - - ---@return parser.guide.object? - local function findLastCall() - for i = #callStack, 1, -1 do - local call = callStack[i] - if call then - -- 标记此处的call失效,等待在堆栈平衡时弹出 - callStack[i] = false - return call - end - end - return nil - end - - local genericCallArgs = genercCache.genericCallArgs - local closureCache = genercCache.closureCache - local function checkGeneric(uri, source, field) - if not source.isGeneric then - return - end - if not isCallID(field) then - return - end - local call = findLastCall() - if not call then - return - end - local id = genercCache.mark[call] - if id == false then - return - end - if id then - searchID(uri, id, field) - return - end - - local args = call.args - if args then - for i = 1, #args do - local arg = args[i] - genericCallArgs[arg] = true - end - end - - local cacheID = uri .. getID(source) .. getID(call) - local closure = closureCache[cacheID] - if closure == false then - genercCache.mark[call] = false - return - end - if not closure then - closure = generic.createClosure(source, call) - closureCache[cacheID] = closure or false - if not closure then - genercCache.mark[call] = false - return - end - end - id = getID(closure) - genercCache.mark[call] = id - searchID(uri, id, field) - end - - local function checkENV(uri, source, field) - if not field then - return - end - if source.special ~= '_G' then - return - end - local newID = 'g:' .. ssub(field, 2) - searchID(uri, newID) - end - - ---@param ward '"forward"'|'"backward"' - ---@param info node.info - local function pushThenCheckReject(uri, ward, info) - local reject = info.reject - local checkReject - local pushReject - if ward == 'forward' then - checkReject = brejectMap[uri] - pushReject = frejectMap[uri] - else - checkReject = frejectMap[uri] - pushReject = brejectMap[uri] - end - pushReject[reject] = (pushReject[reject] or 0) + 1 - if checkReject[reject] and checkReject[reject] > 0 then - return false - end - return true - end - - ---@param ward '"forward"'|'"backward"' - ---@param info node.info - local function popReject(uri, ward, info) - local reject = info.reject - local popTags - if ward == 'forward' then - popTags = frejectMap[uri] - else - popTags = brejectMap[uri] - end - popTags[reject] = popTags[reject] - 1 - end - - ---@type table<node.filter, integer> - local filters = {} - - ---@param id string - ---@param info node.info - local function pushInfoFilter(id, field, info) - local filter = info.filter - local filterValid = info.filterValid - if filterValid and not filterValid(id, field) then - return - end - filters[filter] = (filters[filter] or 0) + 1 - end - - ---@param id string - ---@param info node.info - local function releaseInfoFilter(id, field, info) - local filter = info.filter - local filterValid = info.filterValid - if filterValid and not filterValid(id, field) then - return - end - if filters[filter] <= 1 then - filters[filter] = nil - else - filters[filter] = filters[filter] - 1 - end - end - - ---@param id string - ---@param info node.info - local function checkInfoFilter(id, field, info) - for filter in next, filters do - if not filter(id, field, mode) then - return false - end - end - return true - end - - local function checkForward(uri, id, field) - for forwardID, info in eachForward(nodersMap[uri], id) do - local targetUri, targetID - - --#region checkBeforeForward - if info then - if info.filter then - pushInfoFilter(forwardID, field, info) - end - if info.reject then - if not pushThenCheckReject(uri, 'forward', info) then - goto RELEASE_THEN_CONTINUE - end - end - end - if not checkInfoFilter(forwardID, field, info) then - goto RELEASE_THEN_CONTINUE - end - --#endregion - - targetUri, targetID = getUriAndID(forwardID) - if targetUri and targetUri ~= uri then - if dontCross == 0 then - searchID(targetUri, targetID, field, uri) - end - else - searchID(uri, targetID or forwardID, field) - end - - ::RELEASE_THEN_CONTINUE:: - --#region releaseAfterForward - if info then - if info.reject then - popReject(uri, 'forward', info) - end - if info.filter then - releaseInfoFilter(id, field, info) - end - end - --#endregion - end - end - - local function checkBackward(uri, id, field) - if ignoredIDs[id] - or id == 'dn:string' then - return - end - if mode ~= 'ref' - and mode ~= 'allfield' - and mode ~= 'allref' - and not field then - return - end - for backwardID, info in eachBackward(nodersMap[uri], id) do - local targetUri, targetID - if info and info.deep and mode ~= 'allref' and mode ~= 'allfield' then - goto CONTINUE - end - - --#region checkBeforeBackward - if info then - if info.dontCross then - dontCross = dontCross + 1 - end - if info.filter then - pushInfoFilter(backwardID, field, info) - end - if info.reject then - if not pushThenCheckReject(uri, 'backward', info) then - goto RELEASE_THEN_CONTINUE - end - end - end - if not checkInfoFilter(backwardID, field, info) then - goto RELEASE_THEN_CONTINUE - end - --#endregion - - targetUri, targetID = getUriAndID(backwardID) - if targetUri and targetUri ~= uri then - if dontCross == 0 then - searchID(targetUri, targetID, field, uri) - end - else - searchID(uri, targetID or backwardID, field) - end - - ::RELEASE_THEN_CONTINUE:: - --#region releaseAfterBackward - if info then - if info.reject then - popReject(uri, 'backward', info) - end - if info.filter then - releaseInfoFilter(backwardID, field, info) - end - if info.dontCross then - dontCross = dontCross - 1 - end - end - --#endregion - - ::CONTINUE:: - end - end - - local function checkExtend(uri, id, field) - if not field - and mode ~= 'field' - and mode ~= 'allfield' then - return - end - if field then - local results = status.results - for i = #results, 1, -1 do - local res = results[i] - if res.type == 'setfield' - or res.type == 'setmethod' - or res.type == 'setindex' then - local resField = noder.getFieldID(getID(res)) - if field == resField then - return - end - end - if res.type == 'doc.field.name' then - local resField = STRING_FIELD .. res[1] - if field == resField then - return - end - end - end - end - for extendID in eachExtend(nodersMap[uri], id) do - local targetUri, targetID - - targetUri, targetID = getUriAndID(extendID) - if targetUri and targetUri ~= uri then - if dontCross == 0 then - searchID(targetUri, targetID, field, uri) - end - else - searchID(uri, targetID or extendID, field) - end - end - end - - local function searchSpecial(uri, id, field) - -- Special rule: ('').XX -> stringlib.XX - if id == 'str:' - or id == 'dn:string' then - if field or mode == 'field' or mode == 'allfield' then - searchID(uri, 'dn:stringlib', field) - else - searchID(uri, 'dn:string', field) - end - end - end - - local function checkRequire(uri, requireName, field) - if not requireName then - return - end - local uris = rpath.findUrisByRequirePath(suri, requireName) - footprint(status, 'require:', requireName) - for i = 1, #uris do - local ruri = uris[i] - if uri ~= ruri then - searchID(ruri, 'mainreturn', field, uri) - break - end - end - end - - local function searchGlobal(uri, id, field) - if dontCross ~= 0 then - return - end - if ssub(id, 1, 2) ~= 'g:' then - return - end - footprint(status, 'searchGlobal:', id, field) - local crossed = {} - if mode == 'def' - or mode == 'alldef' - or field - or hasCall(field) then - for _, guri in collector:each(suri, 'def:' .. id) do - if uri == guri then - goto CONTINUE - end - searchID(guri, id, field, uri) - ::CONTINUE:: - end - elseif mode == 'field' - or mode == 'allfield' then - for _, guri in collector:each(suri, 'def:' .. id) do - if uri == guri then - goto CONTINUE - end - searchID(guri, id, field, uri) - ::CONTINUE:: - end - for _, guri in collector:each(suri, 'field:' .. id) do - if uri == guri then - goto CONTINUE - end - searchID(guri, id, field, uri) - ::CONTINUE:: - end - else - for _, guri in collector:each(suri, id) do - if crossed[guri] then - goto CONTINUE - end - if uri == guri then - goto CONTINUE - end - searchID(guri, id, field, uri) - ::CONTINUE:: - end - end - end - - local function searchClass(uri, id, field) - if dontCross ~= 0 then - return - end - if ssub(id, 1, 3) ~= 'dn:' then - return - end - footprint(status, 'searchClass:', id, field) - local crossed = {} - if mode == 'def' - or mode == 'alldef' - or mode == 'field' - or ignoredIDs[id] - or id == 'dn:string' - or hasCall(field) then - for _, guri in collector:each(suri, 'def:' .. id) do - if uri == guri then - goto CONTINUE - end - searchID(guri, id, field, uri) - ::CONTINUE:: - end - else - for _, guri in collector:each(suri, id) do - if crossed[guri] then - goto CONTINUE - end - if uri == guri then - goto CONTINUE - end - searchID(guri, id, field, uri) - ::CONTINUE:: - end - end - end - - local function checkMainReturn(uri, id, field) - if id ~= 'mainreturn' then - return - end - local calls = vm.getLinksTo(uri) - for i = 1, #calls do - local call = calls[i] - local curi = getUri(call) - local cid = getID(call) - if curi ~= uri then - searchID(curi, cid, field, uri) - end - end - end - - local function searchNode(uri, id, field) - local noders = nodersMap[uri] - local call = noders.call[id] - callStack[#callStack+1] = call - - if field == nil and not ignoredSources[id] then - for source in eachSource(noders, id) do - local force = genericCallArgs[source] - pushResult(status, mode, source, force) - end - end - - local requireName = noders.require[id] - if requireName then - checkRequire(uri, requireName, field) - end - - local elock = elockMap[uri] - local ecall = ecallMap[uri] - - if lockExpanding(elock, ecall, id, field) then - if noders.forward[id] then - checkForward(uri, id, field) - end - if noders.backward[id] then - checkBackward(uri, id, field) - end - if noders.extend[id] then - checkExtend(uri, id, field) - end - releaseExpanding(elock, ecall, id, field) - end - - local source = noders.source[id] - if source then - checkGeneric(uri, source, field) - checkENV(uri, source, field) - end - - if mode == 'allref' or mode == 'alldef' then - checkMainReturn(uri, id, field) - end - - if call then - callStack[#callStack] = nil - end - - return false - end - - local function searchAnyField(uri, id, field) - if mode == 'ref' or mode == 'allref' then - return - end - local lastID = getLastID(id) - if not lastID then - return - end - local originField = ssub(id, #lastID + 1) - if originField == TABLE_KEY - or originField == WEAK_TABLE_KEY then - return - end - local anyFieldID = lastID .. ANY_FIELD - searchNode(uri, anyFieldID, field) - end - - local function searchWeak(uri, id, field) - local lastID = getLastID(id) - if not lastID then - return - end - local originField = ssub(id, #lastID + 1) - if originField == WEAK_TABLE_KEY then - local newID = lastID .. TABLE_KEY - searchNode(uri, newID, field) - end - if originField == WEAK_ANY_FIELD then - local newID = lastID .. ANY_FIELD - searchNode(uri, newID, field) - end - end - - local stepCount = 0 - local stepMaxCount = 1e4 - if mode == 'allref' or mode == 'alldef' then - stepMaxCount = 1e5 - end - - function searchStep(uri, id, field) - stepCount = stepCount + 1 - if stepCount > stepMaxCount then - stop(status, 'too deep!') - return - end - searchSpecial(uri, id, field) - searchNode(uri, id, field) - if field and nodersMap[uri].skip[id] then - return - end - - local fullID = id .. (field or '') - if not slockGlobalMap[fullID] then - slockGlobalMap[fullID] = true - searchGlobal(uri, id, field) - searchClass(uri, id, field) - slockGlobalMap[fullID] = nil - end - - splitID(uri, id, field) - searchAnyField(uri, id, field) - searchWeak(uri, id, field) - end - - search(suri, expect, nil) - flushGeneric() -end - -local function prepareSearch(source) - if not source then - return - end - if source.type == 'field' - or source.type == 'method' then - source = source.parent - end - if not source then - return - end - local uri = getUri(source) - local id = getID(source) - -- return function - if source.type == 'function' and source.parent.type == 'return' then - local func = guide.getParentFunction(source) - if func.type == 'function' then - for index, rtn in ipairs(source.parent) do - if rtn == source then - id = sformat('%s%s%s' - , getID(func) - , RETURN_INDEX - , index - ) - break - end - end - end - end - return uri, id -end - -local fieldNextTypeMap = util.switch() - : case 'getmethod' - : case 'setmethod' - : case 'getfield' - : case 'setfield' - : case 'getindex' - : case 'setindex' - : call(pushResult) - : getMap() - -local function getField(status, source, mode) - if source.type == 'table' then - for i = 1, #source do - local field = source[i] - pushResult(status, mode, field) - end - return - end - if source.type == 'doc.type.ltable' then - local fields = source.fields - for i = 1, #fields do - local field = fields[i] - pushResult(status, mode, field) - end - end - if source.type == 'doc.class.name' then - local class = source.parent - local fields = class.fields - for i = 1, #fields do - local field = fields[i] - pushResult(status, mode, field.field) - end - return - end - local field = source.next - if field then - local ftype = field.type - local pushResultOrNil = fieldNextTypeMap[ftype] - if pushResultOrNil then - pushResultOrNil(status, mode, field) - end - return - end -end - -local function searchAllGlobalByUri(status, mode, uri, fullID) - local ast = files.getState(uri) - if not ast then - return - end - local root = ast.ast - --compileAllNodes(root) - local noders = getNoders(root) - if fullID then - for source in eachSource(noders, fullID) do - pushResult(status, mode, source) - end - else - for id in next, noders.source do - if ssub(id, 1, 2) == 'g:' - and not sfind(id, SPLIT_CHAR) then - for source in eachSource(noders, id) do - pushResult(status, mode, source) - end - end - end - end -end - -local function searchAllGlobals(status, mode) - for uri in files.eachFile() do - searchAllGlobalByUri(status, mode, uri) - end -end - ----查找全局变量 ----@param uri uri ----@param mode guide.searchmode ----@param name string ----@return parser.guide.object[] -function m.findGlobals(uri, mode, name) - local status = m.status(nil, nil, mode) - - if name then - local fullID - if type(name) == 'string' then - fullID = sformat('%s%s%s', 'g:', STRING_CHAR, name) - else - fullID = sformat('%s%s%s', 'g:', '', name) - end - searchAllGlobalByUri(status, mode, uri, fullID) - else - searchAllGlobalByUri(status, mode, uri) - end - - return status.results -end - ----搜索对象的引用 ----@param status guide.status ----@param source parser.guide.object ----@param mode guide.searchmode -function m.searchRefs(status, source, mode) - local uri, id = prepareSearch(source) - if not id then - return - end - - local cached, makeCache = checkCache(status, uri, id, mode) - - if cached then - return - end - - if TRACE then - log.debug('searchRefs:', id) - end - m.searchRefsByID(status, uri, id, mode) - if makeCache then - makeCache() - end -end - ----搜索对象的field ----@param status guide.status ----@param source parser.guide.object ----@param mode guide.searchmode ----@param field string -function m.searchFields(status, source, mode, field) - local uri, id = prepareSearch(source) - if not id then - return - end - if TRACE then - log.debug('searchFields:', id, field) - end - if field == '*' then - if source.special == '_G' then - local cached, makeCache = checkCache(status, uri, '*', mode) - if cached then - return - end - searchAllGlobals(status, mode) - if makeCache then - makeCache() - end - else - local cached, makeCache = checkCache(status, uri, id .. '*', mode) - if cached then - return - end - local fieldMode = 'field' - if mode == 'allref' then - fieldMode = 'allfield' - end - local newStatus = m.status(source, field, fieldMode) - m.searchRefsByID(newStatus, uri, id, fieldMode) - local results = newStatus.results - for i = 1, #results do - local def = results[i] - getField(status, def, mode) - end - if makeCache then - makeCache() - end - end - else - if source.special == '_G' then - local fullID - if type(field) == 'string' then - fullID = sformat('%s%s%s', 'g:', STRING_CHAR, field) - else - fullID = sformat('%s%s%s', 'g:', '', field) - end - local cahced, makeCache = checkCache(status, uri, fullID, mode) - if cahced then - return - end - m.searchRefsByID(status, uri, fullID, mode) - if makeCache then - makeCache() - end - else - local fullID - if type(field) == 'string' then - fullID = sformat('%s%s%s', id, STRING_FIELD, field) - else - fullID = sformat('%s%s%s', id, SPLIT_CHAR, field) - end - local cahced, makeCache = checkCache(status, uri, fullID, mode) - if cahced then - return - end - m.searchRefsByID(status, uri, fullID, mode) - if makeCache then - makeCache() - end - end - end -end - ----@class guide.status ----搜索结果 ----@field results parser.guide.object[] ----@field rmark table ----@field id string ----@field source parser.guide.object ----@field field string - ----创建搜索状态 ----@param mode guide.searchmode ----@return guide.status -function m.status(source, field, mode) - local status = { - callStack = {}, - results = {}, - rmark = {}, - footprint = {}, - ids = {}, - mode = mode, - source = source, - field = field, - cache = setmetatable(vm.getCache('searcher:' .. mode), uriMapMT), - } - return status -end - ---- 请求对象的引用 ----@param obj parser.guide.object ----@param field? string ----@return parser.guide.object[] -function m.requestReference(obj, field) - local status = m.status(obj, field, 'ref') - - if field then - m.searchFields(status, obj, 'ref', field) - else - m.searchRefs(status, obj, 'ref') - end - - return status.results -end - ---- 请求对象的全部引用(深度搜索) ----@param obj parser.guide.object ----@param field? string ----@return parser.guide.object[] -function m.requestAllReference(obj, field) - local status = m.status(obj, field, 'allref') - - if field then - m.searchFields(status, obj, 'allref', field) - else - m.searchRefs(status, obj, 'allref') - end - - return status.results -end - ---- 请求对象的定义 ----@param obj parser.guide.object ----@param field? string ----@return parser.guide.object[] -function m.requestDefinition(obj, field) - local status = m.status(obj, field, 'def') - - if field then - m.searchFields(status, obj, 'def', field) - else - m.searchRefs(status, obj, 'def') - end - - return status.results -end - ---- 请求对象的全部定义(深度搜索) ----@param obj parser.guide.object ----@param field? string ----@return parser.guide.object[] -function m.requestAllDefinition(obj, field) - local status = m.status(obj, field, 'alldef') - - if field then - m.searchFields(status, obj, 'alldef', field) - else - m.searchRefs(status, obj, 'alldef') - end - - return status.results -end - ---m.requestReference = m.requestAllReference ---m.requestDefinition = m.requestAllDefinition - -return m diff --git a/script/parser/guide.lua b/script/parser/guide.lua index eda85c97..10b2badb 100644 --- a/script/parser/guide.lua +++ b/script/parser/guide.lua @@ -1,66 +1,61 @@ local error = error local type = type ----@class parser.guide.object ----@field bindDocs parser.guide.object[] ----@field bindGroup parser.guide.object[] ----@field bindSources parser.guide.object[] ----@field value parser.guide.object ----@field parent parser.guide.object +---@class parser.object +---@field bindDocs parser.object[] +---@field bindGroup parser.object[] +---@field bindSources parser.object[] +---@field value parser.object +---@field parent parser.object ---@field type string ---@field special string ---@field tag string ----@field args parser.guide.object[] ----@field locals parser.guide.object[] ----@field returns parser.guide.object[] +---@field args parser.object[] +---@field locals parser.object[] +---@field returns parser.object[] ---@field uri uri ---@field start integer ---@field finish integer ---@field effect integer ---@field attrs string[] ----@field specials parser.guide.object[] ----@field labels parser.guide.object[] ----@field node parser.guide.object +---@field specials parser.object[] +---@field labels parser.object[] +---@field node parser.object ---@field dummy boolean ----@field field parser.guide.object ----@field method parser.guide.object ----@field index parser.guide.object ----@field extends parser.guide.object[] ----@field types parser.guide.object[] ----@field fields parser.guide.object[] ----@field typeGeneric table<integer, parser.guide.object[]> ----@field tkey parser.guide.object ----@field tvalue parser.guide.object +---@field field parser.object +---@field method parser.object +---@field index parser.object +---@field extends parser.object[] +---@field types parser.object[] +---@field fields parser.object[] +---@field typeGeneric table<integer, parser.object[]> +---@field tkey parser.object +---@field tvalue parser.object ---@field tindex integer ----@field op parser.guide.object ----@field next parser.guide.object ----@field docParam parser.guide.object +---@field op parser.object +---@field next parser.object +---@field docParam parser.object ---@field sindex integer ----@field name parser.guide.object ----@field call parser.guide.object ----@field closure parser.guide.object ----@field proto parser.guide.object ----@field exp parser.guide.object +---@field name parser.object +---@field call parser.object +---@field closure parser.object +---@field proto parser.object +---@field exp parser.object ---@field isGeneric boolean ----@field alias parser.guide.object ----@field class parser.guide.object ----@field vararg parser.guide.object ----@field param parser.guide.object ----@field overload parser.guide.object +---@field alias parser.object +---@field class parser.object +---@field vararg parser.object +---@field param parser.object +---@field overload parser.object ---@field docParamMap table<string, integer> ---@field upvalues table<string, string[]> ----@field ref parser.guide.object[] +---@field ref parser.object[] ---@field returnIndex integer ----@field docs parser.guide.object[] +---@field docs parser.object[] ---@field state table ---@field comment table ---@field optional boolean ----@field _root parser.guide.object ----@field _noders noders ----@field _mnode parser.guide.object ----@field _noded boolean ----@field _initedNoders boolean ----@field _compiledGlobals boolean +---@field _root parser.object ---@class guide ---@field debugMode boolean @@ -147,7 +142,7 @@ local childMap = { ['doc.diagnostic'] = {'#names'}, } ----@type table<string, fun(obj: parser.guide.object, list: parser.guide.object[])> +---@type table<string, fun(obj: parser.object, list: parser.object[])> local compiledChildMap = setmetatable({}, {__index = function (self, name) local defs = childMap[name] if not defs then @@ -227,7 +222,7 @@ local function formatNumber(n) end --- 是否是字面量 ----@param obj parser.guide.object +---@param obj parser.object ---@return boolean function m.isLiteral(obj) local tp = obj.type @@ -241,7 +236,7 @@ function m.isLiteral(obj) end --- 获取字面量 ----@param obj parser.guide.object +---@param obj parser.object ---@return any function m.getLiteral(obj) local tp = obj.type @@ -258,8 +253,8 @@ function m.getLiteral(obj) end --- 寻找父函数 ----@param obj parser.guide.object ----@return parser.guide.object +---@param obj parser.object +---@return parser.object function m.getParentFunction(obj) for _ = 1, 10000 do obj = obj.parent @@ -275,8 +270,8 @@ function m.getParentFunction(obj) end --- 寻找所在区块 ----@param obj parser.guide.object ----@return parser.guide.object +---@param obj parser.object +---@return parser.object function m.getBlock(obj) for _ = 1, 10000 do if not obj then @@ -304,8 +299,8 @@ function m.getBlock(obj) end --- 寻找所在父区块 ----@param obj parser.guide.object ----@return parser.guide.object +---@param obj parser.object +---@return parser.object function m.getParentBlock(obj) for _ = 1, 10000 do obj = obj.parent @@ -321,8 +316,8 @@ function m.getParentBlock(obj) end --- 寻找所在可break的父区块 ----@param obj parser.guide.object ----@return parser.guide.object +---@param obj parser.object +---@return parser.object function m.getBreakBlock(obj) for _ = 1, 10000 do obj = obj.parent @@ -341,8 +336,8 @@ function m.getBreakBlock(obj) end --- 寻找doc的主体 ----@param obj parser.guide.object ----@return parser.guide.object +---@param obj parser.object +---@return parser.object function m.getDocState(obj) for _ = 1, 10000 do local parent = obj.parent @@ -358,8 +353,8 @@ function m.getDocState(obj) end --- 寻找所在父类型 ----@param obj parser.guide.object ----@return parser.guide.object +---@param obj parser.object +---@return parser.object function m.getParentType(obj, want) for _ = 1, 10000 do obj = obj.parent @@ -374,8 +369,8 @@ function m.getParentType(obj, want) end --- 寻找根区块 ----@param obj parser.guide.object ----@return parser.guide.object +---@param obj parser.object +---@return parser.object function m.getRoot(obj) local source = obj if source._root then @@ -399,7 +394,7 @@ function m.getRoot(obj) error('guide.getRoot overstack') end ----@param obj parser.guide.object +---@param obj parser.object ---@return uri function m.getUri(obj) if obj.uri then @@ -1106,7 +1101,7 @@ function m.getPath(a, b, sameFunction) end ---是否是全局变量(包括 _G.XXX 形式) ----@param source parser.guide.object +---@param source parser.object ---@return boolean function m.isGlobal(source) if source._isGlobal ~= nil then diff --git a/script/parser/newparser.lua b/script/parser/newparser.lua index e4884212..66df7046 100644 --- a/script/parser/newparser.lua +++ b/script/parser/newparser.lua @@ -2588,9 +2588,9 @@ local function skipSeps() end end ----@return parser.guide.object first ----@return parser.guide.object second ----@return parser.guide.object[] rest +---@return parser.object first +---@return parser.object second +---@return parser.object[] rest local function parseSetValues() skipSpace() local first = parseExp() @@ -2645,8 +2645,8 @@ local function pushActionIntoCurrentChunk(action) end end ----@return parser.guide.object second ----@return parser.guide.object[] rest +---@return parser.object second +---@return parser.object[] rest local function parseVarTails(parser, isLocal) if Tokens[Index + 1] ~= ',' then return diff --git a/script/utility.lua b/script/utility.lua index 6758a47f..0e4df627 100644 --- a/script/utility.lua +++ b/script/utility.lua @@ -21,9 +21,8 @@ local getupvalue = debug.getupvalue local mathHuge = math.huge local inf = 1 / 0 local nan = 0 / 0 -local utf8 = utf8 local error = error -local upvalueid = debug.upvalueid +local assert = assert _ENV = nil @@ -730,4 +729,32 @@ function m.defaultTable(default) end }) end +function m.multiTable(count, default) + local current + if default then + current = setmetatable({}, { __index = function (t, k) + local v = default(k) + t[k] = v + return v + end }) + else + current = setmetatable({}, { __index = function (t, k) + local v = {} + t[k] = v + return v + end }) + end + for _ = 3, count do + current = setmetatable({}, { __index = function (t, k) + t[k] = current + return current + end }) + end + return current +end + +m.MODE_K = { __mode = 'k' } +m.MODE_V = { __mode = 'v' } +m.MODE_KV = { __mode = 'kv' } + return m diff --git a/script/vm/eachDef.lua b/script/vm/eachDef.lua deleted file mode 100644 index ea14ed9f..00000000 --- a/script/vm/eachDef.lua +++ /dev/null @@ -1,11 +0,0 @@ ----@class vm -local vm = require 'vm.vm' -local searcher = require 'core.searcher' - -function vm.getDefs(source, field) - return searcher.requestDefinition(source, field) -end - -function vm.getAllDefs(source, field) - return searcher.requestAllDefinition(source, field) -end diff --git a/script/vm/getDef.lua b/script/vm/getDef.lua new file mode 100644 index 00000000..df180420 --- /dev/null +++ b/script/vm/getDef.lua @@ -0,0 +1,175 @@ +---@class vm +local vm = require 'vm.vm' +local util = require 'utility' +local compiler = require 'vm.node.compiler' +local guide = require 'parser.guide' +local localID = require 'vm.local-id' +local globalNode = require 'vm.global-node' + +local simpleMap + +local function searchGetLocal(source, node, pushResult) + local key = guide.getKeyName(source) + for _, ref in ipairs(node.node.ref) do + if ref.type == 'getlocal' + and guide.isSet(ref.next) + and guide.getKeyName(ref.next) == key then + pushResult(ref.next) + end + end +end + +simpleMap = util.switch() + : case 'local' + : call(function (source, pushResult) + pushResult(source) + if source.ref then + for _, ref in ipairs(source.ref) do + if ref.type == 'setlocal' then + pushResult(ref) + end + end + end + + if source.dummy then + for _, res in ipairs(vm.getDefs(source.method.node)) do + pushResult(res) + end + end + end) + : case 'getlocal' + : case 'setlocal' + : call(function (source, pushResult) + simpleMap['local'](source.node, pushResult) + end) + : case 'field' + : call(function (source, pushResult) + local parent = source.parent + simpleMap[parent.type](parent, pushResult) + end) + : case 'setfield' + : case 'getfield' + : call(function (source, pushResult) + local node = source.node + if node.type == 'getlocal' then + searchGetLocal(source, node, pushResult) + return + end + end) + : case 'getindex' + : case 'setindex' + : call(function (source, pushResult) + local node = source.node + if node.type == 'getlocal' then + searchGetLocal(source, node, pushResult) + end + end) + : getMap() + +local searchFieldMap = util.switch() + : case 'table' + : call(function (node, key, pushResult) + for _, field in ipairs(node) do + if field.type == 'tablefield' + or field.type == 'tableindex' then + if guide.getKeyName(field) == key then + pushResult(field) + end + end + end + end) + : getMap() + +local nodeMap;nodeMap = util.switch() + : case 'field' + : call(function (source, pushResult) + local parent = source.parent + nodeMap[parent.type](parent, pushResult) + end) + : case 'getfield' + : case 'setfield' + : case 'getmethod' + : case 'setmethod' + : case 'getindex' + : case 'setindex' + : call(function (source, pushResult) + local node = compiler.compileNode(source.node) + if not node then + return + end + if searchFieldMap[node.type] then + searchFieldMap[node.type](node, guide.getKeyName(source), pushResult) + end + end) + : getMap() + + ---@param source parser.object + ---@param pushResult fun(src: parser.object) +local function searchBySimple(source, pushResult) + local simple = simpleMap[source.type] + if simple then + simple(source, pushResult) + end +end + +---@param source parser.object +---@param pushResult fun(src: parser.object) +local function searchByGlobal(source, pushResult) + local global = globalNode.getNode(source) + if not global then + return + end + for _, src in ipairs(global:getSets()) do + pushResult(src) + end +end + +---@param source parser.object +---@param pushResult fun(src: parser.object) +local function searchByID(source, pushResult) + local idSources = localID.getSources(source) + if not idSources then + return + end + for _, src in ipairs(idSources) do + if guide.isSet(src) then + pushResult(src) + end + end +end + +---@param source parser.object +---@param pushResult fun(src: parser.object) +local function searchByNode(source, pushResult) + local node = nodeMap[source.type] + if node then + node(source, pushResult) + end +end + +---@param source parser.object +---@return parser.object[] +function vm.getDefs(source) + local results = {} + local mark = {} + + local function pushResult(src) + if not mark[src] then + mark[src] = true + results[#results+1] = src + end + end + + searchBySimple(source, pushResult) + searchByGlobal(source, pushResult) + searchByID(source, pushResult) + searchByNode(source, pushResult) + + return results +end + +---@param source parser.object +---@return parser.object[] +function vm.getAllDefs(source) + return vm.getDefs(source) +end diff --git a/script/vm/getDocs.lua b/script/vm/getDocs.lua index 4aeda446..4204d785 100644 --- a/script/vm/getDocs.lua +++ b/script/vm/getDocs.lua @@ -5,11 +5,10 @@ local vm = require 'vm.vm' local config = require 'config' local collector = require 'core.collector' 'searcher' local define = require 'proto.define' -local noder = require 'core.noder' ---获取class与alias ---@param name? string ----@return parser.guide.object[] +---@return parser.object[] function vm.getDocDefines(uri, name) if type(name) ~= 'string' then return {} diff --git a/script/vm/getGlobals.lua b/script/vm/getGlobals.lua index 8af21d45..2ba734ba 100644 --- a/script/vm/getGlobals.lua +++ b/script/vm/getGlobals.lua @@ -2,7 +2,6 @@ local collector = require 'core.collector' 'searcher' local guide = require 'parser.guide' ---@class vm local vm = require 'vm.vm' -local noder = require 'core.noder' function vm.hasGlobalSets(uri, name) local id diff --git a/script/vm/eachRef.lua b/script/vm/getRef.lua index 899c04c6..ded95b61 100644 --- a/script/vm/eachRef.lua +++ b/script/vm/getRef.lua @@ -1,6 +1,5 @@ ---@class vm local vm = require 'vm.vm' -local searcher = require 'core.searcher' function vm.getRefs(source, field) return searcher.requestReference(source, field) diff --git a/script/vm/global-node.lua b/script/vm/global-node.lua new file mode 100644 index 00000000..6235ff6b --- /dev/null +++ b/script/vm/global-node.lua @@ -0,0 +1,142 @@ +local util = require 'utility' +local guide = require 'parser.guide' +local globalBuilder = require 'vm.node.global' + +---@class parser.object +---@field _globalNode vm.node.global + +---@class vm.global-node +local m = {} +---@type table<string, vm.node.global> +m.globals = util.defaultTable(globalBuilder) +---@type table<uri, table<string, boolean>> +m.globalSubs = util.defaultTable(function () + return {} +end) + +m.ID_SPLITE = '\x1F' + +local compilerGlobalMap = util.switch() + : case 'local' + : call(function (uri, source) + if source.tag ~= '_ENV' then + return + end + if source.ref then + for _, ref in ipairs(source.ref) do + m.compileNode(uri, ref) + end + end + end) + : case 'setglobal' + : call(function (uri, source) + local name = guide.getKeyName(source) + source._globalNode = m.declareGlobal(name, uri, source) + end) + : case 'getglobal' + : call(function (uri, source) + local name = guide.getKeyName(source) + local global = m.getGlobal(name) + global:addGet(uri, source) + source._globalNode = global + + local nxt = source.next + if nxt then + m.compileNode(uri, nxt) + end + end) + : case 'setfield' + : case 'setmethod' + : case 'setindex' + ---@param uri uri + ---@param source parser.object + : call(function (uri, source) + local parent = source.node._globalNode + if not parent then + return + end + local name = parent:getName() .. m.ID_SPLITE .. guide.getKeyName(source) + source._globalNode = m.declareGlobal(name, uri, source) + end) + : case 'getfield' + : case 'getmethod' + : case 'getindex' + ---@param uri uri + ---@param source parser.object + : call(function (uri, source) + local parent = source.node._globalNode + if not parent then + return + end + local name = parent:getName() .. m.ID_SPLITE .. guide.getKeyName(source) + local global = m.getGlobal(name) + global:addGet(uri, source) + source._globalNode = global + + local nxt = source.next + if nxt then + m.compileNode(uri, nxt) + end + end) + : getMap() + + +---@param name string +---@param uri uri +---@param source parser.object +---@return vm.node.global +function m.declareGlobal(name, uri, source) + m.globalSubs[uri][name] = true + local node = m.globals[name] + node:addSet(uri, source) + return node +end + +---@param name string +---@param uri? uri +---@return vm.node.global +function m.getGlobal(name, uri) + if uri then + m.globalSubs[uri][name] = true + end + return m.globals[name] +end + +---@param source parser.object +function m.compileNode(uri, source) + if source._globalNode ~= nil then + return + end + source._globalNode = false + local compiler = compilerGlobalMap[source.type] + if compiler then + compiler(uri, source) + end +end + +---@param source parser.object +function m.compileAst(source) + local uri = guide.getUri(source) + local env = guide.getENV(source) + m.compileNode(uri, env) +end + +---@return vm.node.global +function m.getNode(source) + if source.type == 'field' + or source.type == 'method' then + source = source.parent + end + return source._globalNode +end + +---@param uri uri +function m.dropUri(uri) + local globalSub = m.globalSubs[uri] + m.globalSubs[uri] = nil + for name in pairs(globalSub) do + m.globals[name]:dropUri(uri) + end +end + +return m diff --git a/script/vm/init.lua b/script/vm/init.lua index 935f39e3..5a5de99e 100644 --- a/script/vm/init.lua +++ b/script/vm/init.lua @@ -1,8 +1,9 @@ local vm = require 'vm.vm' +require 'vm.state' require 'vm.getGlobals' require 'vm.getDocs' require 'vm.getLibrary' -require 'vm.eachDef' -require 'vm.eachRef' +require 'vm.getDef' +require 'vm.getRef' require 'vm.getLinks' return vm diff --git a/script/vm/local-id.lua b/script/vm/local-id.lua new file mode 100644 index 00000000..8487d96a --- /dev/null +++ b/script/vm/local-id.lua @@ -0,0 +1,116 @@ +local util = require 'utility' +local guide = require 'parser.guide' + +---@class parser.object +---@field _localID string +---@field _localIDs table<string, parser.object[]> + +---@class vm.local-id +local m = {} + +m.ID_SPLITE = '\x1F' + +local compileMap = util.switch() + : case 'local' + : call(function (source) + if not source.ref then + return + end + for _, ref in ipairs(source.ref) do + m.compileLocalID(ref) + end + end) + : case 'getlocal' + : call(function (source) + source._localID = ('%d'):format(source.node.start) + m.compileLocalID(source.next) + end) + : case 'getfield' + : case 'setfield' + : call(function (source) + local parentID = source.node._localID + if not parentID then + return + end + source._localID = parentID .. m.ID_SPLITE .. guide.getKeyName(source) + source.field._localID = source._localID + if source.type == 'getfield' then + m.compileLocalID(source.next) + end + end) + : getMap() + +local leftMap = util.switch() + : case 'field' + : call(function (source) + return m.getLocal(source.parent) + end) + : case 'getfield' + : case 'setfield' + : call(function (source) + return m.getLocal(source.node) + end) + : case 'getlocal' + : call(function (source) + return source.node + end) + : getMap() + +---@param source parser.object +---@return parser.object? +function m.getLocal(source) + local getLeft = leftMap[source.type] + if getLeft then + return getLeft(source) + end + return nil +end + +function m.compileLocalID(source) + if not source then + return + end + source._localID = false + local compiler = compileMap[source.type] + if not compiler then + return + end + compiler(source) + local root = guide.getRoot(source) + if not root._localIDs then + root._localIDs = util.multiTable(2) + end + local sources = root._localIDs[source._localID] + sources[#sources+1] = source +end + +---@param source parser.object +---@return string|boolean +function m.getID(source) + if source._localID ~= nil then + return source._localID + end + source._localID = false + local loc = m.getLocal(source) + if not loc then + return source._localID + end + m.compileLocalID(loc) + return source._localID +end + +---@param source parser.object +---@return parser.object[]? +function m.getSources(source) + local id = m.getID(source) + if not id then + return nil + end + local root = guide.getRoot(source) + if not root._localIDs then + return nil + end + return root._localIDs[id] +end + +return m diff --git a/script/vm/node/class.lua b/script/vm/node/class.lua new file mode 100644 index 00000000..ee8b1ca8 --- /dev/null +++ b/script/vm/node/class.lua @@ -0,0 +1,11 @@ +---@class vm.node.class +local mt = {} +mt.__index = mt +mt.type = 'class' + +---@return vm.node.class +return function () + local class = setmetatable({ + }, mt) + return class +end diff --git a/script/vm/node/compiler.lua b/script/vm/node/compiler.lua new file mode 100644 index 00000000..dfe4bc0c --- /dev/null +++ b/script/vm/node/compiler.lua @@ -0,0 +1,190 @@ +local guide = require 'parser.guide' +local util = require 'utility' +local state = require 'vm.state' +local union = require 'vm.node.union' +local localID = require 'vm.local-id' + +---@class parser.object +---@field _compiledNodes boolean +---@field _node vm.node + +---@class vm.node.compiler +local m = {} + +---@class vm.node.cross + +---@alias vm.node parser.object | vm.node.union | vm.node.cross | vm.node.global + +function m.setNode(source, node) + if not node then + return + end + local me = source._node + if not me then + source._node = node + return + end + if me.type == 'union' + or me.type == 'cross' then + me:merge(node) + return + end + source._node = union(me, node) +end + +function m.eachNode(node) + if node.type == 'union' then + return node:eachNode() + end + local first = true + return function () + if first then + first = false + return node + end + return nil + end +end + +local function getReturnOfFunction(func, index) + if not func._returns then + func._returns = util.defaultTable(function () + return { + type = 'function.return', + parent = func, + index = index, + } + end) + end + return m.compileNode(func._returns[index]) +end + +local function getReturn(func, index) + local node = m.compileNode(func) + if not node then + return + end + for cnode in m.eachNode(node) do + if cnode.type == 'function' then + return getReturnOfFunction(cnode, index) + end + end +end + +local function compileByLocalID(source) + local sources = localID.getSources(source) + if not sources then + return + end + for _, src in ipairs(sources) do + if src.value then + m.setNode(source, m.compileNode(src.value)) + end + end +end + +local searchFieldMap = util.switch() + : case 'table' + : call(function (node, key, pushResult) + for _, field in ipairs(node) do + if field.type == 'tablefield' + or field.type == 'tableindex' then + if guide.getKeyName(field) == key then + pushResult(field) + end + end + end + end) + : getMap() + +local function compileByParentNode(source) + local parentNode = m.compileNode(source.node) + if not parentNode then + return + end + local key = guide.getKeyName(source) + local f = searchFieldMap[parentNode.type] + if f then + f(parentNode, key, function (field) + m.setNode(source, m.compileNode(field.value)) + end) + end +end + +local compilerMap = util.switch() + : case 'boolean' + : case 'table' + : case 'integer' + : case 'number' + : case 'string' + : case 'function' + : call(function (source) + m.setNode(source, state.declareLiteral(source)) + end) + : case 'local' + : call(function (source) + if source.value then + m.setNode(source, m.compileNode(source.value)) + end + if source.ref then + for _, ref in ipairs(source.ref) do + if ref.type == 'setlocal' then + m.setNode(source, m.compileNode(ref.value)) + end + end + end + end) + : case 'getlocal' + : call(function (source) + m.setNode(source, m.compileNode(source.node)) + end) + : case 'setfield' + : case 'setmethod' + : case 'setindex' + : call(function (source) + compileByLocalID(source) + end) + : case 'getfield' + : case 'getmethod' + : case 'getindex' + : call(function (source) + compileByLocalID(source) + compileByParentNode(source) + end) + : case 'function.return' + : call(function (source) + local func = source.parent + local index = source.index + if func.returns then + for _, rtn in ipairs(func.returns) do + if rtn[index] then + m.setNode(source, m.compileNode(rtn[index])) + end + end + end + end) + : case 'select' + : call(function (source, value) + local vararg = value.vararg + if vararg.type == 'call' then + m.setNode(source, getReturn(vararg.node, value.sindex)) + end + end) + : getMap() + +---@param source parser.object +---@return vm.node +function m.compileNode(source) + if source._node then + return source._node + end + source._node = false + local compiler = compilerMap[source.type] + if compiler then + compiler(source) + end + state.subscribeLiteral(source, source._node) + return source._node +end + +return m diff --git a/script/vm/node/global.lua b/script/vm/node/global.lua new file mode 100644 index 00000000..499e526b --- /dev/null +++ b/script/vm/node/global.lua @@ -0,0 +1,79 @@ +local util = require 'utility' + +---@class vm.node.global.link +---@field gets parser.object[] +---@field sets parser.object[] + +---@class vm.node.global +---@field links table<uri, vm.node.global.link> +---@field setsCache parser.object[] +---@field getsCache parser.object[] +local mt = {} +mt.__index = mt +mt.type = 'global' +mt.name = '' + +---@param uri uri +---@param source parser.object +function mt:addSet(uri, source) + local link = self.links[uri] + link.sets[#link.sets+1] = source +end + +---@param uri uri +---@param source parser.object +function mt:addGet(uri, source) + local link = self.links[uri] + link.gets[#link.gets+1] = source +end + +---@return parser.object[] +function mt:getSets() + if not self.setsCache then + self.setsCache = {} + for _, link in pairs(self.links) do + for _, source in ipairs(link.sets) do + self.setsCache[#self.setsCache+1] = source + end + end + end + return self.setsCache +end + +---@return parser.object[] +function mt:getGets() + if not self.getsCache then + self.getsCache = {} + for _, link in pairs(self.links) do + for _, source in ipairs(link.gets) do + self.getsCache[#self.getsCache+1] = source + end + end + end + return self.getsCache +end + +---@param uri uri +function mt:dropUri(uri) + self.links[uri] = nil + self.setsCache = nil + self.getsCache = nil +end + +---@return string +function mt:getName() + return self.name +end + +---@return vm.node.global +return function (name) + return setmetatable({ + name = name, + links = util.defaultTable(function () + return { + sets = {}, + gets = {}, + } + end), + }, mt) +end diff --git a/script/vm/node/union.lua b/script/vm/node/union.lua new file mode 100644 index 00000000..a8c917d9 --- /dev/null +++ b/script/vm/node/union.lua @@ -0,0 +1,49 @@ +local state = require 'vm.state' + +---@class vm.node.union +local mt = {} +mt.__index = mt +mt.type = 'union' + +---@param node vm.node +function mt:merge(node) + if not node then + return + end + if node.type == 'union' then + for _, c in ipairs(node) do + self[#self+1] = c + end + else + self[#self+1] = node + end +end + +---@param source parser.object +function mt:subscribeLiteral(source) + for _, c in ipairs(self) do + state.subscribeLiteral(source, c) + if c.type == 'cross' then + c:subscribeLiteral(source) + end + end +end + +function mt:eachNode() + local i = 0 + return function () + i = i + 1 + return self[i] + end +end + +---@param me parser.object +---@param node vm.node +---@return vm.node.union +return function (me, node) + local union = setmetatable({ + [1] = me, + }, mt) + union:merge(node) + return union +end diff --git a/script/vm/state.lua b/script/vm/state.lua new file mode 100644 index 00000000..b0689384 --- /dev/null +++ b/script/vm/state.lua @@ -0,0 +1,80 @@ +local util = require 'utility' +local files = require 'files' +local globalNode = require 'vm.global-node' +local guide = require 'parser.guide' + +---@class vm.state +local m = {} +---@type table<uri, parser.object[]> +m.literals = util.multiTable(2) +---@type table<parser.object, table<parser.object, boolean>> +m.literalSubs = util.multiTable(2, function () + return setmetatable({}, util.MODE_K) +end) +---@type table<parser.object, boolean> +m.allLiterals = {} + +---@param source parser.object +function m.declareLiteral(source) + if m.allLiterals[source] then + return + end + m.allLiterals[source] = true + local uri = guide.getUri(source) + local literals = m.literals[uri] + literals[#literals+1] = source +end + +---@param source parser.object +---@param node vm.node +function m.subscribeLiteral(source, node) + if not node then + return + end + if node.type == 'union' + or node.type == 'cross' then + node:subscribeLiteral(source) + return + end + if not m.allLiterals[source] then + return + end + m.literalSubs[node][source] = true +end + +---@param uri uri +function m.dropUri(uri) + local literals = m.literals[uri] + m.literals[uri] = nil + for _, literal in ipairs(literals) do + m.allLiterals[literal] = nil + local literalSubs = m.literalSubs[literal] + m.literalSubs[literal] = nil + for source in pairs(literalSubs) do + source._node = nil + end + end +end + +for uri in files.eachFile() do + local state = files.getState(uri) + if state then + globalNode.compileAst(state.ast) + end +end + +files.watch(function (ev, uri) + if ev == 'update' then + local state = files.getState(uri) + if state then + globalNode.compileAst(state.ast) + end + end + if ev == 'remove' then + m.dropUri(uri) + globalNode.dropUri(uri) + end +end) + + +return m diff --git a/script/vm/vm.lua b/script/vm/vm.lua index aa18ea73..ff893f24 100644 --- a/script/vm/vm.lua +++ b/script/vm/vm.lua @@ -1,11 +1,8 @@ local guide = require 'parser.guide' -local util = require 'utility' local files = require 'files' local timer = require 'timer' local setmetatable = setmetatable -local running = coroutine.running -local ipairs = ipairs local log = log local xpcall = xpcall local mathHuge = math.huge @@ -51,8 +51,8 @@ end local function testAll() test 'basic' - test 'references' test 'definition' + test 'references' test 'type_inference' test 'hover' test 'completion' diff --git a/test/basic/init.lua b/test/basic/init.lua index 1b698493..8490d51c 100644 --- a/test/basic/init.lua +++ b/test/basic/init.lua @@ -1,2 +1 @@ require 'basic.textmerger' -require 'basic.noder' diff --git a/test/basic/linker.txt b/test/basic/linker.txt deleted file mode 100644 index ea3ba180..00000000 --- a/test/basic/linker.txt +++ /dev/null @@ -1,141 +0,0 @@ -ast -> linkers = { - ['g|"X"|"Y"|"Z"'] = {src1, src2, src3}, - ['g|"X"|"Y"'] = {src4, src5, src6}, - ['g|"X"'] = {src7, src8, src9}, - ['l|7'] = {src10}, - ['l|7|"x"'] = {src11}, - ['l|11|"k"'] = {src12}, -} - -```lua -x.y.<?z?> = <!f!> - -<?g?> = x.y.z - -t.<!z!> = 1 -x.y = t - -x = { - y = { - <!z!> = 1 - } -} -``` - -expect: 'l|x|y|z' -forward: 'l|x|y|z' -> f -backward: 'l|x|y|z' -> g -last: 'l|x|y' + 'z' - -expect: 'l|x|y' + '|z' -forward: 'l|t' + '|z' -> 'l|t|z' -> t.z -backward: nil -last: 'l|x' + '|y|z' - -expect: 'l|x' + '|y|z' -forward: 'l|0' + '|y|z' -> 'l|0|y|z' -backward: nil -last: nil - -expect: 'l|0|y|z' -forward: nil -backward: nil -last: 'l|0|y' + '|z' - -expect: 'l|0|y' + '|z' -forward: 'l|1'+ '|z' -> 'l|1|z' -> field z -backward: nil -last: 'l|0' + '|y|z' - - -```lua -a = { - b = { - <?c?> = 1, - } -} - -print(a.b.<!c!>) -``` - -expect: 't|3|c' -forward: nil -backward: nil -last: 't|3' + '|c' - -expect: 't|3' + '|c' -forward: nil -backward: 't|2|b' + '|c' -last: nil - -expect: 't|2|b|c' -forward: nil -backward: 't|2|b' + '|c' -last: nil - -```lua ----@return <?A?> -local function f() -end - -local <!x!> = f() -``` - -'d|A' -'f|1|#1' -'f|1' + '|#1' -'l|1' + '|#1' -'s|1' + '|#1' - -```lua ----@generic T ----@param a T ----@return T -local function f(a) end - -local <?c?> - -local <!v!> = f(c) -``` - -'l1' -'l2|@1' -'f|1|@1' -'f|1|#1' - -``` ----@generic T ----@param p T ----@return T -local function f(p) end - -local <?r?> = f(<!k!>) -``` - -l:r -s:1#1 call -l:f#1 call -f:1#1 call -> f:1&T = l:k -l:f@1 --> 从保存的call信息里找到 f:1&T = l:k -l:k - - - -``` ----@generic T, V ----@param p T ----@return fun(V):T, V -local function f(p) end - -local f2 = f(<!k!>) -local <?r?> = f2() -``` - -l:r -s:2|#1 call1 -l:f2|#1 call1 -f:2|#1 call1 -s:1#1|#1 call2 -f:1#1|#1 call2 -> f:1&T = l:k -dfun:1|#1 -dn:V -> f:1&T = l:k diff --git a/test/basic/noder.lua b/test/basic/noder.lua deleted file mode 100644 index 49585ee8..00000000 --- a/test/basic/noder.lua +++ /dev/null @@ -1,143 +0,0 @@ -local noder = require 'core.noder' -local files = require 'files' -local util = require 'utility' -local guide = require 'parser.guide' -local catch = require 'catch' - -local function getSource(pos) - local ast = files.getState('') - return guide.eachSourceContain(ast.ast, pos, function (source) - if source.type == 'local' - or source.type == 'getlocal' - or source.type == 'setlocal' - or source.type == 'setglobal' - or source.type == 'getglobal' - or source.type == 'setfield' - or source.type == 'getfield' - or source.type == 'setmethod' - or source.type == 'getmethod' - or source.type == 'tablefield' - or source.type == 'setindex' - or source.type == 'getindex' - or source.type == 'tableindex' - or source.type == 'label' - or source.type == 'goto' then - return source - end - end) -end - -local CARE = {} -local function TEST(script) - return function (expect) - local newScript, catched = catch(script, '?') - files.setText('', newScript) - local source = getSource(catched['?'][1][1]) - assert(source) - local result = { - id = noder.getID(source), - } - - expect['id'] = expect['id']:gsub('|', '\x1F') - - for key in pairs(CARE) do - assert(result[key] == expect[key]) - end - files.remove('') - end -end - -CARE['id'] = true -TEST [[ -local <?x?> -]] { - id = 'l:6', -} - -TEST [[ -local x -print(<?x?>) -]] { - id = 'l:6', -} - -TEST [[ -local x -<?x?> = 1 -]] { - id = 'l:6', -} - -TEST [[ -print(<?X?>) -]] { - id = 'g:.X', -} - -TEST [[ -print(<?X?>) -]] { - id = 'g:.X', -} - -TEST [[ -local x -print(x.y.<?z?>) -]] { - id = 'l:6|.y|.z', -} - -TEST [[ -local x -function x:<?f?>() end -]] { - id = 'l:6|.f', -} - -TEST [[ -print(X.Y.<?Z?>) -]] { - id = 'g:.X|.Y|.Z', -} - -TEST [[ -function x:<?f?>() end -]] { - id = 'g:.x|.f', -} - -TEST [[ -{ - <?x?> = 1, -} -]] { - id = 't:0|.x', -} - -TEST [[ -return <?X?> -]] { - id = 'g:.X', -} - -TEST [[ -function f() - return <?X?> -end -]] { - id = 'g:.X', -} - -TEST [[ -::<?label?>:: -goto label -]] { - id = 'l:2', -} - -TEST [[ -::label:: -goto <?label?> -]] { - id = 'l:2', -} diff --git a/test/definition/field.lua b/test/definition/field.lua new file mode 100644 index 00000000..5b5b67f9 --- /dev/null +++ b/test/definition/field.lua @@ -0,0 +1,23 @@ +TEST [[ +X.<!y!> = 1 + +local t = X + +print(t.<?y?>) +]] + +TEST [[ +X.x.<!y!> = 1 + +local t = X.x + +print(t.<?y?>) +]] + +TEST [[ +X.x.<!y!> = 1 + +local t = X + +print(t.x.<?y?>) +]] diff --git a/test/definition/init.lua b/test/definition/init.lua index fc2903c1..08b97442 100644 --- a/test/definition/init.lua +++ b/test/definition/init.lua @@ -44,6 +44,7 @@ end require 'definition.local' require 'definition.set' +require 'definition.field' require 'definition.arg' require 'definition.function' require 'definition.table' diff --git a/test/definition/table.lua b/test/definition/table.lua index 61e8746d..66e71b0c 100644 --- a/test/definition/table.lua +++ b/test/definition/table.lua @@ -134,32 +134,44 @@ local y = { t.<?insert?>() ]] + +TEST [[ +local x +x.y.<!z!> = 1 +print(x.y.<?z?>) +]] + + +TEST [[ +local x +x.y = { + <!z!> = 1 +} +print(x.y.<?z?>) +]] + +TEST [[ +local x = { + y = { + <!z!> = 1 + } +} +print(x.y.<?z?>) +]] + TEST [[ local function f() - local t = {} - t.field1 = { + local t = { <!x!> = 1, - y = 1, - z = 1, - } - t.field2 = { - x = 1, - y = 1, - z = 1, - } - t.field3 = { - x = 1, - y = 1, - z = 1, } return t end local t = f() -t.field1.<?x?> +t.<?x?> ]] -TEST [[ -local t = { <!a!> } - -print(t[<?1?>]) -]] +--TEST [[ +--local t = { <!a!> } +-- +--print(t[<?1?>]) +--]] |