diff options
Diffstat (limited to 'server/src/matcher')
-rw-r--r-- | server/src/matcher/completion.lua | 501 | ||||
-rw-r--r-- | server/src/matcher/definition.lua | 88 | ||||
-rw-r--r-- | server/src/matcher/diagnostics.lua | 269 | ||||
-rw-r--r-- | server/src/matcher/document_symbol.lua | 77 | ||||
-rw-r--r-- | server/src/matcher/env.lua | 139 | ||||
-rw-r--r-- | server/src/matcher/find_lib.lua | 47 | ||||
-rw-r--r-- | server/src/matcher/find_result.lua | 47 | ||||
-rw-r--r-- | server/src/matcher/hover.lua | 482 | ||||
-rw-r--r-- | server/src/matcher/implementation.lua | 88 | ||||
-rw-r--r-- | server/src/matcher/init.lua | 16 | ||||
-rw-r--r-- | server/src/matcher/library.lua | 177 | ||||
-rw-r--r-- | server/src/matcher/references.lua | 38 | ||||
-rw-r--r-- | server/src/matcher/rename.lua | 51 | ||||
-rw-r--r-- | server/src/matcher/signature.lua | 66 | ||||
-rw-r--r-- | server/src/matcher/vm.lua | 1501 |
15 files changed, 0 insertions, 3587 deletions
diff --git a/server/src/matcher/completion.lua b/server/src/matcher/completion.lua deleted file mode 100644 index 508c0320..00000000 --- a/server/src/matcher/completion.lua +++ /dev/null @@ -1,501 +0,0 @@ -local findResult = require 'matcher.find_result' -local hover = require 'matcher.hover' - -local CompletionItemKind = { - Text = 1, - Method = 2, - Function = 3, - Constructor = 4, - Field = 5, - Variable = 6, - Class = 7, - Interface = 8, - Module = 9, - Property = 10, - Unit = 11, - Value = 12, - Enum = 13, - Keyword = 14, - Snippet = 15, - Color = 16, - File = 17, - Reference = 18, - Folder = 19, - EnumMember = 20, - Constant = 21, - Struct = 22, - Event = 23, - Operator = 24, - TypeParameter = 25, -} - -local function matchKey(me, other) - if me == other then - return false - end - if me == '' then - return true - end - if #me > #other then - return false - end - local lMe = me:lower() - local lOther = other:lower() - if lMe:sub(1, 1) ~= lOther:sub(1, 1) then - return false - end - if lMe == lOther:sub(1, #lMe) then - return true - end - local used = { - [1] = true, - } - local cur = 2 - local lookup - local researched - for i = 2, #lMe do - local c = lMe:sub(i, i) - -- 1. 看当前字符是否匹配 - if c == lOther:sub(cur, cur) then - used[cur] = true - goto NEXT - end - -- 2. 看前一个字符是否匹配 - if not used[cur-1] then - if c == lOther:sub(cur-1, cur-1) then - used[cur-1] = true - goto NEXT - end - end - -- 3. 向后找这个字 - lookup = lOther:find(c, cur+1, true) - if lookup then - cur = lookup - used[cur] = true - goto NEXT - end - - -- 4. 重新搜索整个字符串,但是只允许1次,否则失败.如果找不到也失败 - if researched then - return false - else - researched = true - for j = 2, cur - 2 do - if c == lOther:sub(j, j) then - used[j] = true - goto NEXT - end - end - return false - end - -- 5. 找到下一个可用的字,如果超出长度就算成功 - ::NEXT:: - repeat - cur = cur + 1 - until not used[cur] - if cur > #lOther then - break - end - end - return true -end - -local function searchLocals(vm, pos, name, callback) - for _, loc in ipairs(vm.results.locals) do - if loc.source.start == 0 then - goto CONTINUE - end - if loc.source.start <= pos and loc.close >= pos then - if matchKey(name, loc.key) then - callback(loc) - end - end - ::CONTINUE:: - end -end - -local function searchFields(name, parent, object, callback) - if not parent or not parent.value or not parent.value.child then - return - end - for key, field in pairs(parent.value.child) do - if type(key) ~= 'string' then - goto CONTINUE - end - if object then - if not field.value or field.value.type ~= 'function' then - goto CONTINUE - end - end - if type(name) == 'string' and matchKey(name, key) then - callback(field) - end - ::CONTINUE:: - end -end - -local KEYS = {'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 'until', 'while', 'toclose'} -local function searchKeyWords(name, callback) - for _, key in ipairs(KEYS) do - if matchKey(name, key) then - callback(key) - end - end -end - -local function getKind(var, default) - local value = var.value - if default == CompletionItemKind.Variable then - if value.type == 'function' then - return CompletionItemKind.Function - end - end - if default == CompletionItemKind.Field then - local tp = type(value.value) - if tp == 'number' or tp == 'integer' or tp == 'string' then - return CompletionItemKind.Enum - end - if value.type == 'function' then - if var.parent and var.parent.value and var.parent.value.ENV ~= true then - return CompletionItemKind.Method - else - return CompletionItemKind.Function - end - end - end - return default -end - -local function getDetail(var) - local tp = type(var.value.value) - if tp == 'boolean' then - return ('= %q'):format(var.value.value) - elseif tp == 'number' then - if math.type(var.value.value) == 'integer' then - return ('= %q'):format(var.value.value) - else - local str = ('= %.10f'):format(var.value.value) - local dot = str:find('.', 1, true) - local suffix = str:find('[0]+$', dot+2) - if suffix then - return str:sub(1, suffix-1) - else - return str - end - end - elseif tp == 'string' then - return ('= %q'):format(var.value.value) - end - return nil -end - -local function getDocument(var, source) - if var.value.type == 'function' then - return { - kind = 'markdown', - value = hover(var, source), - } - end - return nil -end - -local function searchAsLocal(vm, pos, result, callback) - searchFields(result.key, vm.results.locals[1], nil, function (var) - callback(var, CompletionItemKind.Variable) - end) - - -- 支持 local function - if matchKey(result.key, 'function') then - callback('function', CompletionItemKind.Keyword) - end -end - -local function searchAsArg(vm, pos, result, callback) - searchFields(result.key, vm.results.locals[1], nil, function (var) - if var.value.lib then - return - end - callback(var, CompletionItemKind.Variable) - end) -end - -local function searchAsGlobal(vm, pos, result, callback) - if result.key == '' then - return - end - searchLocals(vm, pos, result.key, function (var) - callback(var, CompletionItemKind.Variable) - end) - searchFields(result.key, vm.results.locals[1], nil, function (var) - callback(var, CompletionItemKind.Field) - end) - searchKeyWords(result.key, function (name) - callback(name, CompletionItemKind.Keyword) - end) -end - -local function searchAsSuffix(result, callback) - searchFields(result.key, result.parent, result.source.object, function (var) - callback(var, CompletionItemKind.Field) - end) -end - -local function searchInArg(vm, inCall, inString, callback) - local lib = inCall.func.lib - if not lib then - return - end - - -- require列举出可以引用到的文件 - if lib.special == 'require' then - if not vm.lsp or not vm.lsp.workspace then - return - end - local results = vm.lsp.workspace:matchPath(vm.uri, inString[1]) - if not results then - return - end - for _, v in ipairs(results) do - if v ~= inString[1] then - callback(v, CompletionItemKind.File) - end - end - end - - -- 其他库函数,根据参数位置找枚举值 - if lib.args and lib.enums then - local arg = lib.args[inCall.select] - local name = arg and arg.name - for _, enum in ipairs(lib.enums) do - if enum.name == name and enum.enum then - if inString then - callback(enum.enum, CompletionItemKind.EnumMember, { - documentation = enum.description - }) - else - callback(('%q'):format(enum.enum), CompletionItemKind.EnumMember, { - documentation = enum.description - }) - end - end - end - end -end - -local function searchAsIndex(vm, pos, result, callback) - searchLocals(vm, pos, result.key, function (var) - callback(var, CompletionItemKind.Variable) - end) - for _, index in ipairs(vm.results.indexs) do - if matchKey(result.key, index) then - callback(index, CompletionItemKind.Property) - end - end - searchFields(result.key, vm.results.locals[1], nil, function (var) - callback(var, CompletionItemKind.Field) - end) -end - -local function findClosePos(vm, pos) - local curDis = math.maxinteger - local parent = nil - local function found(object, source) - local dis = pos - source.finish - if dis > 1 and dis < curDis then - curDis = dis - parent = object - end - end - for sources, object in pairs(vm.results.sources) do - if sources.type == 'multi-source' then - for _, source in ipairs(sources) do - if source.type ~= 'simple' then - found(object, source) - end - end - else - found(object, sources) - end - end - if not parent then - return nil - end - if parent.type ~= 'local' and parent.type ~= 'field' then - return nil - end - -- 造个假的 DirtyName - local source = { - type = 'name', - start = pos, - finish = pos, - [1] = '', - } - local result = { - type = 'field', - parent = parent, - key = '', - source = source, - } - return result, source -end - -local function isContainPos(obj, pos) - if obj.start <= pos and obj.finish + 1 >= pos then - return true - end - return false -end - -local function findString(vm, pos) - for _, source in ipairs(vm.results.strings) do - if isContainPos(source, pos) then - return source - end - end - return nil -end - -local function findArgCount(args, pos) - for i, arg in ipairs(args) do - if isContainPos(arg, pos) then - return i - end - end - return #args + 1 -end - --- 找出范围包含pos的call -local function findCall(vm, pos) - local results = {} - for _, call in ipairs(vm.results.calls) do - if isContainPos(call.args, pos) then - local n = findArgCount(call.args, pos) - local var = vm.results.sources[call.lastObj] - if var then - results[#results+1] = { - func = call.func, - var = var, - source = call.lastObj, - select = n, - args = call.args, - } - end - end - end - if #results == 0 then - return nil - end - -- 可能处于 'func1(func2(' 的嵌套中,因此距离越远的函数层级越低 - table.sort(results, function (a, b) - return a.args.start < b.args.start - end) - return results[#results] -end - -local function makeList(source) - local list = {} - local mark = {} - local function callback(var, defualt, data) - local key - if type(var) == 'string' then - key = var - else - key = var.key - end - if mark[key] then - return - end - mark[key] = true - data = data or {} - list[#list+1] = data - if var == key then - data.label = var - data.kind = defualt - else - data.label = var.key - data.kind = getKind(var, defualt) - data.detail = data.detail or getDetail(var) - data.documentation = data.documentation or getDocument(var, source) - end - end - return list, callback -end - -local function searchInResult(result, source, vm, pos, callback) - if result.type == 'local' then - if source.isArg then - searchAsArg(vm, pos, result, callback) - elseif source.isLocal then - searchAsLocal(vm, pos, result, callback) - else - searchAsGlobal(vm, pos, result, callback) - end - elseif result.type == 'field' then - if source.isIndex then - searchAsIndex(vm, pos, result, callback) - elseif result.parent and result.parent.value and result.parent.value.ENV == true then - searchAsGlobal(vm, pos, result, callback) - else - searchAsSuffix(result, callback) - end - end -end - -local function searchSpecial(vm, pos, callback) - -- 尝试 # - local result, source = findResult(vm, pos, 2) - if source and source.type == 'index' - and result.source and result.source.op == '#' - then - local name = {} - local var = result - while true do - var = var.parent - if not var then - break - end - if var.value and var.value.ENV then - break - end - local key = var.key - if type(key) ~= 'string' or key == '' then - return - end - table.insert(name, 1, key) - end - local label = table.concat(name, '.') .. '+1' - callback(label, CompletionItemKind.Snippet, { - textEdit = { - start = result.source.start + 1, - finish = source.finish, - newText = ('%s] = '):format(label), - } - }) - end -end - -return function (vm, pos) - local result, source = findResult(vm, pos) - if not result then - result, source = findClosePos(vm, pos) - end - - if not result then - return nil - end - - local list, callback = makeList(source) - local inCall = findCall(vm, pos) - local inString = findString(vm, pos) - if inCall then - searchInArg(vm, inCall, inString, callback) - end - searchSpecial(vm, pos, callback) - if not inString then - searchInResult(result, source, vm, pos, callback) - end - if #list == 0 then - return nil - end - return list -end diff --git a/server/src/matcher/definition.lua b/server/src/matcher/definition.lua deleted file mode 100644 index 3ca27b89..00000000 --- a/server/src/matcher/definition.lua +++ /dev/null @@ -1,88 +0,0 @@ -local function parseResultAcrossUri(positions, vm, result) - -- 跨越文件时,遍历的是值的绑定信息 - for _, info in ipairs(result.value) do - if info.type == 'set' and info.source.uri == result.value.uri then - positions[1] = { - info.source.start, - info.source.finish, - info.source.uri, - } - end - end - if #positions == 0 then - for _, info in ipairs(result.value) do - if info.type == 'return' and info.source.uri == result.value.uri then - positions[1] = { - info.source.start, - info.source.finish, - info.source.uri, - } - end - end - end - if #positions == 0 then - positions[1] = { - 0, 0, - result.value.uri, - } - end -end - -local function parseResult(vm, result) - local positions = {} - local tp = result.type - if tp == 'local' then - if result.value.uri ~= vm.uri then - parseResultAcrossUri(positions, vm, result) - else - for _, info in ipairs(result) do - if info.type == 'local' then - positions[#positions+1] = { - info.source.start, - info.source.finish, - info.source.uri, - } - end - end - end - elseif tp == 'field' then - if result.value.uri ~= vm.uri then - parseResultAcrossUri(positions, vm, result) - else - for _, info in ipairs(result) do - if info.type == 'set' then - positions[#positions+1] = { - info.source.start, - info.source.finish, - info.source.uri, - } - end - end - end - elseif tp == 'label' then - for _, info in ipairs(result) do - if info.type == 'set' then - positions[#positions+1] = { - info.source.start, - info.source.finish, - } - end - end - elseif tp == 'string' then - -- require 'XXX' 专用 - positions[#positions+1] = { - 0, - 0, - result.uri, - } - end - return positions -end - -return function (vm, result) - if not result then - return nil - end - local positions = parseResult(vm, result) - return positions -end diff --git a/server/src/matcher/diagnostics.lua b/server/src/matcher/diagnostics.lua deleted file mode 100644 index a6672c0b..00000000 --- a/server/src/matcher/diagnostics.lua +++ /dev/null @@ -1,269 +0,0 @@ -local lang = require 'language' - -local DiagnosticSeverity = { - Error = 1, - Warning = 2, - Information = 3, - Hint = 4, -} - -local function searchUnusedLocals(results, callback) - for _, var in ipairs(results.locals) do - if var.key == 'self' - or var.key == '_' - or var.key == '_ENV' - or var.key == '' - then - goto NEXT_VAR - end - for _, info in ipairs(var) do - if info.type == 'get' then - goto NEXT_VAR - end - if info.type == 'local' then - if info.source.start == 0 then - goto NEXT_VAR - end - end - end - callback(var.source.start, var.source.finish, var.key) - ::NEXT_VAR:: - end -end - -local function searchUndefinedGlobal(results, callback) - local env = results.locals[1] - for index, field in pairs(env.value.child) do - if field.value.lib then - goto NEXT_VAR - end - if type(index) ~= 'string' then - goto NEXT_VAR - end - local lIndex = index:lower() - if lIndex == 'log' or lIndex == 'arg' or lIndex == '' then - goto NEXT_VAR - end - if not index:find '%l' then - goto NEXT_VAR - end - for _, info in ipairs(field) do - if info.type == 'set' then - goto NEXT_VAR - end - end - for _, info in ipairs(field) do - if info.type == 'get' then - callback(info.source.start, info.source.finish, tostring(index)) - end - end - ::NEXT_VAR:: - end -end - -local function searchUnusedLabel(results, callback) - for _, label in ipairs(results.labels) do - for _, info in ipairs(label) do - if info.type == 'goto' then - goto NEXT_LABEL - end - end - for _, info in ipairs(label) do - if info.type == 'set' then - callback(info.source.start, info.source.finish, label.key) - end - end - ::NEXT_LABEL:: - end -end - -local function isContainPos(obj, start, finish) - if obj.start <= start and obj.finish + 1 >= finish then - return true - end - return false -end - -local function isInString(vm, start, finish) - for _, source in ipairs(vm.results.strings) do - if isContainPos(source, start, finish) then - return true - end - end - return false -end - -local function searchSpaces(vm, lines, callback) - for i = 1, #lines do - local line = lines:line(i) - - if line:find '^[ \t]+$' then - local start, finish = lines:range(i) - if isInString(vm, start, finish) then - goto NEXT_LINE - end - callback(start, finish, lang.script.DIAG_LINE_ONLY_SPACE) - goto NEXT_LINE - end - - local pos = line:find '[ \t]+$' - if pos then - local start, finish = lines:range(i) - start = start + pos - 1 - if isInString(vm, start, finish) then - goto NEXT_LINE - end - callback(start, finish, lang.script.DIAG_LINE_POST_SPACE) - goto NEXT_LINE - end - ::NEXT_LINE:: - end -end - -local function searchRedefinition(results, uri, callback) - local used = {} - for _, var in ipairs(results.locals) do - if var.key == '_' - or var.key == '_ENV' - or var.key == '' - then - goto NEXT_VAR - end - local shadow = var.shadow - if not shadow then - goto NEXT_VAR - end - if used[shadow] then - goto NEXT_VAR - end - used[shadow] = true - -- 如果多次重定义,则不再警告 - if #shadow >= 4 then - goto NEXT_VAR - end - local related = {} - for i = 1, #shadow do - related[i] = { - start = shadow[i].source.start, - finish = shadow[i].source.finish, - uri = uri, - } - end - for i = 2, #shadow do - callback(shadow[i].source.start, shadow[i].source.finish, shadow[i].key, related) - end - ::NEXT_VAR:: - end -end - -local function searchNewLineCall(results, lines, callback) - for _, call in ipairs(results.calls) do - if not call.nextObj then - goto NEXT_CALL - end - if not call.lastObj.start then - goto NEXT_CALL - end - local callline = lines:rowcol(call.args.start) - local lastline = lines:rowcol(call.lastObj.finish) - if callline > lastline then - callback(call.args.start, call.args.finish) - end - ::NEXT_CALL:: - end -end - -local function searchRedundantParameters(results, callback) - for _, call in ipairs(results.calls) do - if not call.func.built then - goto NEXT_CALL - end - if call.func.hasDots then - goto NEXT_CALL - end - if not call.func.args then - return - end - local max = #call.func.args - local passed = #call.args - for i = max + 1, passed do - local source = call.args[i] - if source.start then - callback(source.start, source.finish, max, passed) - else - log.error('No start: ', table.dump(source)) - end - end - ::NEXT_CALL:: - end -end - -return function (vm, lines, uri) - local datas = {} - local results = vm.results - -- 未使用的局部变量 - searchUnusedLocals(results, function (start, finish, key) - datas[#datas+1] = { - start = start, - finish = finish, - level = DiagnosticSeverity.Information, - message = lang.script('DIAG_UNUSED_LOCAL', key), - } - end) - -- 读取未定义全局变量 - searchUndefinedGlobal(results, function (start, finish, key) - datas[#datas+1] = { - start = start, - finish = finish, - level = DiagnosticSeverity.Warning, - message = lang.script('DIAG_UNDEFINED_GLOBAL', key), - } - end) - -- 未使用的Label - searchUnusedLabel(results, function (start, finish, key) - datas[#datas+1] = { - start = start, - finish = finish, - level =DiagnosticSeverity.Information, - message = lang.script('DIAG_UNUSED_LABEL', key) - } - end) - -- 只有空格与制表符的行,以及后置空格 - searchSpaces(vm, lines, function (start, finish, message) - datas[#datas+1] = { - start = start, - finish = finish, - level = DiagnosticSeverity.Information, - message = message, - } - end) - -- 重定义局部变量 - searchRedefinition(results, uri, function (start, finish, key, related) - datas[#datas+1] = { - start = start, - finish = finish, - level = DiagnosticSeverity.Information, - message = lang.script('DIAG_REDEFINED_LOCAL', key), - related = related, - } - end) - -- 以括号开始的一行(可能被误解析为了上一行的call) - searchNewLineCall(results, lines, function (start, finish) - datas[#datas+1] = { - start = start, - finish = finish, - level = DiagnosticSeverity.Information, - message = lang.script.DIAG_PREVIOUS_CALL, - } - end) - -- 调用函数时的参数数量是否超过函数的接收数量 - searchRedundantParameters(results, function (start, finish, max, passed) - datas[#datas+1] = { - start = start, - finish = finish, - level = DiagnosticSeverity.Warning, - message = lang.script('DIAG_OVER_MAX_ARGS', max, passed), - } - end) - return datas -end diff --git a/server/src/matcher/document_symbol.lua b/server/src/matcher/document_symbol.lua deleted file mode 100644 index b6802c93..00000000 --- a/server/src/matcher/document_symbol.lua +++ /dev/null @@ -1,77 +0,0 @@ -local hover = require 'matcher.hover' - -local SymbolKind = { - File = 1, - Module = 2, - Namespace = 3, - Package = 4, - Class = 5, - Method = 6, - Property = 7, - Field = 8, - Constructor = 9, - Enum = 10, - Interface = 11, - Function = 12, - Variable = 13, - Constant = 14, - String = 15, - Number = 16, - Boolean = 17, - Array = 18, - Object = 19, - Key = 20, - Null = 21, - EnumMember = 22, - Struct = 23, - Event = 24, - Operator = 25, - TypeParameter = 26, -} - -local function buildFunc(vm, func, nextFunction, nextFinish) - local source = func.source - local var = vm.results.sources[source.name] - if not var then - return - end - local hvr = hover(var, source.name) - if not hvr then - return - end - return { - name = hvr.name, - detail = hvr.label, - kind = SymbolKind.Function, - range = { source.start, source.finish }, - selectionRange = { source.name.start, source.name.finish }, - } -end - -return function (vm) - local i = 0 - local function nextFunction() - i = i + 1 - local func = vm.results.funcs[i] - return func - end - - local function nextFinish() - local func = vm.results.funcs[i+1] - if not func then - return 0 - end - return func.source.finish - end - - local symbols = {} - while true do - local func = nextFunction() - if not func then - break - end - symbols[1] = buildFunc(vm, func, nextFunction, nextFinish) - end - - return symbols -end diff --git a/server/src/matcher/env.lua b/server/src/matcher/env.lua deleted file mode 100644 index ada26145..00000000 --- a/server/src/matcher/env.lua +++ /dev/null @@ -1,139 +0,0 @@ -local setmetatable = setmetatable -local pairs = pairs -local type = type -local table_sort = table.sort - -return function (root) - local env = {root} - local is_table = {} - for key, value in pairs(root) do - if type(value) == 'table' then - is_table[key] = true - end - end - root._next = nil - root._cut = {} - - local mt = { _env = env } - function mt:push() - env[#env+1] = { _next = env[#env], _cut = {} } - end - function mt:pop() - env[#env] = nil - end - function mt:cut(key) - env[#env]._cut[key] = true - end - function mt:__index(key) - local origin = env[#env] - if is_table[key] then - return setmetatable({}, { - __index = function (_, ckey) - local o = origin - while o do - local t = o[key] - if t and t[ckey] ~= nil then - return t[ckey] - end - o = not o._cut[key] and o._next - end - end, - __newindex = function (_, ckey, value) - local o = origin - if not o[key] then - o[key] = {} - end - o[key][ckey] = value - end, - __pairs = function () - local o = origin - local tbl = {} - while o do - local t = o[key] - if t then - for k, v in pairs(t) do - if tbl[k] == nil then - tbl[k] = v - end - end - end - o = not o._cut[key] and o._next - end - return next, tbl - end, - }) - else - local o = origin - while o do - if o[key] ~= nil then - return o[key] - end - o = not o._cut[key] and o._next - end - end - end - function mt:__newindex(key, value) - local o = env[#env] - if is_table[key] then - if type(o[key]) ~= 'table' then - o[key] = {} - end - if type(value) == 'table' then - for k, v in pairs(value) do - o[key][k] = v - end - else - error(('[env.%s] should be table, got [%s]'):format(key, value)) - end - else - o[key] = value - end - end - function mt:__pairs() - local keys = {} - local cuted = {} - local result = {} - local o = env[#env] - while true do - for key in pairs(o._cut) do - cuted[key] = true - end - for key, value in pairs(o) do - if key == '_cut' or key == '_next' then - goto CONTINUE - end - if cuted[key] then - goto CONTINUE - end - if result[key] == nil then - keys[#keys+1] = key - if is_table[key] then - result[key] = {} - else - result[key] = value - end - end - if is_table[key] then - for k, v in pairs(value) do - if result[key][k] == nil then - result[key][k] = v - end - end - end - ::CONTINUE:: - end - o = o._next - if not o then - break - end - end - table_sort(keys) - local i = 0 - return function () - i = i + 1 - local k = keys[i] - return k, result[k] - end - end - return setmetatable(mt, mt) -end diff --git a/server/src/matcher/find_lib.lua b/server/src/matcher/find_lib.lua deleted file mode 100644 index c4d5bff3..00000000 --- a/server/src/matcher/find_lib.lua +++ /dev/null @@ -1,47 +0,0 @@ -local function findLib(var) - if var.type ~= 'local' and var.type ~= 'field' then - return nil - end - local value = var.value - local lib = value.lib - if not lib then - return nil - end - if lib.parent then - local res - for _, parent in ipairs(lib.parent) do - if parent.type == value.parentType then - res = parent - end - end - if not res then - res = lib.parent[1] - end - if res.type == 'object' then - local fullKey = ('*%s:%s'):format(res.nick or res.name, lib.name) - return lib, fullKey, true - else - local fullKey = ('%s.%s'):format(res.nick or res.name, lib.name) - return lib, fullKey, false - end - else - local res - if not lib.source then - return lib, lib.nick or lib.name, false - end - for _, source in ipairs(lib.source) do - if source.type == value.parentType then - res = source - end - end - if not res then - return lib, lib.nick or lib.name, false - end - return lib, res.nick or res.name or lib.nick or lib.name, false - end -end - -return function (var) - local lib, fullKey, oo = findLib(var) - return lib, fullKey, oo -end diff --git a/server/src/matcher/find_result.lua b/server/src/matcher/find_result.lua deleted file mode 100644 index ac8dc3f4..00000000 --- a/server/src/matcher/find_result.lua +++ /dev/null @@ -1,47 +0,0 @@ -local function isContainPos(obj, pos) - if obj.start <= pos and obj.finish + 1 >= pos then - return true - end - return false -end - -local function findAtPos(results, pos, level) - local res = {} - for sources, object in pairs(results.sources) do - if sources.type == 'multi-source' then - for _, source in ipairs(sources) do - if source.type ~= 'simple' and isContainPos(source, pos) then - res[#res+1] = { - object = object, - source = source, - range = source.finish - source.start, - } - end - end - else - local source = sources - if source.type ~= 'simple' and isContainPos(source, pos) then - res[#res+1] = { - object = object, - source = source, - range = source.finish - source.start, - } - end - end - end - if #res == 0 then - return nil - end - table.sort(res, function (a, b) - return a.range < b.range - end) - local data = res[level or 1] - if not data then - return nil - end - return data.object, data.source -end - -return function (vm, pos, level) - return findAtPos(vm.results, pos, level) -end diff --git a/server/src/matcher/hover.lua b/server/src/matcher/hover.lua deleted file mode 100644 index 5239f3b8..00000000 --- a/server/src/matcher/hover.lua +++ /dev/null @@ -1,482 +0,0 @@ -local findLib = require 'matcher.find_lib' - -local OriginTypes = { - ['any'] = true, - ['nil'] = true, - ['integer'] = true, - ['number'] = true, - ['boolean'] = true, - ['string'] = true, - ['thread'] = true, - ['userdata'] = true, - ['table'] = true, - ['function'] = true, -} - -local function buildLibArgs(lib, oo, select) - if not lib.args then - return '' - end - local start - if oo then - start = 2 - if select then - select = select + 1 - end - else - start = 1 - end - local strs = {} - local argLabel - for i = start, #lib.args do - local arg = lib.args[i] - if arg.optional then - if i > start then - strs[#strs+1] = ' [' - else - strs[#strs+1] = '[' - end - end - if i > start then - strs[#strs+1] = ', ' - end - - local argStr = {} - if arg.name then - argStr[#argStr+1] = ('%s: '):format(arg.name) - end - if type(arg.type) == 'table' then - argStr[#argStr+1] = table.concat(arg.type, '/') - else - argStr[#argStr+1] = arg.type or 'any' - end - if arg.default then - argStr[#argStr+1] = ('(%q)'):format(arg.default) - end - - for _, str in ipairs(argStr) do - strs[#strs+1] = str - end - if arg.optional == 'self' then - strs[#strs+1] = ']' - end - if i == select then - argLabel = table.concat(argStr) - end - end - for _, arg in ipairs(lib.args) do - if arg.optional == 'after' then - strs[#strs+1] = ']' - end - end - return table.concat(strs), argLabel -end - -local function buildLibReturns(lib) - if not lib.returns then - return '' - end - local strs = {} - for i, rtn in ipairs(lib.returns) do - if rtn.optional then - if i > 1 then - strs[#strs+1] = ' [' - else - strs[#strs+1] = '[' - end - end - if i > 1 then - strs[#strs+1] = ', ' - end - if rtn.name then - strs[#strs+1] = ('%s: '):format(rtn.name) - end - if type(rtn.type) == 'table' then - strs[#strs+1] = table.concat(rtn.type, '/') - else - strs[#strs+1] = rtn.type or 'any' - end - if rtn.default then - strs[#strs+1] = ('(%q)'):format(rtn.default) - end - if rtn.optional == 'self' then - strs[#strs+1] = ']' - end - end - for _, rtn in ipairs(lib.returns) do - if rtn.optional == 'after' then - strs[#strs+1] = ']' - end - end - return '\n -> ' .. table.concat(strs) -end - -local function buildEnum(lib) - if not lib.enums then - return '' - end - local container = table.container() - for _, enum in ipairs(lib.enums) do - if not enum.name or (not enum.enum and not enum.code) then - goto NEXT_ENUM - end - if not container[enum.name] then - container[enum.name] = {} - if lib.args then - for _, arg in ipairs(lib.args) do - if arg.name == enum.name then - container[enum.name].type = arg.type - break - end - end - end - if lib.returns then - for _, rtn in ipairs(lib.returns) do - if rtn.name == enum.name then - container[enum.name].type = rtn.type - break - end - end - end - end - table.insert(container[enum.name], enum) - ::NEXT_ENUM:: - end - local strs = {} - for name, enums in pairs(container) do - local tp - if type(enums.type) == 'table' then - tp = table.concat(enums.type, '/') - else - tp = enums.type - end - strs[#strs+1] = ('\n%s: %s'):format(name, tp or 'any') - for _, enum in ipairs(enums) do - if enum.default then - strs[#strs+1] = '\n -> ' - else - strs[#strs+1] = '\n | ' - end - if enum.code then - strs[#strs+1] = tostring(enum.code) - else - strs[#strs+1] = ('%q'):format(enum.enum) - end - if enum.description then - strs[#strs+1] = ' -- ' .. enum.description - end - end - end - return table.concat(strs) -end - -local function buildValueName(result, source) - local func = result.value - local declarat = func.declarat or source - if declarat then - local key - if declarat.type == 'name' then - key = declarat[1] - elseif declarat.type == 'string' then - key = ('%q'):format(declarat[1]) - elseif declarat.type == 'number' or declarat.type == 'boolean' then - key = tostring(declarat[1]) - elseif func.type == 'function' then - key = '' - elseif type(result.key) == 'string' then - key = result.key - else - key = '' - end - - local parentName = declarat.parentName - - if not parentName then - return key or '' - end - - if parentName == '?' then - local parentType = result.parentValue and result.parentValue.type - if parentType == 'table' then - else - parentName = '*' .. parentType - end - end - if source.object then - return parentName .. ':' .. key - else - if parentName then - if declarat.index then - return parentName .. '[' .. key .. ']' - else - return parentName .. '.' .. key - end - else - return key - end - end - end - return result.key or '' -end - -local function buildValueArgs(result, source, select) - local func = result.value - local names = {} - local values = {} - if func.args then - for i, arg in ipairs(func.args) do - names[i] = arg.key - end - end - if func.argValues then - for i, value in ipairs(func.argValues) do - values[i] = value.type - end - end - local strs = {} - local argLabel - local start = 1 - if source.object then - start = 2 - if select then - select = select + 1 - end - end - local max - if func.built then - max = #names - else - max = math.max(#names, #values) - end - for i = start, max do - local name = names[i] - local value = values[i] or 'any' - if name then - strs[#strs+1] = name .. ': ' .. value - else - strs[#strs+1] = value - end - if i == select then - argLabel = strs[#strs] - end - end - if func.hasDots then - strs[#strs+1] = '...' - end - return table.concat(strs, ', '), argLabel -end - -local function buildValueReturns(result) - local func = result.value - if not func.hasReturn then - return '' - end - local strs = {} - if func.returns then - for i, rtn in ipairs(func.returns) do - strs[i] = rtn.type - end - end - if #strs == 0 then - strs[1] = 'any' - end - return '\n -> ' .. table.concat(strs, ', ') -end - -local function getFunctionHover(name, result, source, lib, oo, select) - local args = '' - local returns - local enum - local tip - local argLabel - if lib then - args, argLabel = buildLibArgs(lib, oo, select) - returns = buildLibReturns(lib) - enum = buildEnum(lib) - tip = lib.description - else - args, argLabel = buildValueArgs(result, source, select) - returns = buildValueReturns(result) - end - local title = ('function %s(%s)%s'):format(name, args, returns) - return { - label = title, - description = tip, - enum = enum, - argLabel = argLabel, - } -end - -local function findClass(result) - -- 根据部分字段尝试找出自定义类型 - local metatable = result.value.metatable - if not metatable or not metatable.child then - return nil - end - -- 查找meta表的 __name 字段 - local name = metatable.child['__name'] - -- 值必须是字符串 - if name and name.value and type(name.value.value) == 'string' then - return name.value.value - end - -- 查找meta表 __index 里的字段 - local index = metatable.child['__index'] - if index and index.value and index.value.child then - for key, field in pairs(index.value.child) do - -- 键值类型必须均为字符串 - if type(key) ~= 'string' then - goto CONTINUE - end - if not field.value or type(field.value.value) ~= 'string' then - goto CONTINUE - end - local lKey = key:lower() - if lKey == 'type' or lKey == 'name' or lKey == 'class' then - -- 必须只有过一次赋值 - local hasSet = false - for _, info in ipairs(field) do - if info.type == 'set' then - if hasSet then - goto CONTINUE - end - hasSet = true - end - end - return field.value.value - end - ::CONTINUE:: - end - end - return nil -end - -local function unpackTable(result) - local child = result.value and result.value.child - if not child then - return '{}' - end - local lines = {'{'} - for key, field in pairs(child) do - local kType = type(key) - if kType == 'table' then - key = ('[*%s]'):format(key.type) - else - if kType ~= 'string' then - key = ('[%s]'):format(key) - end - end - - local value = field.value - if not value then - lines[#lines+1] = (' %s: %s,'):format(key, 'any') - goto CONTINUE - end - - local vType = type(value.value) - if vType == 'boolean' or vType == 'integer' or vType == 'number' or vType == 'string' then - lines[#lines+1] = (' %s: %s = %q,'):format(key, value.type, value.value) - else - lines[#lines+1] = (' %s: %s,'):format(key, value.type) - end - ::CONTINUE:: - end - lines[#lines+1] = '}' - return table.concat(lines, '\r\n') -end - -local function getValueHover(name, valueType, result, source, lib) - if not lib then - local class = findClass(result) - if class then - valueType = class - end - end - - if not OriginTypes[valueType] then - valueType = '*' .. valueType - end - - local value - local tip - if lib then - value = lib.code or (lib.value and ('%q'):format(lib.value)) - tip = lib.description - else - value = result.value.value and ('%q'):format(result.value.value) - end - - local tp = result.type - if tp == 'field' then - if result.parent and result.parent.value and result.parent.value.ENV then - tp = 'global' - end - end - - local text - if valueType == 'table' then - text = ('%s %s: %s'):format(tp, name, unpackTable(result)) - else - if value == nil then - text = ('%s %s: %s'):format(tp, name, valueType) - else - text = ('%s %s: %s = %s'):format(tp, name, valueType, value) - end - end - return { - label = text, - description = tip, - } -end - -local function getStringHover(result, lsp) - if not result.uri then - return nil - end - if not lsp or not lsp.workspace then - return nil - end - local path = lsp.workspace:relativePathByUri(result.uri) - return { - description = ('[%s](%s)'):format(path:string(), result.uri), - } -end - -return function (result, source, lsp, select) - if not result.value then - return - end - - if result.key == '' then - return - end - - if result.type == 'string' then - return getStringHover(result, lsp) - end - - if result.type ~= 'local' and result.type ~= 'field' then - return - end - - local lib, fullKey, oo = findLib(result) - local valueType = lib and lib.type - if valueType then - if type(valueType) == 'table' then - valueType = valueType[1] - end - else - valueType = result.value.type or 'nil' - end - local name = fullKey or buildValueName(result, source) - local hover - if valueType == 'function' then - hover = getFunctionHover(name, result, source, lib, oo, select) - else - hover = getValueHover(name, valueType, result, source, lib) - end - if not hover then - return - end - hover.name = name - return hover -end diff --git a/server/src/matcher/implementation.lua b/server/src/matcher/implementation.lua deleted file mode 100644 index 9ff3fdca..00000000 --- a/server/src/matcher/implementation.lua +++ /dev/null @@ -1,88 +0,0 @@ -local function parseResultAcrossUri(positions, vm, result) - -- 跨越文件时,遍历的是值的绑定信息 - for _, info in ipairs(result.value) do - if info.type == 'set' and info.source.uri == result.value.uri then - positions[1] = { - info.source.start, - info.source.finish, - info.source.uri, - } - end - end - if #positions == 0 then - for _, info in ipairs(result.value) do - if info.type == 'return' and info.source.uri == result.value.uri then - positions[1] = { - info.source.start, - info.source.finish, - info.source.uri, - } - end - end - end - if #positions == 0 then - positions[1] = { - 0, 0, - result.value.uri, - } - end -end - -local function parseResult(vm, result) - local positions = {} - local tp = result.type - if tp == 'local' then - if result.value.uri ~= vm.uri then - parseResultAcrossUri(positions, vm, result) - else - for _, info in ipairs(result) do - if info.type == 'set' then - positions[#positions+1] = { - info.source.start, - info.source.finish, - info.source.uri, - } - end - end - end - elseif tp == 'field' then - if result.value.uri ~= vm.uri then - parseResultAcrossUri(positions, vm, result) - else - for _, info in ipairs(result) do - if info.type == 'set' then - positions[#positions+1] = { - info.source.start, - info.source.finish, - info.source.uri, - } - end - end - end - elseif tp == 'label' then - for _, info in ipairs(result) do - if info.type == 'set' then - positions[#positions+1] = { - info.source.start, - info.source.finish, - } - end - end - elseif tp == 'string' then - -- require 'XXX' 专用 - positions[#positions+1] = { - 0, - 0, - result.uri, - } - end - return positions -end - -return function (vm, result) - if not result then - return nil - end - local positions = parseResult(vm, result) - return positions -end diff --git a/server/src/matcher/init.lua b/server/src/matcher/init.lua deleted file mode 100644 index cf33359a..00000000 --- a/server/src/matcher/init.lua +++ /dev/null @@ -1,16 +0,0 @@ -local api = { - definition = require 'matcher.definition', - implementation = require 'matcher.implementation', - references = require 'matcher.references', - rename = require 'matcher.rename', - hover = require 'matcher.hover', - diagnostics = require 'matcher.diagnostics', - findResult = require 'matcher.find_result', - findLib = require 'matcher.find_lib', - completion = require 'matcher.completion', - signature = require 'matcher.signature', - documentSymbol = require 'matcher.document_symbol', - vm = require 'matcher.vm', -} - -return api diff --git a/server/src/matcher/library.lua b/server/src/matcher/library.lua deleted file mode 100644 index b268b368..00000000 --- a/server/src/matcher/library.lua +++ /dev/null @@ -1,177 +0,0 @@ -local lni = require 'lni' -local fs = require 'bee.filesystem' - -local function mergeEnum(lib, locale) - if not lib or not locale then - return - end - local pack = {} - for _, enum in ipairs(lib) do - if enum.enum then - pack[enum.enum] = enum - end - if enum.code then - pack[enum.code] = enum - end - end - for _, enum in ipairs(locale) do - if pack[enum.enum] then - if enum.description then - pack[enum.enum].description = enum.description - end - end - if pack[enum.code] then - if enum.description then - pack[enum.code].description = enum.description - end - end - end -end - -local function mergeField(lib, locale) - if not lib or not locale then - return - end - local pack = {} - for _, field in ipairs(lib) do - if field.field then - pack[field.field] = field - end - end - for _, field in ipairs(locale) do - if pack[field.field] then - if field.description then - pack[field.field].description = field.description - end - end - end -end - -local function mergeLocale(libs, locale) - if not libs or not locale then - return - end - for name in pairs(locale) do - if libs[name] then - if locale[name].description then - libs[name].description = locale[name].description - end - mergeEnum(libs[name].enums, locale[name].enums) - mergeField(libs[name].fields, locale[name].fields) - end - end -end - -local function mergeSource(alllibs, name, lib) - if not lib.source then - alllibs.global[name] = lib - return - end - for _, source in ipairs(lib.source) do - local sourceName = source.name or name - if source.type == 'global' then - alllibs.global[sourceName] = lib - elseif source.type == 'library' then - alllibs.library[sourceName] = lib - elseif source.type == 'object' then - alllibs.object[sourceName] = lib - end - end -end - -local function copy(t) - local new = {} - for k, v in pairs(t) do - new[k] = v - end - return new -end - -local function insert(tbl, name, key, value) - if not name or not key then - return - end - if not tbl[name] then - tbl[name] = { - type = name, - name = name, - child = {}, - } - end - tbl[name].child[key] = copy(value) -end - -local function mergeParent(alllibs, name, lib) - for _, parent in ipairs(lib.parent) do - if parent.type == 'global' then - insert(alllibs.global, parent.name, name, lib) - elseif parent.type == 'library' then - insert(alllibs.library, parent.name, name, lib) - elseif parent.type == 'object' then - insert(alllibs.object, parent.name, name, lib) - end - end -end - -local function mergeLibs(alllibs, libs) - if not libs then - return - end - for _, lib in pairs(libs) do - if lib.parent then - mergeParent(alllibs, lib.name, lib) - else - mergeSource(alllibs, lib.name, lib) - end - end -end - -local function loadLocale(language, relative) - local localePath = ROOT / 'locale' / language / relative - local localeBuf = io.load(localePath) - if localeBuf then - local locale = table.container() - xpcall(lni.classics, log.error, localeBuf, localePath:string(), {locale}) - return locale - end - return nil -end - -local function fix(libs) - for name, lib in pairs(libs) do - lib.name = lib.name or name - lib.child = {} - end -end - -local function init() - local lang = require 'language' - local id = lang.id - local alllibs = { - global = table.container(), - library = table.container(), - object = table.container(), - } - for path in io.scan(ROOT / 'libs') do - local libs - local buf = io.load(path) - if buf then - libs = table.container() - xpcall(lni.classics, log.error, buf, path:string(), {libs}) - fix(libs) - end - local relative = fs.relative(path, ROOT) - - local locale = loadLocale('en-US', relative) - mergeLocale(libs, locale) - if id ~= 'en-US' then - locale = loadLocale(id, relative) - mergeLocale(libs, locale) - end - mergeLibs(alllibs, libs) - end - - return alllibs -end - -return init() diff --git a/server/src/matcher/references.lua b/server/src/matcher/references.lua deleted file mode 100644 index 71b7eb77..00000000 --- a/server/src/matcher/references.lua +++ /dev/null @@ -1,38 +0,0 @@ -local findResult = require 'matcher.find_result' - -local function parseResult(result, declarat) - local positions = {} - local tp = result.type - if tp == 'local' then - for _, info in ipairs(result) do - if declarat or info.type == 'get' then - positions[#positions+1] = {info.source.start, info.source.finish} - end - end - elseif tp == 'field' then - for _, info in ipairs(result) do - if declarat or info.type == 'get' then - positions[#positions+1] = {info.source.start, info.source.finish} - end - end - elseif tp == 'label' then - for _, info in ipairs(result) do - if declarat or info.type == 'goto' then - positions[#positions+1] = {info.source.start, info.source.finish} - end - end - elseif tp == 'string' then - else - error('Unknow result type:' .. result.type) - end - return positions -end - -return function (vm, pos, declarat) - local result = findResult(vm, pos) - if not result then - return nil - end - local positions = parseResult(result, declarat) - return positions -end diff --git a/server/src/matcher/rename.lua b/server/src/matcher/rename.lua deleted file mode 100644 index 1033d331..00000000 --- a/server/src/matcher/rename.lua +++ /dev/null @@ -1,51 +0,0 @@ -local findResult = require 'matcher.find_result' -local parser = require 'parser' - -local function parseResult(result, source, newName) - local positions = {} - local tp = result.type - if tp == 'local' or tp == 'field' then - local key = source[1] - if result.disableRename then - return positions - end - if source.index then - if not parser.grammar(newName, 'Exp') then - return positions - end - else - if not parser.grammar(newName, 'Name') then - return positions - end - end - local mark = {} - for _, info in ipairs(result) do - if not mark[info.source] then - mark[info.source] = info - if info.source[1] == key then - positions[#positions+1] = {info.source.start, info.source.finish} - end - end - end - elseif tp == 'label' then - if not parser.grammar(newName, 'Name') then - return positions - end - local label = result.label - for _, info in ipairs(label) do - positions[#positions+1] = {info.source.start, info.source.finish} - end - else - error('Unknow result type:' .. result.type) - end - return positions -end - -return function (vm, pos, newName) - local result, source = findResult(vm, pos) - if not result then - return nil - end - local positions = parseResult(result, source, newName) - return positions -end diff --git a/server/src/matcher/signature.lua b/server/src/matcher/signature.lua deleted file mode 100644 index dc6af15e..00000000 --- a/server/src/matcher/signature.lua +++ /dev/null @@ -1,66 +0,0 @@ -local hover = require 'matcher.hover' - -local function isContainPos(obj, pos) - if obj.start <= pos and obj.finish + 1 >= pos then - return true - end - return false -end - -local function findArgCount(args, pos) - for i, arg in ipairs(args) do - if isContainPos(arg, pos) then - return i, arg - end - end - return #args + 1, nil -end - --- 找出范围包含pos的call -local function findCall(vm, pos) - local results = {} - for _, call in ipairs(vm.results.calls) do - if isContainPos(call.args, pos) then - local n, arg = findArgCount(call.args, pos) - if arg and arg.type == 'string' then - return nil - end - local var = vm.results.sources[call.lastObj] - if var then - results[#results+1] = { - func = call.func, - var = var, - source = call.lastObj, - select = n, - args = call.args, - } - end - end - end - -- 可能处于 'func1(func2(' 的嵌套中,因此距离越远的函数层级越低 - table.sort(results, function (a, b) - return a.args.start < b.args.start - end) - return results -end - -return function (vm, pos) - local calls = findCall(vm, pos) - if not calls or #calls == 0 then - return nil - end - - local hovers = {} - for _, call in ipairs(calls) do - local hvr = hover(call.var, call.source, nil, call.select) - if hvr and hvr.argLabel then - hovers[#hovers+1] = hvr - end - end - - if #hovers == 0 then - return nil - end - - return hovers -end diff --git a/server/src/matcher/vm.lua b/server/src/matcher/vm.lua deleted file mode 100644 index 6bd7e7c3..00000000 --- a/server/src/matcher/vm.lua +++ /dev/null @@ -1,1501 +0,0 @@ -local env = require 'matcher.env' -local library = require 'matcher.library' - -local DefaultSource = { start = 0, finish = 0 } - --- 根据赋值顺序决定遍历顺序的表 -local function orderTable() - local t = {} - local list = {} - local mark = {} - return setmetatable(t, { - __newindex = function (self, k, v) - if not mark[k] then - mark[k] = true - list[#list+1] = k - end - rawset(self, k, v) - end, - __pairs = function (self) - local i = 0 - return function () - while true do - i = i + 1 - local k = list[i] - if not k then - return nil, nil - end - local v = t[k] - if v ~= nil then - return k, v - end - end - end - end, - }) -end - -local function readOnly(t) - local keys - return setmetatable({}, { - __index = function (self, k) - if k == nil then - return nil - end - local v = t[k] - if type(v) == 'table' then - v = readOnly(v) - end - self[k] = v - return v - end, - __len = function (self) - return #t - end, - __pairs = function (self) - if not keys then - keys = {} - for k in pairs(t) do - keys[#keys+1] = k - end - end - local i = 0 - return function () - i = i + 1 - local k = keys[i] - return k, self[k] - end - end, - __source = t, - }) -end - -local function insertOnce(tbl, key) - if tbl[key] then - return - end - tbl[key] = true - tbl[#tbl+1] = key -end - -local mt = {} -mt.__index = mt - -function mt:createDummyVar(source, value) - local loc = { - type = 'local', - key = '', - source = source or DefaultSource, - } - self:setValue(loc, value, source) - return loc -end - -function mt:createLocal(key, source, value) - local loc = { - type = 'local', - key = key, - source = source or DefaultSource, - close = self.scope.block.finish, - } - - if source then - source.isLocal = true - end - - local shadow = self.scope.locals[key] - if shadow then - shadow.close = source and (source.start-1) - local group - if shadow.shadow then - group = shadow.shadow - else - group = { shadow } - shadow.shadow = group - end - group[#group+1] = loc - loc.shadow = group - end - - self.scope.locals[key] = loc - self.results.locals[#self.results.locals+1] = loc - - self:addInfo(loc, 'local', source) - self:setValue(loc, value, source) - return loc -end - -function mt:createArg(key, source, value) - local loc = self:createLocal(key, source, value) - if source then - source.isArg = true - end - return loc -end - -function mt:scopePush(block) - if not block.start then - error('Scope push without start!') - end - self.scope:push() - self.scope.block = block -end - -function mt:scopePop() - self.scope:pop() -end - -function mt:addInfo(obj, type, source) - if source and not source.start then - error('Miss start: ' .. table.dump(source)) - end - obj[#obj+1] = { - type = type, - source = source or DefaultSource, - } - if source then - source.uri = self.uri - local other = self.results.sources[source] - if other then - if other.type == 'multi-source' then - other[#other+1] = obj - else - other = { - type = 'multi-source', - [1] = other, - [2] = obj, - } - end - else - self.results.sources[source] = obj - end - if type == 'set' or type == 'return' then - if not obj.declarat then - obj.declarat = source - end - end - end - return obj -end - -function mt:createDots(index, source) - local dots = { - type = 'dots', - source = source or DefaultSource, - func = self:getCurrentFunction(), - index = index, - } - self.chunk.dots = dots - return dots -end - -function mt:buildTable(source) - local tbl = self:createValue('table', source) - if not source then - return tbl - end - local n = 0 - for index, obj in ipairs(source) do - if obj.type == 'pair' then - local value = self:getExp(obj[2]) - local key = obj[1] - if key.index then - local index = self:getIndex(key) - local field = self:createField(tbl, index, key) - if value.type == 'list' then - self:setValue(field, value[1], key) - else - self:setValue(field, value, key) - end - else - if key.type == 'name' then - local index = key[1] - insertOnce(self.results.indexs, index) - local field = self:createField(tbl, index, key) - key.isIndex = true - if value.type == 'list' then - self:setValue(field, value[1], key) - else - self:setValue(field, value, key) - end - end - end - else - local value = self:getExp(obj) - if value.type == 'list' then - if index == #source then - for i, v in ipairs(value) do - local field = self:createField(tbl, n + i) - self:setValue(field, v) - end - else - n = n + 1 - local field = self:createField(tbl, n) - self:setValue(field, value[1]) - end - else - n = n + 1 - local field = self:createField(tbl, n) - self:setValue(field, value) - end - -- 处理写了一半的 key = value,把name类的数组元素视为哈希键 - if obj.type == 'name' then - obj.isIndex = true - end - end - end - return tbl -end - -function mt:mergeValue(a, b, mark) - if a == b then - return - end - if not mark then - mark = {} - end - if mark[a] or mark[b] then - return - end - if a.uri ~= self.uri then - return - end - mark[a] = true - mark[b] = true - self:mergeChild(a, b, mark) - for k in pairs(a) do - a[k] = nil - end - for k, v in pairs(b) do - a[k] = v - end -end - -function mt:mergeField(a, b, mark) - if a == b then - return - end - if not mark then - mark = {} - end - for i, info in ipairs(a) do - a[i] = nil - b[#b+1] = info - end - for i, v in ipairs(b) do - a[i] = v - end - self:mergeValue(a.value, b.value, mark) -end - -function mt:mergeChild(a, b, mark) - if a == b then - return - end - if not a.child and not b.child then - return - end - if not mark then - mark = {} - end - if a.uri ~= self.uri then - return - end - if b.uri == self.uri then - local child = a.child or orderTable() - local other = b.child or orderTable() - a.child = nil - b.child = nil - for k, v in pairs(other) do - if child[k] then - self:mergeField(child[k], v, mark) - else - child[k] = v - end - end - a.child = child - b.child = child - else - local child = a.child or orderTable() - local other = b.child - if not other then - return - end - a.child = nil - for k, v in pairs(other) do - child[k] = v - end - a.child = child - end -end - -function mt:setValue(var, value, source) - if value and value.type == 'list' then - error('Cant set value list') - end - value = value or self:createValue('any', source) - if var.value then - if value.type == 'any' then - self:mergeChild(var.value, value) - else - self:mergeValue(var.value, value) - end - value = var.value - else - var.value = value - end - if source and source.start then - self:addInfo(var, 'set', source) - self:addInfo(value, 'set', source) - end - return value -end - -function mt:getValue(var) - if not var.value then - var.value = self:createValue('any') - end - return var.value -end - -function mt:createField(pValue, name, source) - if pValue.type == 'local' or pValue.type == 'field' then - error('Only value can create field') - end - local field = { - type = 'field', - key = name, - source = source or DefaultSource, - } - - if not pValue.child then - pValue.child = orderTable() - end - pValue.child[name] = field - self:inference(pValue, 'table') - - return field -end - -function mt:getField(pValue, name, source) - local field = (pValue.child and pValue.child[name]) - or self:createField(pValue, name, source) - - return field -end - -function mt:buildFunction(exp, object) - local func = self:createValue('function', exp) - func.args = {} - func.argValues = {} - - if not exp then - return func - end - - func.built = true - - self:scopePush(exp) - self.chunk:push() - self.chunk:cut 'dots' - self.chunk:cut 'labels' - self.chunk.func = func - - if object then - local var = self:createArg('self', object.source, self:getValue(object)) - var.disableRename = true - func.args[1] = var - end - - local stop - self:forList(exp.arg, function (arg) - if stop then - return - end - if arg.type == 'name' then - local var = self:createArg(arg[1], arg) - arg.isArg = true - func.args[#func.args+1] = var - func.argValues[#func.args] = self:getValue(var) - elseif arg.type == '...' then - self:createDots(#func.args+1, arg) - func.hasDots = true - for _ = 1, 10 do - func.argValues[#func.argValues+1] = self:createValue('any', arg) - end - stop = true - end - end) - - self:doActions(exp) - - self.results.funcs[#self.results.funcs+1] = func - - self.chunk:pop() - self:scopePop() - - return func -end - -function mt:forList(list, callback) - if not list then - return - end - if list.type == 'list' then - for i = 1, #list do - callback(list[i]) - end - else - callback(list) - end -end - -function mt:countList(list) - if not list then - return 0 - end - if list.type == 'list' then - return #list - end - return 1 -end - -function mt:updateFunctionArgs(func) - if not func.argValues then - return - end - if not func.args then - return - end - - local values = func.argValues - for i, var in ipairs(func.args) do - if var.type == 'dots' then - local list = { - type = 'list', - } - for n = i, #values do - list[n-i+1] = values[n] - end - self:setValue(var, list) - break - else - self:setValue(var, values[i]) - end - end -end - -function mt:setFunctionArg(func, values) - if func.uri ~= self.uri then - return - end - if not func.argValues then - func.argValues = {} - end - for i = 1, #values do - if not func.argValues[i] then - func.argValues[i] = values[i] - end - self:inference(values[i], func.argValues[i].type) - self:inference(func.argValues[i], values[i].type) - end - - self:updateFunctionArgs(func) -end - -function mt:getFunctionArg(func, i) - if not func.argValues then - func.argValues = {} - end - if not func.argValues[i] then - for n = #func.argValues+1, i do - func.argValues[n] = self:createValue('any') - end - end - return func.argValues[i] -end - -function mt:checkMetaIndex(value, meta) - local index = self:getField(meta, '__index') - if not index then - return - end - local indexValue = self:getValue(index) - -- TODO 支持function - self:mergeChild(value, indexValue) -end - -function mt:callSetMetaTable(func, values) - if not values[1] then - values[1] = self:createValue('any') - end - if not values[2] then - values[2] = self:createValue('any') - end - self:setFunctionReturn(func, 1, values[1]) - - values[1].metatable = values[2] - -- 检查 __index - self:checkMetaIndex(values[1], values[2]) -end - -function mt:getRequire(strValue, destVM) - -- 取出对方的主函数 - local main = destVM.results.main - -- 获取主函数返回值,注意不能修改对方的环境 - local mainValue - if main.returns then - mainValue = readOnly(main.returns[1]) - else - mainValue = self:createValue('boolean', nil, true) - mainValue.uri = destVM.uri - end - - return mainValue -end - -function mt:getLoadFile(strValue, destVM) - -- 取出对方的主函数 - local main = destVM.results.main - -- loadfile 的返回值就是对方的主函数 - local mainValue = readOnly(main) - - return mainValue -end - -function mt:tryRequireOne(strValue, mode) - if not self.lsp or not self.lsp.workspace then - return nil - end - local str = strValue.value - if type(str) == 'string' then - -- 支持 require 'xxx' 的转到定义 - local strSource = strValue.source - self.results.sources[strSource] = strValue - strValue.isRequire = true - - local uri - if mode == 'require' then - uri = self.lsp.workspace:searchPath(self.uri, str) - elseif mode == 'loadfile' then - uri = self.lsp.workspace:loadPath(self.uri, str) - elseif mode == 'dofile' then - uri = self.lsp.workspace:loadPath(self.uri, str) - end - if not uri then - return nil - end - - strValue.uri = uri - -- 如果取不到VM(不编译),则做个标记,之后再取一次 - local destVM = self.lsp:getVM(uri) - self.lsp:compileChain(self.uri, uri) - if destVM then - if mode == 'require' then - return self:getRequire(strValue, destVM) - elseif mode == 'loadfile' then - return self:getLoadFile(strValue, destVM) - elseif mode == 'dofile' then - return self:getRequire(strValue, destVM) - end - end - end - return nil -end - -function mt:callRequire(func, values) - if not values[1] then - values[1] = self:createValue('any') - end - local str = values[1].value - if type(str) ~= 'string' then - return - end - local lib = library.library[str] - if lib then - local value = self:getLibValue(lib, 'library') - self:setFunctionReturn(func, 1, value) - return - else - local requireValue = self:tryRequireOne(values[1], 'require') - if not requireValue then - requireValue = self:createValue('boolean') - requireValue.isRequire = true - end - self:setFunctionReturn(func, 1, requireValue) - end -end - -function mt:callLoadFile(func, values) - if not values[1] then - values[1] = self:createValue('any') - end - local str = values[1].value - if type(str) ~= 'string' then - return - end - local requireValue = self:tryRequireOne(values[1], 'loadfile') - if not requireValue then - requireValue = self:createValue('any') - requireValue.isRequire = true - end - self:setFunctionReturn(func, 1, requireValue) -end - -function mt:callDoFile(func, values) - if not values[1] then - values[1] = self:createValue('any') - end - local str = values[1].value - if type(str) ~= 'string' then - return - end - local requireValue = self:tryRequireOne(values[1], 'dofile') - if not requireValue then - requireValue = self:createValue('any') - requireValue.isRequire = true - end - self:setFunctionReturn(func, 1, requireValue) -end - -function mt:call(func, values) - self:inference(func, 'function') - local lib = func.lib - if lib then - if lib.args then - for i, arg in ipairs(lib.args) do - if arg.type == '...' then - self:inference(self:getFunctionArg(func, i), 'any') - else - self:inference(self:getFunctionArg(func, i), arg.type or 'any') - end - end - end - if lib.returns then - for i, rtn in ipairs(lib.returns) do - if rtn.type == '...' then - self:inference(self:getFunctionReturns(func, i), 'any') - else - self:inference(self:getFunctionReturns(func, i), rtn.type or 'any') - end - end - end - if lib.special then - if lib.special == 'setmetatable' then - self:callSetMetaTable(func, values) - elseif lib.special == 'require' then - self:callRequire(func, values) - elseif lib.special == 'loadfile' then - self:callLoadFile(func, values) - elseif lib.special == 'dofile' then - self:callDoFile(func, values) - end - end - end - - self:setFunctionArg(func, values) - - return self:getFunctionReturns(func) -end - -function mt:getCurrentFunction() - return self.chunk.func -end - -function mt:mergeFunctionReturn(func, index, value) - if not func.returns[index] then - func.returns[index] = value - return - end - if value.type == 'nil' then - return - end - if value == 'any' and func.returns[index] ~= 'nil' then - return - end - func.returns[index] = value -end - -function mt:setFunctionReturn(func, index, value) - func.hasReturn = true - if not func.returns then - func.returns = { - type = 'list', - } - end - if value then - if value.type == 'list' then - for i, v in ipairs(value) do - self:mergeFunctionReturn(func, index+i-1, v) - end - else - self:mergeFunctionReturn(func, index, value) - end - else - self:mergeFunctionReturn(func, index, self:createValue('any')) - end -end - -function mt:getFunctionReturns(func, i) - if func.maxReturns and i and func.maxReturns < i then - return self:createValue('nil') - end - if not func.returns then - func.returns = { - type = 'list', - } - end - if i then - if not func.returns[i] then - for n = #func.returns+1, i do - func.returns[n] = self:createValue('any') - end - end - return func.returns[i] - else - return func.returns - end -end - -function mt:inference(value, type) - if type == '...' then - error('Value type cant be ...') - end - if value.type == 'any' and type ~= 'nil' then - value.type = type - end -end - -function mt:createValue(tp, source, v) - if tp == '...' then - error('Value type cant be ...') - end - -- TODO lib里的多类型 - if type(tp) == 'table' then - tp = tp[1] - end - local value = { - type = tp, - source = source or DefaultSource, - value = v, - uri = self.uri, - } - local lib = library.object[tp] - if lib then - self:getLibChild(value, lib, 'object') - end - return value -end - -function mt:getLibChild(value, lib, parentType) - if lib.child then - if self.libraryChild[lib] then - value.child = self.libraryChild[lib] - return - end - self.libraryChild[lib] = {} - for fName, fLib in pairs(lib.child) do - local fField = self:createField(value, fName) - local fValue = self:getLibValue(fLib, parentType) - self:setValue(fField, fValue) - end - if value.child then - for k, v in pairs(value.child) do - self.libraryChild[lib][k] = v - end - end - value.child = self.libraryChild[lib] - end -end - -function mt:getLibValue(lib, parentType, v) - if self.libraryValue[lib] then - return self.libraryValue[lib] - end - local tp = lib.type - local value - if tp == 'table' then - value = self:createValue('table') - elseif tp == 'function' then - value = self:createValue('function') - if lib.returns then - local dots - for i, rtn in ipairs(lib.returns) do - self:setFunctionReturn(value, i, self:getLibValue(rtn, parentType)) - if rtn.type == '...' then - dots = true - end - end - if not dots then - value.maxReturns = #lib.returns - end - else - value.maxReturns = 0 - end - if lib.args then - local values = {} - for i, arg in ipairs(lib.args) do - values[i] = self:getLibValue(arg, parentType) or self:createValue('any') - end - self:setFunctionArg(value, values) - end - elseif tp == 'string' then - value = self:createValue('string', nil, v or lib.value) - elseif tp == 'boolean' then - value = self:createValue('boolean', nil, v or lib.value) - elseif tp == 'number' then - value = self:createValue('number', nil, v or lib.value) - elseif tp == 'integer' then - value = self:createValue('integer', nil, v or lib.value) - elseif tp == 'nil' then - value = self:createValue('nil') - elseif tp == '...' then - value = self:createValue('any') - else - value = self:createValue(tp or 'any') - end - self.libraryValue[lib] = value - value.lib = lib - value.parentType = parentType - - self:getLibChild(value, lib, parentType) - - return value -end - -function mt:getName(name, source) - local loc = self.scope.locals[name] - if loc then - return loc - end - local ENV = self.scope.locals._ENV - local global = self:getField(self:getValue(ENV), name, source) - global.parent = ENV - return global -end - -function mt:getIndex(obj) - local tp = obj.type - if tp == 'name' then - local var = self:getName(obj[1]) - local value = self:getValue(var) - self:addInfo(var, 'get', obj) - return value - elseif (tp == 'string' or tp == 'number' or tp == 'boolean') then - return obj[1] - else - return self:getExp(obj) - end -end - --- expect表示遇到 ... 时,期待的返回数量 -function mt:unpackDots(res, expect) - local dots = self:getDots(1) - local func = dots.func - local start = dots.index - if expect then - local finish = start + expect - 1 - for i = start, finish do - res[#res+1] = self:getFunctionArg(func, i) - end - else - if not func.argValues then - return - end - for i = start, #func.argValues do - res[#res+1] = func.argValues[i] - end - end -end - -function mt:unpackList(list, expect) - local res = { - type = 'list', - } - if not list then - return res - end - if list.type == 'list' or list.type == 'call' then - for i, exp in ipairs(list) do - if exp.type == '...' then - self:unpackDots(res, expect) - break - end - local value = self:getExp(exp) - if value.type == 'list' then - if i == #list then - for _, v in ipairs(value) do - res[#res+1] = v - end - else - res[#res+1] = value[1] - end - else - res[#res+1] = value - end - end - elseif list.type == '...' then - self:unpackDots(res, expect) - else - local value = self:getExp(list) - if value.type == 'list' then - for i, v in ipairs(value) do - res[i] = v - end - else - res[1] = value - end - end - for _, v in ipairs(res) do - if v.type == 'list' then - error('Unpack list') - end - end - return res -end - -function mt:getSimple(simple, mode) - local value = self:getExp(simple[1]) - local field - local parentName - local tp = simple[1].type - if tp == 'name' then - field = self:getName(simple[1][1]) - parentName = field.key - elseif tp == 'string' or tp == 'number' or tp == 'nil' or tp == 'boolean' then - local v = self:createValue(tp, simple[1], simple[1][1]) - field = self:createDummyVar(simple[1], v) - parentName = '*' .. tp - else - local v = self:createValue('any', simple[1]) - field = self:createDummyVar(simple[1], v) - parentName = '?' - end - local object - local lastField = field - for i = 2, #simple do - local obj = simple[i] - local tp = obj.type - - if tp == 'call' then - local args = self:unpackList(obj) - if object then - table.insert(args, 1, self:getValue(object)) - end - local func = value - -- 函数的返回值一定是list - value = self:call(func, args) - if i < #simple then - value = value[1] or self:createValue('any') - end - self.results.calls[#self.results.calls+1] = { - args = obj, - lastObj = simple[i-1], - nextObj = simple[i+1], - func = func, - } - parentName = parentName .. '(...)' - elseif tp == 'index' then - local child = obj[1] - local index = self:getIndex(child) - field = self:getField(value, index, child) - field.parentValue = value - value = self:getValue(field) - if mode == 'value' or i < #simple then - self:addInfo(field, 'get', obj) - end - field.parent = lastField - lastField = field - obj.object = object - obj.parentName = parentName - if obj[1].type == 'string' then - parentName = ('%s[%q]'):format(parentName, index) - elseif obj[1].type == 'number' or obj[1].type == 'boolean' then - parentName = ('%s[%s]'):format(parentName, index) - else - parentName = ('%s[?]'):format(parentName) - end - else - if tp == 'name' then - field = self:getField(value, obj[1], obj) - field.parentValue = value - value = self:getValue(field) - if mode == 'value' or i < #simple then - self:addInfo(field, 'get', obj) - end - field.parent = lastField - lastField = field - obj.object = object - obj.parentName = parentName - parentName = parentName .. '.' .. field.key - elseif tp == ':' then - object = field - end - end - end - if mode == 'value' then - return value, object - elseif mode == 'field' then - return field, object - end - error('Unknow simple mode: ' .. mode) -end - -function mt:isTrue(v) - if v.type == 'nil' then - return false - end - if v.type == 'boolean' and not v.value then - return false - end - return true -end - -function mt:getBinary(exp) - local v1 = self:getExp(exp[1]) - local v2 = self:getExp(exp[2]) - local op = exp.op - -- TODO 搜索元方法 - if op == 'or' then - if self:isTrue(v1) then - return v1 - else - return v2 - end - elseif op == 'and' then - if self:isTrue(v1) then - return v2 - else - return v1 - end - elseif op == '<=' - or op == '>=' - or op == '<' - or op == '>' - then - self:inference(v1, 'number') - self:inference(v2, 'number') - return self:createValue('boolean') - elseif op == '~=' - or op == '==' - then - return self:createValue('boolean') - elseif op == '|' - or op == '~' - or op == '&' - or op == '<<' - or op == '>>' - then - self:inference(v1, 'integer') - self:inference(v2, 'integer') - if math.type(v1.value) == 'integer' and math.type(v2.value) == 'integer' then - if op == '|' then - return self:createValue('integer', v1.value | v2.value) - elseif op == '~' then - return self:createValue('integer', v1.value ~ v2.value) - elseif op == '&' then - return self:createValue('integer', v1.value &v2.value) - elseif op == '<<' then - return self:createValue('integer', v1.value << v2.value) - elseif op == '>>' then - return self:createValue('integer', v1.value >> v2.value) - end - end - return self:createValue('integer') - elseif op == '..' then - self:inference(v1, 'string') - self:inference(v2, 'string') - if type(v1.value) == 'string' and type(v2.value) == 'string' then - return self:createValue('string', nil, v1.value .. v2.value) - end - return self:createValue('string') - elseif op == '+' - or op == '-' - or op == '*' - or op == '/' - or op == '^' - or op == '%' - or op == '//' - then - self:inference(v1, 'number') - self:inference(v2, 'number') - if type(v1.value) == 'number' and type(v2.value) == 'number' then - if op == '+' then - return self:createValue('number', nil, v1.value + v2.value) - elseif op == '-' then - return self:createValue('number', nil, v1.value - v2.value) - elseif op == '*' then - return self:createValue('number', nil, v1.value * v2.value) - elseif op == '/' then - return self:createValue('number', nil, v1.value / v2.value) - elseif op == '^' then - return self:createValue('number', nil, v1.value ^ v2.value) - elseif op == '%' then - return self:createValue('number', nil, v1.value % v2.value) - elseif op == '//' then - return self:createValue('number', nil, v1.value // v2.value) - end - end - return self:createValue('number') - end - return nil -end - -function mt:getUnary(exp) - local v1 = self:getExp(exp[1]) - local op = exp.op - -- TODO 搜索元方法 - if op == 'not' then - return self:createValue('boolean') - elseif op == '#' then - self:inference(v1, 'table') - if type(v1.value) == 'string' then - return self:createValue('integer', nil, #v1.value) - end - return self:createValue('integer') - elseif op == '-' then - self:inference(v1, 'number') - if type(v1.value) == 'number' then - return self:createValue('number', nil, -v1.value) - end - return self:createValue('number') - elseif op == '~' then - self:inference(v1, 'integer') - if math.type(v1.value) == 'integer' then - return self:createValue('integer', nil, ~v1.value) - end - return self:createValue('integer') - end - return nil -end - -function mt:getDots() - if not self.chunk.dots then - self:createDots(1) - end - return self.chunk.dots -end - -function mt:getExp(exp) - local tp = exp.type - if tp == 'nil' then - return self:createValue('nil', exp) - elseif tp == 'string' then - self.results.strings[#self.results.strings+1] = exp - return self:createValue('string', exp, exp[1]) - elseif tp == 'boolean' then - return self:createValue('boolean', exp, exp[1]) - elseif tp == 'number' then - return self:createValue('number', exp, exp[1]) - elseif tp == 'name' then - local var = self:getName(exp[1], exp) - local value = self:getValue(var) - self:addInfo(var, 'get', exp) - return value - elseif tp == 'simple' then - return self:getSimple(exp, 'value') - elseif tp == 'binary' then - return self:getBinary(exp) - elseif tp == 'unary' then - return self:getUnary(exp) - elseif tp == 'function' then - return self:buildFunction(exp) - elseif tp == 'table' then - return self:buildTable(exp) - elseif tp == '...' then - local value = { type = 'list' } - self:unpackDots(value) - return value - end - error('Unkown exp type: ' .. tostring(tp)) -end - -function mt:doDo(action) - self:scopePush(action) - self:doActions(action) - self:scopePop() -end - -function mt:doReturn(action) - self:getCurrentFunction().hasReturn = true - for i, exp in ipairs(action) do - local value = self:getExp(exp) - if value.type == 'list' then - if i == #action then - if #value == 0 then - value[1] = self:createValue('any', exp) - end - for x, v in ipairs(value) do - self:addInfo(v, 'return', exp) - self:setFunctionReturn(self:getCurrentFunction(), i + x - 1, v) - end - break - else - local v = value[1] or self:createValue('nil', exp) - self:addInfo(v, 'return', exp) - self:setFunctionReturn(self:getCurrentFunction(), i, v) - end - else - self:addInfo(value, 'return', exp) - self:setFunctionReturn(self:getCurrentFunction(), i, value) - end - end -end - -function mt:createLabel(action) - local name = action[1] - if not self.chunk.labels[name] then - local label = { - type = 'label', - key = name, - } - self.chunk.labels[name] = label - self.results.labels[#self.results.labels+1] = label - end - return self.chunk.labels[name] -end - -function mt:doSet(action) - if not action[2] then - return - end - local n = self:countList(action[1]) - -- 要先计算值 - local values = self:unpackList(action[2], n) - self:forList(action[1], function (key) - local value = table.remove(values, 1) - if key.type == 'name' then - local var = self:getName(key[1], key) - self:setValue(var, value, key) - elseif key.type == 'simple' then - local field = self:getSimple(key, 'field') - self:setValue(field, value, key[#key]) - end - end) -end - -function mt:doLocal(action) - local n = self:countList(action[1]) - local values - if action[2] then - values = self:unpackList(action[2], n) - end - self:forList(action[1], function (key) - local value - if values then - value = table.remove(values, 1) - end - self:createLocal(key[1], key, value) - end) -end - -function mt:doIf(action) - for _, block in ipairs(action) do - if block.filter then - self:getExp(block.filter) - end - - self:scopePush(block) - self:doActions(block) - self:scopePop() - end -end - -function mt:doLoop(action) - - local min = self:getExp(action.min) - self:getExp(action.max) - if action.step then - self:getExp(action.step) - end - - self:scopePush(action) - self:createLocal(action.arg[1], action.arg, min) - self:doActions(action) - self:scopePop() -end - -function mt:doIn(action) - local args = self:unpackList(action.exp) - - self:scopePush(action) - local func = table.remove(args, 1) or self:createValue('any') - local values = self:call(func, args) - self:forList(action.arg, function (arg) - local value = table.remove(values, 1) - self:createLocal(arg[1], arg, value) - end) - - self:doActions(action) - - self:scopePop() -end - -function mt:doWhile(action) - - self:getExp(action.filter) - - self:scopePush(action) - self:doActions(action) - self:scopePop() -end - -function mt:doRepeat(action) - self:scopePush(action) - self:doActions(action) - self:getExp(action.filter) - self:scopePop() -end - -function mt:doFunction(action) - local name = action.name - local var, object - local source - if name then - if name.type == 'simple' then - var, object = self:getSimple(name, 'field') - source = name[#name] - else - var = self:getName(name[1], name) - source = name - end - end - local func = self:buildFunction(action, object) - if var then - self:setValue(var, func, source) - end -end - -function mt:doLocalFunction(action) - local name = action.name - local var, object - local source - if name then - if name.type == 'simple' then - var, object = self:getSimple(name, 'field') - source = name[#name] - else - var = self:createLocal(name[1], name) - source = name - end - end - local func = self:buildFunction(action, object) - if var then - self:setValue(var, func, source) - end -end - -function mt:doAction(action) - local tp = action.type - if tp == 'do' then - self:doDo(action) - elseif tp == 'break' then - elseif tp == 'return' then - self:doReturn(action) - elseif tp == 'label' then - local label = self:createLabel(action) - self:addInfo(label, 'set', action) - elseif tp == 'goto' then - local label = self:createLabel(action) - self:addInfo(label, 'goto', action) - elseif tp == 'set' then - self:doSet(action) - elseif tp == 'local' then - self:doLocal(action) - elseif tp == 'simple' then - -- call - self:getSimple(action, 'value') - elseif tp == 'if' then - self:doIf(action) - elseif tp == 'loop' then - self:doLoop(action) - elseif tp == 'in' then - self:doIn(action) - elseif tp == 'while' then - self:doWhile(action) - elseif tp == 'repeat' then - self:doRepeat(action) - elseif tp == 'function' then - self:doFunction(action) - elseif tp == 'localfunction' then - self:doLocalFunction(action) - else - self:getExp(action) - end -end - -function mt:doActions(actions) - for _, action in ipairs(actions) do - self:doAction(action) - end -end - -function mt:createEnvironment() - self.scope.block = { start = 0, finish = math.maxinteger } - -- 整个文件是一个函数 - self.chunk.func = self:buildFunction() - self.results.main = self.chunk.func - -- 隐藏的上值`_ENV` - local parent = self:createLocal('_ENV') - local envValue = self:setValue(parent, self:buildTable()) - -- _ENV 有个特殊标记 - envValue.ENV = true - -- 隐藏的参数`...` - self:createDots(1) - - -- 设置全局变量 - for name, lib in pairs(library.global) do - local field = self:createField(envValue, name) - local value = self:getLibValue(lib, 'global') - value = self:setValue(field, value) - end - - -- 设置 _G 使用 _ENV 的child - local g = self:getField(envValue, '_G') - local gValue = self:getValue(g) - gValue.child = envValue.child -end - -local function compile(ast, lsp, uri) - local vm = setmetatable({ - scope = env { - locals = {}, - }, - chunk = env { - labels = {}, - }, - results = { - locals = {}, - labels = {}, - funcs = {}, - calls = {}, - sources= {}, - strings= {}, - indexs = {}, - main = nil, - }, - libraryValue = {}, - libraryChild = {}, - lsp = lsp, - uri = uri, - }, mt) - - -- 创建初始环境 - vm:createEnvironment() - - -- 执行代码 - vm:doActions(ast) - - return vm -end - -return function (ast, lsp, uri) - if not ast then - return nil - end - local suc, res = xpcall(compile, log.error, ast, lsp, uri) - if not suc then - return nil - end - return res -end |