diff options
Diffstat (limited to 'server')
99 files changed, 3163 insertions, 0 deletions
diff --git a/server/bin/API-MS-Win-core-xstate-l2-1-0.dll b/server/bin/API-MS-Win-core-xstate-l2-1-0.dll Binary files differnew file mode 100644 index 00000000..064d9c7f --- /dev/null +++ b/server/bin/API-MS-Win-core-xstate-l2-1-0.dll diff --git a/server/bin/api-ms-win-core-console-l1-1-0.dll b/server/bin/api-ms-win-core-console-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..d991b177 --- /dev/null +++ b/server/bin/api-ms-win-core-console-l1-1-0.dll diff --git a/server/bin/api-ms-win-core-datetime-l1-1-0.dll b/server/bin/api-ms-win-core-datetime-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..eb960250 --- /dev/null +++ b/server/bin/api-ms-win-core-datetime-l1-1-0.dll diff --git a/server/bin/api-ms-win-core-debug-l1-1-0.dll b/server/bin/api-ms-win-core-debug-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..1f84ace6 --- /dev/null +++ b/server/bin/api-ms-win-core-debug-l1-1-0.dll diff --git a/server/bin/api-ms-win-core-errorhandling-l1-1-0.dll b/server/bin/api-ms-win-core-errorhandling-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..07b393ef --- /dev/null +++ b/server/bin/api-ms-win-core-errorhandling-l1-1-0.dll diff --git a/server/bin/api-ms-win-core-file-l1-1-0.dll b/server/bin/api-ms-win-core-file-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..2ad839ff --- /dev/null +++ b/server/bin/api-ms-win-core-file-l1-1-0.dll diff --git a/server/bin/api-ms-win-core-file-l1-2-0.dll b/server/bin/api-ms-win-core-file-l1-2-0.dll Binary files differnew file mode 100644 index 00000000..ca52643a --- /dev/null +++ b/server/bin/api-ms-win-core-file-l1-2-0.dll diff --git a/server/bin/api-ms-win-core-file-l2-1-0.dll b/server/bin/api-ms-win-core-file-l2-1-0.dll Binary files differnew file mode 100644 index 00000000..b9798e0e --- /dev/null +++ b/server/bin/api-ms-win-core-file-l2-1-0.dll diff --git a/server/bin/api-ms-win-core-handle-l1-1-0.dll b/server/bin/api-ms-win-core-handle-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..1fabaeee --- /dev/null +++ b/server/bin/api-ms-win-core-handle-l1-1-0.dll diff --git a/server/bin/api-ms-win-core-heap-l1-1-0.dll b/server/bin/api-ms-win-core-heap-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..0c78d9b6 --- /dev/null +++ b/server/bin/api-ms-win-core-heap-l1-1-0.dll diff --git a/server/bin/api-ms-win-core-interlocked-l1-1-0.dll b/server/bin/api-ms-win-core-interlocked-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..f30ea8b3 --- /dev/null +++ b/server/bin/api-ms-win-core-interlocked-l1-1-0.dll diff --git a/server/bin/api-ms-win-core-libraryloader-l1-1-0.dll b/server/bin/api-ms-win-core-libraryloader-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..346770fc --- /dev/null +++ b/server/bin/api-ms-win-core-libraryloader-l1-1-0.dll diff --git a/server/bin/api-ms-win-core-localization-l1-2-0.dll b/server/bin/api-ms-win-core-localization-l1-2-0.dll Binary files differnew file mode 100644 index 00000000..1e5072f4 --- /dev/null +++ b/server/bin/api-ms-win-core-localization-l1-2-0.dll diff --git a/server/bin/api-ms-win-core-memory-l1-1-0.dll b/server/bin/api-ms-win-core-memory-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..ca54325f --- /dev/null +++ b/server/bin/api-ms-win-core-memory-l1-1-0.dll diff --git a/server/bin/api-ms-win-core-namedpipe-l1-1-0.dll b/server/bin/api-ms-win-core-namedpipe-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..0338d576 --- /dev/null +++ b/server/bin/api-ms-win-core-namedpipe-l1-1-0.dll diff --git a/server/bin/api-ms-win-core-processenvironment-l1-1-0.dll b/server/bin/api-ms-win-core-processenvironment-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..da33c1a3 --- /dev/null +++ b/server/bin/api-ms-win-core-processenvironment-l1-1-0.dll diff --git a/server/bin/api-ms-win-core-processthreads-l1-1-0.dll b/server/bin/api-ms-win-core-processthreads-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..0baacbba --- /dev/null +++ b/server/bin/api-ms-win-core-processthreads-l1-1-0.dll diff --git a/server/bin/api-ms-win-core-processthreads-l1-1-1.dll b/server/bin/api-ms-win-core-processthreads-l1-1-1.dll Binary files differnew file mode 100644 index 00000000..cdd9a887 --- /dev/null +++ b/server/bin/api-ms-win-core-processthreads-l1-1-1.dll diff --git a/server/bin/api-ms-win-core-profile-l1-1-0.dll b/server/bin/api-ms-win-core-profile-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..5fcfd92c --- /dev/null +++ b/server/bin/api-ms-win-core-profile-l1-1-0.dll diff --git a/server/bin/api-ms-win-core-rtlsupport-l1-1-0.dll b/server/bin/api-ms-win-core-rtlsupport-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..2735c669 --- /dev/null +++ b/server/bin/api-ms-win-core-rtlsupport-l1-1-0.dll diff --git a/server/bin/api-ms-win-core-string-l1-1-0.dll b/server/bin/api-ms-win-core-string-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..2ab518ad --- /dev/null +++ b/server/bin/api-ms-win-core-string-l1-1-0.dll diff --git a/server/bin/api-ms-win-core-synch-l1-1-0.dll b/server/bin/api-ms-win-core-synch-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..56447809 --- /dev/null +++ b/server/bin/api-ms-win-core-synch-l1-1-0.dll diff --git a/server/bin/api-ms-win-core-synch-l1-2-0.dll b/server/bin/api-ms-win-core-synch-l1-2-0.dll Binary files differnew file mode 100644 index 00000000..0d22398b --- /dev/null +++ b/server/bin/api-ms-win-core-synch-l1-2-0.dll diff --git a/server/bin/api-ms-win-core-sysinfo-l1-1-0.dll b/server/bin/api-ms-win-core-sysinfo-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..2fcbd871 --- /dev/null +++ b/server/bin/api-ms-win-core-sysinfo-l1-1-0.dll diff --git a/server/bin/api-ms-win-core-timezone-l1-1-0.dll b/server/bin/api-ms-win-core-timezone-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..37492726 --- /dev/null +++ b/server/bin/api-ms-win-core-timezone-l1-1-0.dll diff --git a/server/bin/api-ms-win-core-util-l1-1-0.dll b/server/bin/api-ms-win-core-util-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..536f9c97 --- /dev/null +++ b/server/bin/api-ms-win-core-util-l1-1-0.dll diff --git a/server/bin/api-ms-win-crt-conio-l1-1-0.dll b/server/bin/api-ms-win-crt-conio-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..a5312bd0 --- /dev/null +++ b/server/bin/api-ms-win-crt-conio-l1-1-0.dll diff --git a/server/bin/api-ms-win-crt-convert-l1-1-0.dll b/server/bin/api-ms-win-crt-convert-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..d0e8a74f --- /dev/null +++ b/server/bin/api-ms-win-crt-convert-l1-1-0.dll diff --git a/server/bin/api-ms-win-crt-environment-l1-1-0.dll b/server/bin/api-ms-win-crt-environment-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..33ab4235 --- /dev/null +++ b/server/bin/api-ms-win-crt-environment-l1-1-0.dll diff --git a/server/bin/api-ms-win-crt-filesystem-l1-1-0.dll b/server/bin/api-ms-win-crt-filesystem-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..73c1bbd0 --- /dev/null +++ b/server/bin/api-ms-win-crt-filesystem-l1-1-0.dll diff --git a/server/bin/api-ms-win-crt-heap-l1-1-0.dll b/server/bin/api-ms-win-crt-heap-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..b2491c58 --- /dev/null +++ b/server/bin/api-ms-win-crt-heap-l1-1-0.dll diff --git a/server/bin/api-ms-win-crt-locale-l1-1-0.dll b/server/bin/api-ms-win-crt-locale-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..5c6daf8b --- /dev/null +++ b/server/bin/api-ms-win-crt-locale-l1-1-0.dll diff --git a/server/bin/api-ms-win-crt-math-l1-1-0.dll b/server/bin/api-ms-win-crt-math-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..59f5938e --- /dev/null +++ b/server/bin/api-ms-win-crt-math-l1-1-0.dll diff --git a/server/bin/api-ms-win-crt-multibyte-l1-1-0.dll b/server/bin/api-ms-win-crt-multibyte-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..08fde912 --- /dev/null +++ b/server/bin/api-ms-win-crt-multibyte-l1-1-0.dll diff --git a/server/bin/api-ms-win-crt-private-l1-1-0.dll b/server/bin/api-ms-win-crt-private-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..5504b29b --- /dev/null +++ b/server/bin/api-ms-win-crt-private-l1-1-0.dll diff --git a/server/bin/api-ms-win-crt-process-l1-1-0.dll b/server/bin/api-ms-win-crt-process-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..6db6d131 --- /dev/null +++ b/server/bin/api-ms-win-crt-process-l1-1-0.dll diff --git a/server/bin/api-ms-win-crt-runtime-l1-1-0.dll b/server/bin/api-ms-win-crt-runtime-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..3ccdf40b --- /dev/null +++ b/server/bin/api-ms-win-crt-runtime-l1-1-0.dll diff --git a/server/bin/api-ms-win-crt-stdio-l1-1-0.dll b/server/bin/api-ms-win-crt-stdio-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..50e08763 --- /dev/null +++ b/server/bin/api-ms-win-crt-stdio-l1-1-0.dll diff --git a/server/bin/api-ms-win-crt-string-l1-1-0.dll b/server/bin/api-ms-win-crt-string-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..32a56dbb --- /dev/null +++ b/server/bin/api-ms-win-crt-string-l1-1-0.dll diff --git a/server/bin/api-ms-win-crt-time-l1-1-0.dll b/server/bin/api-ms-win-crt-time-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..f5c85a6c --- /dev/null +++ b/server/bin/api-ms-win-crt-time-l1-1-0.dll diff --git a/server/bin/api-ms-win-crt-utility-l1-1-0.dll b/server/bin/api-ms-win-crt-utility-l1-1-0.dll Binary files differnew file mode 100644 index 00000000..0dd8c7e9 --- /dev/null +++ b/server/bin/api-ms-win-crt-utility-l1-1-0.dll diff --git a/server/bin/debugger.dll b/server/bin/debugger.dll Binary files differnew file mode 100644 index 00000000..5dfd8be4 --- /dev/null +++ b/server/bin/debugger.dll diff --git a/server/bin/ffi.dll b/server/bin/ffi.dll Binary files differnew file mode 100644 index 00000000..048b2b0f --- /dev/null +++ b/server/bin/ffi.dll diff --git a/server/bin/filesystem.dll b/server/bin/filesystem.dll Binary files differnew file mode 100644 index 00000000..536b3d93 --- /dev/null +++ b/server/bin/filesystem.dll diff --git a/server/bin/lpeglabel.dll b/server/bin/lpeglabel.dll Binary files differnew file mode 100644 index 00000000..56b76ed4 --- /dev/null +++ b/server/bin/lpeglabel.dll diff --git a/server/bin/lua.exe b/server/bin/lua.exe Binary files differnew file mode 100644 index 00000000..f72d0632 --- /dev/null +++ b/server/bin/lua.exe diff --git a/server/bin/lua53.dll b/server/bin/lua53.dll Binary files differnew file mode 100644 index 00000000..a8d1f825 --- /dev/null +++ b/server/bin/lua53.dll diff --git a/server/bin/msvcp140.dll b/server/bin/msvcp140.dll Binary files differnew file mode 100644 index 00000000..0a30fd08 --- /dev/null +++ b/server/bin/msvcp140.dll diff --git a/server/bin/process.dll b/server/bin/process.dll Binary files differnew file mode 100644 index 00000000..ff0fc1d3 --- /dev/null +++ b/server/bin/process.dll diff --git a/server/bin/vcruntime140.dll b/server/bin/vcruntime140.dll Binary files differnew file mode 100644 index 00000000..fa755e5c --- /dev/null +++ b/server/bin/vcruntime140.dll diff --git a/server/bin/ydbase.dll b/server/bin/ydbase.dll Binary files differnew file mode 100644 index 00000000..48fa2abf --- /dev/null +++ b/server/bin/ydbase.dll diff --git a/server/bin/yue.dll b/server/bin/yue.dll Binary files differnew file mode 100644 index 00000000..42f015c6 --- /dev/null +++ b/server/bin/yue.dll diff --git a/server/main.lua b/server/main.lua new file mode 100644 index 00000000..773fb0a8 --- /dev/null +++ b/server/main.lua @@ -0,0 +1,19 @@ +require 'filesystem' +ROOT = fs.current_path() +package.path = (ROOT / 'src' / '?.lua'):string() + .. ';' .. (ROOT / 'src' / '?' / 'init.lua'):string() + +log = require 'log' +log.init(ROOT, ROOT / 'log' / 'test.log') +log.info('Lua 语言服务启动,路径为:', ROOT) + +local dbg = require 'debugger' +dbg:io 'listen:0.0.0.0:546858' +dbg:start() + +require 'utility' +require 'global_protect' +local service = require 'service' +local session = service() + +session:listen() diff --git a/server/src/ffi/sleep.lua b/server/src/ffi/sleep.lua new file mode 100644 index 00000000..5c4be639 --- /dev/null +++ b/server/src/ffi/sleep.lua @@ -0,0 +1,8 @@ +local ffi = require 'ffi' +ffi.cdef[[ + void Sleep(unsigned long dwMilliseconds); +]] + +return function (time) + ffi.C.Sleep(time) +end diff --git a/server/src/ffi/unicode.lua b/server/src/ffi/unicode.lua new file mode 100644 index 00000000..734b4679 --- /dev/null +++ b/server/src/ffi/unicode.lua @@ -0,0 +1,49 @@ +local ffi = require 'ffi' +ffi.cdef[[ + int MultiByteToWideChar(unsigned int CodePage, unsigned long dwFlags, const char* lpMultiByteStr, int cbMultiByte, wchar_t* lpWideCharStr, int cchWideChar); + int WideCharToMultiByte(unsigned int CodePage, unsigned long dwFlags, const wchar_t* lpWideCharStr, int cchWideChar, char* lpMultiByteStr, int cchMultiByte, const char* lpDefaultChar, int* pfUsedDefaultChar); +]] + +local CP_UTF8 = 65001 +local CP_ACP = 0 + +local function u2w(input) + local wlen = ffi.C.MultiByteToWideChar(CP_UTF8, 0, input, #input, nil, 0) + local wstr = ffi.new('wchar_t[?]', wlen+1) + ffi.C.MultiByteToWideChar(CP_UTF8, 0, input, #input, wstr, wlen) + return wstr, wlen +end + +local function a2w(input) + local wlen = ffi.C.MultiByteToWideChar(CP_ACP, 0, input, #input, nil, 0) + local wstr = ffi.new('wchar_t[?]', wlen+1) + ffi.C.MultiByteToWideChar(CP_ACP, 0, input, #input, wstr, wlen) + return wstr, wlen +end + +local function w2u(wstr, wlen) + local len = ffi.C.WideCharToMultiByte(CP_UTF8, 0, wstr, wlen, nil, 0, nil, nil) + local str = ffi.new('char[?]', len+1) + ffi.C.WideCharToMultiByte(CP_UTF8, 0, wstr, wlen, str, len, nil, nil) + return ffi.string(str) +end + +local function w2a(wstr, wlen) + local len = ffi.C.WideCharToMultiByte(CP_ACP, 0, wstr, wlen, nil, 0, nil, nil) + local str = ffi.new('char[?]', len) + ffi.C.WideCharToMultiByte(CP_ACP, 0, wstr, wlen, str, len, nil, nil) + return ffi.string(str) +end + +return { + u2w = u2w, + a2w = a2w, + w2u = w2u, + w2a = w2a, + u2a = function (input) + return w2a(u2w(input)) + end, + a2u = function (input) + return w2u(a2w(input)) + end, +} diff --git a/server/src/global_protect.lua b/server/src/global_protect.lua new file mode 100644 index 00000000..6c736ea6 --- /dev/null +++ b/server/src/global_protect.lua @@ -0,0 +1,10 @@ +local mt = {} +setmetatable(_G, mt) + +function mt:__index(k) + error(('读取不存在的全局变量[%s]'):format(k), 2) +end + +function mt:__newindex(k, v) + error(('保存全局变量[%s] = [%s]'):format(k, v), 2) +end diff --git a/server/src/json/decode.lua b/server/src/json/decode.lua new file mode 100644 index 00000000..6a93d127 --- /dev/null +++ b/server/src/json/decode.lua @@ -0,0 +1,128 @@ +local lpeg = require 'lpeglabel' +local save_sort +local table_pack = table.pack + +local P = lpeg.P +local S = lpeg.S +local R = lpeg.R +local V = lpeg.V +local C = lpeg.C +local Ct = lpeg.Ct +local Cg = lpeg.Cg +local Cc = lpeg.Cc +local Cp = lpeg.Cp +local Cs = lpeg.Cs + +local EscMap = { + ['t'] = '\t', + ['r'] = '\r', + ['n'] = '\n', + ['"'] = '"', + ['\\'] = '\\', +} +local BoolMap = { + ['true'] = true, + ['false'] = false, +} + +local hashmt = { + __pairs = function (self) + local i = 1 + local function next() + i = i + 1 + local k = self[i] + if k == nil then + return + end + local v = self[k] + if v == nil then + return next() + end + return k, v + end + return next + end, + __newindex = function (self, k, v) + local i = 2 + while self[i] do + i = i + 1 + end + rawset(self, i, k) + rawset(self, k, v) + end, + __debugger_extand = function (self) + local list = {} + for k, v in pairs(self) do + k = tostring(k) + list[#list+1] = k + list[k] = v + end + return list + end, +} + +local tointeger = math.tointeger +local tonumber = tonumber +local setmetatable = setmetatable +local rawset = rawset +local function HashTable(patt) + return C(patt) / function (_, ...) + local hash = table_pack(...) + local n = hash.n + hash.n = nil + if save_sort then + local max = n // 2 + for i = 1, max do + local key, value = hash[2*i-1], hash[2*i] + hash[key] = value + hash[i+1] = key + end + hash[1] = nil + for i = max+2, max*2 do + hash[i] = nil + end + return setmetatable(hash, hashmt) + else + local max = n // 2 + for i = 1, max do + local a = 2*i-1 + local b = 2*i + local key, value = hash[a], hash[b] + hash[key] = value + hash[a] = nil + hash[b] = nil + end + return hash + end + end +end + +local Token = P +{ + V'Value' * Cp(), + Nl = P'\r\n' + S'\r\n', + Sp = S' \t', + Spnl = (V'Sp' + V'Nl')^0, + Bool = C(P'true' + P'false') / BoolMap, + Int = C('0' + (P'-'^-1 * R'19' * R'09'^0)) / tointeger, + Float = C(P'-'^-1 * ('0' + R'19' * R'09'^0) * '.' * R'09'^0) / tonumber, + Null = P'null' * Cc(nil), + String = '"' * Cs(V'Char'^0) * '"', + Char = V'Esc' + (1 - P'"' - P'\t' - V'Nl'), + Esc = P'\\' * C(S'tnr"\\') / EscMap, + Hash = V'Spnl' * '{' * V'Spnl' * HashTable(V'Object'^-1 * (P',' * V'Object')^0) * V'Spnl' * P'}' * V'Spnl', + Array = V'Spnl' * '[' * V'Spnl' * Ct(V'Value'^-1 * (P',' * V'Spnl' * V'Value')^0) * V'Spnl' * P']' * V'Spnl', + Object = V'Spnl' * V'Key' * V'Spnl' * V'Value' * V'Spnl', + Key = V'String' * V'Spnl' * ':', + Value = V'Hash' + V'Array' + V'Bool' + V'Null' + V'String' + V'Float' + V'Int', +} + +return function (str, save_sort_) + save_sort = save_sort_ + local table, pos = Token:match(str) + if not pos or pos <= #str then + pos = pos or 1 + error(('没匹配完[%s]\n%s'):format(pos, str:sub(pos, pos+100))) + end + return table +end diff --git a/server/src/json/encode.lua b/server/src/json/encode.lua new file mode 100644 index 00000000..4bba26de --- /dev/null +++ b/server/src/json/encode.lua @@ -0,0 +1,135 @@ + +local rep = string.rep +local format = string.format +local gsub = string.gsub +local sub = string.sub +local sort = table.sort +local find = string.find +local tostring = tostring +local getmetatable = debug.getmetatable +local type = type +local next = next +local ipairs = ipairs + +local index +local lines +local n = -1 +local tabs = {} + +local esc_map = { + ['\\'] = '\\\\', + ['\r'] = '\\r', + ['\n'] = '\\n', + ['\t'] = '\\t', + ['"'] = '\\"', +} + +local function encode(data, key) + n = n + 1 + if not tabs[n] then + tabs[n] = rep('\t', n) + end + local tp = type(data) + if tp == 'table' then + if not data[1] and next(data) then + -- 认为这个是哈希表 + if key then + index=index+1;lines[index] = tabs[n] .. '"' .. gsub(key, '[\\\r\n\t"]', esc_map) .. '" : {\n' + else + index=index+1;lines[index] = tabs[n] .. '{\n' + end + local meta = getmetatable(data) + local sep + if meta and meta.__pairs then + for k, v in meta.__pairs(data), data do + if encode(v, k) then + index=index+1;lines[index] = ',\n' + sep = true + end + end + else + local list = {} + local i = 0 + for k in next, data do + i=i+1;list[i] = k + end + sort(list) + for j = 1, i do + local k = list[j] + if encode(data[k], k) then + index=index+1;lines[index] = ',\n' + sep = true + end + end + end + if sep then + lines[index] = '\n' + end + index=index+1;lines[index] = tabs[n] .. '}' + else + -- 认为这个是数组 + if key then + index=index+1;lines[index] = tabs[n] .. '"' .. gsub(key, '[\\\r\n\t"]', esc_map) .. '" : [\n' + else + index=index+1;lines[index] = tabs[n] .. '[\n' + end + local sep + for k, v in pairs(data) do + if encode(v) then + index=index+1;lines[index] = ',\n' + sep = true + end + end + if sep then + lines[index] = '\n' + end + index=index+1;lines[index] = tabs[n] .. ']' + end + elseif tp == 'number' then + data = tostring(data) + -- 判断 inf -inf -nan(ind) 1.#INF -1.#INF -1.#IND + if find(data, '%a') then + data = '0' + end + if key then + index=index+1;lines[index] = tabs[n] .. '"' .. gsub(key, '[\\\r\n\t"]', esc_map) .. '" : ' .. data + else + index=index+1;lines[index] = tabs[n] .. data + end + elseif tp == 'boolean' then + if key then + index=index+1;lines[index] = tabs[n] .. '"' .. gsub(key, '[\\\r\n\t"]', esc_map) .. '" : ' .. tostring(data) + else + index=index+1;lines[index] = tabs[n] .. tostring(data) + end + elseif tp == 'nil' then + if key then + index=index+1;lines[index] = tabs[n] .. '"' .. gsub(key, '[\\\r\n\t"]', esc_map) .. '" : null' + else + index=index+1;lines[index] = tabs[n] .. 'null' + end + elseif tp == 'string' then + local str = gsub(data, '[\\\r\n\t"]', esc_map) + if key then + index=index+1;lines[index] = tabs[n] .. '"' .. gsub(key, '[\\\r\n\t"]', esc_map) .. '" : "' .. str .. '"' + else + index=index+1;lines[index] = tabs[n] .. '"' .. str .. '"' + end + else + n = n - 1 + return false + end + n = n - 1 + return true +end + +local function json(t) + lines = {} + index = 0 + + encode(t) + + return table.concat(lines) +end + +return json diff --git a/server/src/json/init.lua b/server/src/json/init.lua new file mode 100644 index 00000000..c28e7aed --- /dev/null +++ b/server/src/json/init.lua @@ -0,0 +1,6 @@ +local api = { + decode = require 'json.decode', + encode = require 'json.encode', +} + +return api diff --git a/server/src/log.lua b/server/src/log.lua new file mode 100644 index 00000000..13e3ce7c --- /dev/null +++ b/server/src/log.lua @@ -0,0 +1,89 @@ +require 'filesystem' + +local log = {} + +log.file = nil +log.start_time = os.time() - os.clock() +log.size = 0 +log.max_size = 100 * 1024 * 1024 + +local function trim_src(src) + src = src:sub(log.prefix_len, -5) + src = src:gsub('[\\/]+', '.') + return src +end + +local function push_log(level, ...) + if not log.path then + return + end + if log.size > log.max_size then + return + end + local t = table.pack(...) + for i = 1, t.n do + t[i] = tostring(t[i]) + end + local str = table.concat(t, '\t') + if level == 'error' then + str = str .. '\n' .. debug.traceback(nil, 3) + end + if not log.file then + log.file = io.open(log.path, 'w') + if not log.file then + return + end + log.file:write('') + log.file:close() + log.file = io.open(log.path, 'ab') + if not log.file then + return + end + log.file:setvbuf 'no' + end + local sec, ms = math.modf(log.start_time + os.clock()) + local timestr = os.date('%Y-%m-%d %H:%M:%S', sec) + local info = debug.getinfo(3, 'Sl') + local buf + if info and info.currentline > 0 then + buf = ('[%s.%03.f][%s]: [%s:%s]%s\n'):format(timestr, ms * 1000, level, trim_src(info.source), info.currentline, str) + else + buf = ('[%s.%03.f][%s]: %s\n'):format(timestr, ms * 1000, level, str) + end + log.file:write(buf) + log.size = log.size + #buf + if log.size > log.max_size then + log.file:write('[日志过大]') + end + return str +end + +function log.info(...) + push_log('info', ...) +end + +function log.debug(...) + push_log('debug', ...) +end + +function log.trace(...) + push_log('trace', ...) +end + +function log.warn(...) + push_log('warn', ...) +end + +function log.error(...) + push_log('error', ...) +end + +function log.init(root, path) + log.path = path:string() + log.prefix_len = #root:string() + 3 + if not fs.exists(path:parent_path()) then + fs.create_directories(path:parent_path()) + end +end + +return log diff --git a/server/src/lsp.lua b/server/src/lsp.lua new file mode 100644 index 00000000..4a88aba6 --- /dev/null +++ b/server/src/lsp.lua @@ -0,0 +1,155 @@ +local json = require 'json' +local parser = require 'parser' + +local ErrorCodes = { + -- Defined by JSON RPC + ParseError = -32700, + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, + serverErrorStart = -32099, + serverErrorEnd = -32000, + ServerNotInitialized = -32002, + UnknownErrorCode = -32001, + + -- Defined by the protocol. + RequestCancelled = -32800, +} + +local mt = {} +mt.__index = mt + +mt._input = nil +mt._output = nil +mt._method = nil +mt._file = nil + +function mt:_callback(method, params) + local f = self._method + if f then + return f(method, params) + end + return nil, '没有注册method' +end + +function mt:_send(data) + local f = self._output + if not f then + return + end + data.jsonrpc = '2.0' + local content = json.encode(data) + local buf = ('Content-Length: %d\r\n\r\n%s'):format(#content, content) + f(buf) +end + +function mt:_readAsContent(header) + local len = tonumber(header:match('%d+')) + if not len then + log.error('错误的协议头:', header) + return + end + local buf = self:read(len+2) + local suc, res = pcall(json.decode, buf) + if not suc then + log.error('错误的协议:', buf) + return + end + local id = res.id + local method = res.method + local params = res.params + local response, err = self:_callback(method, params) + if id then + if response then + self:_send { + id = id, + result = response, + } + else + self:_send { + id = id, + error = { + code = ErrorCodes.UnknownErrorCode, + message = err or ('没有回应:' .. method), + }, + } + end + end + if not response then + log.error(err or ('没有回应:' .. method)) + end + -- 运行时不清理垃圾,在回复前端之后清理垃圾 + collectgarbage() +end + +function mt:setInput(input) + self._input = input +end + +function mt:setOutput(output) + self._output = output +end + +function mt:read(mode) + if not self._input then + return nil + end + return self._input(mode) +end + +function mt:saveText(uri, version, text) + local obj = self._file[uri] + if obj then + if obj.version >= version then + return + end + obj.version = version + obj.text = text + else + self._file[uri] = { + version = version, + text = text, + } + end +end + +function mt:loadText(uri) + local obj = self._file[uri] + if not obj then + return nil + end + return obj.text +end + +function mt:removeText(uri) + self._file[uri] = nil +end + +function mt:start(method) + self._method = method + while true do + local header = self:read 'l' + if not header then + return + end + if header:sub(1, #'Content-Length') == 'Content-Length' then + self:_readAsContent(header) + elseif header:sub(1, #'Content-Type') == 'Content-Type' then + else + log.error('错误的协议头:', header) + end + end + return true +end + +function mt:stop() + self._input = nil + self._output = nil +end + +return function () + return setmetatable({ + _file = {}, + }, mt) +end diff --git a/server/src/matcher/definition.lua b/server/src/matcher/definition.lua new file mode 100644 index 00000000..a744efed --- /dev/null +++ b/server/src/matcher/definition.lua @@ -0,0 +1,282 @@ +local parser = require 'parser' + +local pos +local defs = {} +local scopes +local result +local namePos +local colonPos + +local DUMMY_TABLE = {} + +local function scopeInit() + scopes = {{}} +end + +local function scopeDef(obj) + local scope = scopes[#scopes] + local name = obj[1] + scope[name] = obj +end + +local function scopeGet(name) + for i = #scopes, 1, -1 do + local scope = scopes[i] + local obj = scope[name] + if obj then + return obj + end + end + return nil +end + +local function scopeSet(obj) + local name = obj[1] + local scope = scopes[#scopes] + if not scope[name] then + scope[name] = obj + end +end + +local function globalSet(obj) + local name = obj[1] + if not scopeGet(name) then + local scope = scopes[1] + scope[name] = obj + end +end + +local function scopePush() + scopes[#scopes+1] = {} +end + +local function scopePop() + scopes[#scopes] = nil +end + +local function checkDifinition(name, p) + if pos < p or pos > p + #name then + return + end + result = scopeGet(name) +end + +function defs.NamePos(p) + namePos = p +end + +function defs.Name(str) + checkDifinition(str, namePos) + return {str, namePos, type = 'name'} +end + +function defs.DOTSPos(p) + namePos = p +end + +function defs.DOTS(str) + checkDifinition(str, namePos) + return {str, namePos, type = 'name'} +end + +function defs.COLONPos(p) + colonPos = p +end + +function defs.ColonName(name) + name.colon = colonPos + return name +end + +function defs.LocalVar(names) + for _, name in ipairs(names) do + scopeDef(name) + end +end + +function defs.LocalSet(names) + for _, name in ipairs(names) do + scopeDef(name) + end +end + +function defs.Set(simples) + for _, simple in ipairs(simples) do + if simple.type == 'simple' and #simple == 1 then + local obj = simple[1] + local name = obj[1] + globalSet(obj) + end + end +end + +function defs.Simple(...) + return { type = 'simple', ... } +end + +function defs.ArgList(...) + if ... == '' then + return DUMMY_TABLE + end + return { type = 'list', ... } +end + +function defs.FuncName(...) + if ... == '' then + return DUMMY_TABLE + end + return { type = 'simple', ... } +end + +function defs.FunctionDef(simple, args) + if #simple == 1 then + globalSet(simple[1]) + end + scopePush() + -- 判断隐藏的局部变量self + if #simple > 0 then + local name = simple[#simple] + if name.colon then + scopeDef {'self', name.colon, name.colon, type = 'name'} + end + end + for _, arg in ipairs(args) do + if arg.type == 'simple' and #arg == 1 then + local name = arg[1] + scopeDef(name) + end + if arg.type == 'name' then + scopeDef(arg) + end + end +end + +function defs.FunctionLoc(simple, args) + if #simple == 1 then + scopeDef(simple[1]) + end + scopePush() + -- 判断隐藏的局部变量self + if #simple > 0 then + local name = simple[#simple] + if name.colon then + scopeDef {'self', name.colon, name.colon, type = 'name'} + end + end + for _, arg in ipairs(args) do + if arg.type == 'simple' and #arg == 1 then + local name = arg[1] + scopeDef(name) + end + if arg.type == 'name' then + scopeDef(arg) + end + end +end + +function defs.Function() + scopePop() +end + +function defs.DoDef() + scopePush() +end + +function defs.Do() + scopePop() +end + +function defs.IfDef() + scopePush() +end + +function defs.If() + scopePop() +end + +function defs.ElseIfDef() + scopePush() +end + +function defs.ElseIf() + scopePop() +end + +function defs.ElseDef() + scopePush() +end + +function defs.Else() + scopePop() +end + +function defs.LoopDef(name) + scopePush() + scopeDef(name) +end + +function defs.Loop() + scopePop() +end + +function defs.LoopStart(name, exp) + return name +end + +function defs.NameList(...) + return { type = 'list', ... } +end + +function defs.SimpleList(...) + return { type = 'list', ... } +end + +function defs.InDef(names) + scopePush() + for _, name in ipairs(names) do + scopeDef(name) + end +end + +function defs.In() + scopePop() +end + +function defs.WhileDef() + scopePush() +end + +function defs.While() + scopePop() +end + +function defs.RepeatDef() + scopePush() +end + +function defs.Until() + scopePop() +end + +return function (buf, pos_) + pos = pos_ + result = nil + scopeInit() + + local suc, err = parser.grammar(buf, 'Lua', defs) + if not suc then + return false, '语法错误', err + end + + if not result then + return false, 'No word' + end + local name, start, finish = result[1], result[2], result[3] + if not start then + return false, 'No match' + end + if not finish then + finish = start + #name - 1 + end + return true, start, finish +end diff --git a/server/src/matcher/implementation.lua b/server/src/matcher/implementation.lua new file mode 100644 index 00000000..bd28ee3a --- /dev/null +++ b/server/src/matcher/implementation.lua @@ -0,0 +1,377 @@ +local parser = require 'parser' + +local pos +local defs = {} +local scopes +local logics +local result +local namePos +local colonPos + +local DUMMY_TABLE = {} + +local function logicPush() + logics[#logics+1] = 0 +end + +local function logicPop() + logics[#logics] = nil +end + +local function logicAdd() + logics[#logics] = logics[#logics] + 1 +end + +local function logicGet() + local list = {} + for i = 1, #logics do + list[i] = logics[i] + end + return list +end + +local function scopeInit() + scopes = {{}} +end + +local function scopeGet(name) + for i = #scopes, 1, -1 do + local scope = scopes[i] + local list = scope[name] + if list then + return list + end + end + return nil +end + +local function scopeSet(obj) + obj.logic = logicGet() + local name = obj[1] + local scope = scopes[#scopes] + local list = scope[name] + if list then + list[#list+1] = obj + else + scope[name] = {obj} + end +end + +local function scopePush() + scopes[#scopes+1] = {} +end + +local function scopePop() + scopes[#scopes] = nil +end + +local function globalSet(obj) + obj.logic = logicGet() + local name = obj[1] + for i = #scopes, 1, -1 do + local scope = scopes[i] + local list = scope[name] + if list then + list[#list+1] = obj + return + end + end + local scope = scopes[1] + scope[name] = {obj} +end + +local function sameLogic(cur, target) + for i = 1, #cur do + if target[i] == nil then + break + end + if cur[i] ~= target[i] then + return false + end + end + return true +end + +local function mustCovered(results, target) + for _, result in ipairs(results) do + local logic = result.logic + if #logic == #target then + local isSame = true + for i = 1, #logic do + if logic[i] ~= target[i] then + isSame = false + end + end + if isSame then + return true + end + end + end + return false +end + +local function checkImplementation(name, p) + if result ~= nil then + return + end + if pos < p or pos > p + #name then + return + end + local list = scopeGet(name) + if list then + local logic = logicGet() + result = {} + for i = #list, 1, -1 do + local obj = list[i] + local name, start, finish = obj[1], obj[2], obj[3] + if not finish then + finish = start + #name - 1 + end + -- 如果不在同一个分支里,则跳过 + if not sameLogic(logic, obj.logic) then + goto CONTINUE + end + -- 如果该分支已经有确定值,则跳过 + if mustCovered(result, obj.logic) then + goto CONTINUE + end + result[#result+1] = {start, finish, logic = obj.logic} + -- 如果分支长度比自己小,则一定是确信值,不用再继续找了 + if #obj.logic <= #logic then + break + end + ::CONTINUE:: + end + else + result = false + end +end + +function defs.NamePos(p) + namePos = p +end + +function defs.Name(str) + checkImplementation(str, namePos) + return {str, namePos, type = 'name'} +end + +function defs.DOTSPos(p) + namePos = p +end + +function defs.DOTS(str) + checkImplementation(str, namePos) + return {str, namePos, type = 'name'} +end + +function defs.COLONPos(p) + colonPos = p +end + +function defs.ColonName(name) + name.colon = colonPos + return name +end + +function defs.LocalVar(names) + for _, name in ipairs(names) do + scopeSet(name) + end +end + +function defs.LocalSet(names) + for _, name in ipairs(names) do + scopeSet(name) + end +end + +function defs.Set(simples) + for _, simple in ipairs(simples) do + if simple.type == 'simple' and #simple == 1 then + local obj = simple[1] + local name = obj[1] + globalSet(obj) + end + end +end + +function defs.Simple(...) + return { type = 'simple', ... } +end + +function defs.ArgList(...) + if ... == '' then + return DUMMY_TABLE + end + return { type = 'list', ... } +end + +function defs.FuncName(...) + if ... == '' then + return DUMMY_TABLE + end + return { type = 'simple', ... } +end + +function defs.FunctionDef(simple, args) + if #simple == 1 then + globalSet(simple[1]) + end + scopePush() + -- 判断隐藏的局部变量self + if #simple > 0 then + local name = simple[#simple] + if name.colon then + scopeSet {'self', name.colon, name.colon, type = 'name'} + end + end + for _, arg in ipairs(args) do + if arg.type == 'simple' and #arg == 1 then + local name = arg[1] + scopeSet(name) + end + if arg.type == 'name' then + scopeSet(arg) + end + end +end + +function defs.FunctionLoc(simple, args) + if #simple == 1 then + scopeSet(simple[1]) + end + scopePush() + -- 判断隐藏的局部变量self + if #simple > 0 then + local name = simple[#simple] + if name.colon then + scopeSet {'self', name.colon, name.colon, type = 'name'} + end + end + for _, arg in ipairs(args) do + if arg.type == 'simple' and #arg == 1 then + local name = arg[1] + scopeSet(name) + end + if arg.type == 'name' then + scopeSet(arg) + end + end +end + +function defs.Function() + scopePop() +end + +function defs.DoDef() + scopePush() +end + +function defs.Do() + scopePop() +end + +function defs.IfDef() + logicPush() + scopePush() +end + +function defs.If() + scopePop() +end + +function defs.ElseIfDef() + logicAdd() + scopePush() +end + +function defs.ElseIf() + scopePop() +end + +function defs.ElseDef() + logicAdd() + scopePush() +end + +function defs.Else() + scopePop() +end + +function defs.EndIf() + logicPop() +end + +function defs.LoopDef(name) + logicPush() + scopePush() + scopeSet(name) +end + +function defs.Loop() + scopePop() + logicPop() +end + +function defs.LoopStart(name, exp) + return name +end + +function defs.NameList(...) + return { type = 'list', ... } +end + +function defs.SimpleList(...) + return { type = 'list', ... } +end + +function defs.InDef(names) + logicPush() + scopePush() + for _, name in ipairs(names) do + scopeSet(name) + end +end + +function defs.In() + scopePop() + logicPop() +end + +function defs.WhileDef() + logicPush() + scopePush() +end + +function defs.While() + scopePop() + logicPop() +end + +function defs.RepeatDef() + logicPush() + scopePush() +end + +function defs.Until() + scopePop() + logicPop() +end + +return function (buf, pos_) + pos = pos_ + result = nil + logics = {} + scopeInit() + + local suc, err = parser.grammar(buf, 'Lua', defs) + if not suc then + return false, '语法错误', err + end + + if not result then + return false, 'No word' + end + return true, result +end diff --git a/server/src/matcher/init.lua b/server/src/matcher/init.lua new file mode 100644 index 00000000..c570b342 --- /dev/null +++ b/server/src/matcher/init.lua @@ -0,0 +1,6 @@ +local api = { + definition = require 'matcher.definition', + implementation = require 'matcher.implementation', +} + +return api diff --git a/server/src/method/exit.lua b/server/src/method/exit.lua new file mode 100644 index 00000000..716a86e7 --- /dev/null +++ b/server/src/method/exit.lua @@ -0,0 +1,3 @@ +return function () + return true +end diff --git a/server/src/method/init.lua b/server/src/method/init.lua new file mode 100644 index 00000000..a99c3ed5 --- /dev/null +++ b/server/src/method/init.lua @@ -0,0 +1,17 @@ +local method = {} + +local function init(name) + method[name] = require('method.' .. name:gsub('/', '.')) +end + +init 'exit' +init 'initialize' +init 'initialized' +init 'shutdown' +init 'textDocument/implementation' +init 'textDocument/definition' +init 'textDocument/didOpen' +init 'textDocument/didChange' +init 'textDocument/didClose' + +return method diff --git a/server/src/method/initialize.lua b/server/src/method/initialize.lua new file mode 100644 index 00000000..866ded66 --- /dev/null +++ b/server/src/method/initialize.lua @@ -0,0 +1,18 @@ +return function (lsp, data) + lsp._inited = true + return { + capabilities = { + -- 支持“转到定义” + definitionProvider = true, + -- 支持“转到实现” + implementationProvider = true, + -- 文本同步方式 + textDocumentSync = { + -- 打开关闭文本时通知 + openClose = true, + -- 文本改变时完全通知 TODO 支持差量更新(2) + change = 1, + } + } + } +end diff --git a/server/src/method/initialized.lua b/server/src/method/initialized.lua new file mode 100644 index 00000000..0451dc50 --- /dev/null +++ b/server/src/method/initialized.lua @@ -0,0 +1,3 @@ +return function (lsp) + return true +end diff --git a/server/src/method/shutdown.lua b/server/src/method/shutdown.lua new file mode 100644 index 00000000..0451dc50 --- /dev/null +++ b/server/src/method/shutdown.lua @@ -0,0 +1,3 @@ +return function (lsp) + return true +end diff --git a/server/src/method/textDocument/definition.lua b/server/src/method/textDocument/definition.lua new file mode 100644 index 00000000..3a17b463 --- /dev/null +++ b/server/src/method/textDocument/definition.lua @@ -0,0 +1,46 @@ +local parser = require 'parser' +local matcher = require 'matcher' + +return function (lsp, params) + local uri = params.textDocument.uri + local text = lsp:loadText(uri) + if not text then + return nil, '找不到文件:' .. uri + end + local start_clock = os.clock() + -- lua是从1开始的,因此都要+1 + local pos = parser.calcline.position_utf8(text, params.position.line + 1, params.position.character + 1) + local suc, start, finish = matcher.definition(text, pos) + if not suc then + if finish then + log.debug(start, uri) + finish.lua = nil + log.debug(table.dump(finish)) + end + return {} + end + + local start_row, start_col = parser.calcline.rowcol_utf8(text, start) + local finish_row, finish_col = parser.calcline.rowcol_utf8(text, finish) + + local response = { + uri = uri, + range = { + start = { + line = start_row - 1, + character = start_col - 1, + }, + ['end'] = { + line = finish_row - 1, + -- 这里不用-1,因为前端期待的是匹配完成后的位置 + character = finish_col, + }, + }, + } + local passed_clock = os.clock() - start_clock + if passed_clock >= 0.01 then + log.warn(('[转到定义]耗时[%.3f]秒,文件大小[%s]字节'):format(passed_clock, #text)) + end + + return response +end diff --git a/server/src/method/textDocument/didChange.lua b/server/src/method/textDocument/didChange.lua new file mode 100644 index 00000000..6856b729 --- /dev/null +++ b/server/src/method/textDocument/didChange.lua @@ -0,0 +1,7 @@ +return function (lsp, params) + local doc = params.textDocument + local change = params.contentChanges + -- TODO 支持差量更新 + lsp:saveText(doc.uri, doc.version, change[1].text) + return true +end diff --git a/server/src/method/textDocument/didClose.lua b/server/src/method/textDocument/didClose.lua new file mode 100644 index 00000000..d4edb624 --- /dev/null +++ b/server/src/method/textDocument/didClose.lua @@ -0,0 +1,5 @@ +return function (lsp, params) + local doc = params.textDocument + lsp:removeText(doc.uri, doc.version) + return true +end diff --git a/server/src/method/textDocument/didOpen.lua b/server/src/method/textDocument/didOpen.lua new file mode 100644 index 00000000..27fdda71 --- /dev/null +++ b/server/src/method/textDocument/didOpen.lua @@ -0,0 +1,5 @@ +return function (lsp, params) + local doc = params.textDocument + lsp:saveText(doc.uri, doc.version, doc.text) + return true +end diff --git a/server/src/method/textDocument/implementation.lua b/server/src/method/textDocument/implementation.lua new file mode 100644 index 00000000..6d3cd0ac --- /dev/null +++ b/server/src/method/textDocument/implementation.lua @@ -0,0 +1,51 @@ +local parser = require 'parser' +local matcher = require 'matcher' + +return function (lsp, params) + local uri = params.textDocument.uri + local text = lsp:loadText(uri) + if not text then + return nil, '找不到文件:' .. uri + end + local start_clock = os.clock() + -- lua是从1开始的,因此都要+1 + local pos = parser.calcline.position_utf8(text, params.position.line + 1, params.position.character + 1) + local suc, results, info = matcher.implementation(text, pos) + if not suc then + if info then + log.debug(results, uri) + info.lua = nil + log.debug(table.dump(info)) + end + return {} + end + + local locations = {} + for i, result in ipairs(results) do + local start, finish = result[1], result[2] + local start_row, start_col = parser.calcline.rowcol_utf8(text, start) + local finish_row, finish_col = parser.calcline.rowcol_utf8(text, finish) + locations[i] = { + uri = uri, + range = { + start = { + line = start_row - 1, + character = start_col - 1, + }, + ['end'] = { + line = finish_row - 1, + -- 这里不用-1,因为前端期待的是匹配完成后的位置 + character = finish_col, + }, + } + } + end + + local response = locations + local passed_clock = os.clock() - start_clock + if passed_clock >= 0.01 then + log.warn(('[转到实现]耗时[%.3f]秒,文件大小[%s]字节'):format(passed_clock, #text)) + end + + return response +end diff --git a/server/src/parser/calcline.lua b/server/src/parser/calcline.lua new file mode 100644 index 00000000..26f475d9 --- /dev/null +++ b/server/src/parser/calcline.lua @@ -0,0 +1,93 @@ +local m = require 'lpeglabel' + +local row +local fl +local NL = (m.P'\r\n' + m.S'\r\n') * m.Cp() / function (pos) + row = row + 1 + fl = pos +end +local ROWCOL = (NL + m.P(1))^0 +local function rowcol(str, n) + row = 1 + fl = 1 + ROWCOL:match(str:sub(1, n)) + local col = n - fl + 1 + return row, col +end + +local function rowcol_utf8(str, n) + row = 1 + fl = 1 + ROWCOL:match(str:sub(1, n)) + return row, utf8.len(str, fl, n) +end + +local function position(str, _row, _col) + local cur = 1 + local row = 1 + while true do + if row == _row then + return cur + _col - 1 + elseif row > _row then + return cur - 1 + end + local pos = str:find('[\r\n]', cur) + if not pos then + return #str + end + row = row + 1 + if str:sub(pos, pos+1) == '\r\n' then + cur = pos + 2 + else + cur = pos + 1 + end + end +end + +local function position_utf8(str, _row, _col) + local cur = 1 + local row = 1 + while true do + if row == _row then + return utf8.offset(str, _col, cur) + elseif row > _row then + return cur - 1 + end + local pos = str:find('[\r\n]', cur) + if not pos then + return #str + end + row = row + 1 + if str:sub(pos, pos+1) == '\r\n' then + cur = pos + 2 + else + cur = pos + 1 + end + end +end + +local NL = m.P'\r\n' + m.S'\r\n' + +local function line(str, row) + local count = 0 + local res + local LINE = m.Cmt((1 - NL)^0, function (_, _, c) + count = count + 1 + if count == row then + res = c + return false + end + return true + end) + local MATCH = (LINE * NL)^0 * LINE + MATCH:match(str) + return res +end + +return { + rowcol = rowcol, + rowcol_utf8 = rowcol_utf8, + position = position, + position_utf8 = position_utf8, + line = line, +} diff --git a/server/src/parser/grammar.lua b/server/src/parser/grammar.lua new file mode 100644 index 00000000..1a4f105d --- /dev/null +++ b/server/src/parser/grammar.lua @@ -0,0 +1,336 @@ +local re = require 'parser.relabel' +local m = require 'lpeglabel' +local calcline = require 'parser.calcline' + +local scriptBuf = '' +local compiled = {} +local parser + +local defs = setmetatable({}, {__index = function (self, key) + self[key] = function (...) + if parser[key] then + return parser[key](...) + end + end + return self[key] +end}) + +defs.nl = (m.P'\r\n' + m.S'\r\n') / function () + if parser.nl then + return parser.nl() + end +end +defs.s = m.S' \t' +defs.S = - defs.s +defs.ea = '\a' +defs.eb = '\b' +defs.ef = '\f' +defs.en = '\n' +defs.er = '\r' +defs.et = '\t' +defs.ev = '\v' +local eof = re.compile '!. / %{SYNTAX_ERROR}' + +local function grammar(tag) + return function (script) + scriptBuf = script .. '\r\n' .. scriptBuf + compiled[tag] = re.compile(scriptBuf, defs) * eof + end +end + +local labels = { + +} + +local function errorpos(lua, pos, err) + local row, col = calcline.rowcol(lua, pos) + local str = calcline.line(lua, row) + return { + lua = lua, + line = row, + pos = col, + err = err, + code = str, + level = 'error', + } +end + +grammar 'Comment' [[ +Comment <- '--' (LongComment / ShortComment) +LongComment <- '[' {:eq: '='* :} '[' CommentClose +CommentClose <- ']' =eq ']' / . CommentClose +ShortComment <- (!%nl .)* +]] + +grammar 'Sp' [[ +Sp <- (Comment / %nl / %s)* +]] + +grammar 'Common' [[ +Cut <- ![a-zA-Z0-9_] +X16 <- [a-fA-F0-9] + +AND <- Sp 'and' Cut +BREAK <- Sp 'break' Cut +DO <- Sp 'do' Cut +ELSE <- Sp 'else' Cut +ELSEIF <- Sp 'elseif' Cut +END <- Sp 'end' Cut +FALSE <- Sp 'false' Cut +FOR <- Sp 'for' Cut +FUNCTION <- Sp 'function' Cut +GOTO <- Sp 'goto' Cut +IF <- Sp 'if' Cut +IN <- Sp 'in' Cut +LOCAL <- Sp 'local' Cut +NIL <- Sp 'nil' Cut +NOT <- Sp 'not' Cut +OR <- Sp 'or' Cut +REPEAT <- Sp 'repeat' Cut +RETURN <- Sp 'return' Cut +THEN <- Sp 'then' Cut +TRUE <- Sp 'true' Cut +UNTIL <- Sp 'until' Cut +WHILE <- Sp 'while' Cut + +Esc <- '\' EChar +EChar <- 'a' -> ea + / 'b' -> eb + / 'f' -> ef + / 'n' -> en + / 'r' -> er + / 't' -> et + / 'v' -> ev + / '\' + / '"' + / "'" + / %nl + / 'z' (%nl / %s)* -> '' + / 'x' X16 X16 + / [0-9] [0-9]? [0-9]? + / 'u{' X16^+1^-6 '}' + +Comp <- Sp CompList +CompList <- '<=' + / '>=' + / '<' + / '>' + / '~=' + / '==' +BOR <- Sp '|' +BXOR <- Sp '~' +BAND <- Sp '&' +Bshift <- Sp BshiftList +BshiftList <- '<<' + / '>>' +Concat <- Sp '..' +Adds <- Sp AddsList +AddsList <- '+' + / '-' +Muls <- Sp MulsList +MulsList <- '*' + / '//' + / '/' + / '%' +Unary <- Sp UnaryList +UnaryList <- 'not' + / '#' + / '-' + / '~' +POWER <- Sp '^' + +PL <- Sp '(' +PR <- Sp ')' +BL <- Sp '[' +BR <- Sp ']' +TL <- Sp '{' +TR <- Sp '}' +COMMA <- Sp ',' +SEMICOLON <- Sp ';' +DOTS <- Sp {} -> DOTSPos + '...' -> DOTS +DOT <- Sp '.' +COLON <- Sp {} -> COLONPos + ':' -> COLON +LABEL <- Sp '::' +ASSIGN <- Sp '=' +]] + +grammar 'Nil' [[ +Nil <- NIL +]] + +grammar 'Boolean' [[ +Boolean <- TRUE + / FALSE +]] + +grammar 'String' [[ +String <- Sp StringDef +StringDef <- '"' (Esc / !%nl !'"' .)* '"' + / "'" (Esc / !%nl !"'" .)* "'" + / '[' {:eq: '='* :} '[' StringClose +StringClose <- ']' =eq ']' / . StringClose +]] + +grammar 'Number' [[ +Number <- Sp NumberDef +NumberDef <- Number16 / Number10 + +Number10 <- Integer10 Float10 +Integer10 <- '0' / [1-9] [0-9]* +Float10 <- ('.' [0-9]*)? ([eE] [+-]? [1-9]? [0-9]*)? + +Number16 <- Integer16 Float16 +Integer16 <- '0' [xX] X16* +Float16 <- ('.' X16*)? ([pP] [+-]? [1-9]? [0-9]*)? +]] + +grammar 'Name' [[ +Name <- Sp {} -> NamePos + {[a-zA-Z_] [a-zA-Z0-9_]*} -> Name +]] + +grammar 'Exp' [[ +Exp <- ExpOr +ExpOr <- ExpAnd (OR ExpAnd)* +ExpAnd <- ExpCompare (AND ExpCompare)* +ExpCompare <- ExpBor (Comp ExpBor)* +ExpBor <- ExpBxor (BOR ExpBxor)* +ExpBxor <- ExpBand (BXOR ExpBand)* +ExpBand <- ExpBshift (BAND ExpBshift)* +ExpBshift <- ExpConcat (Bshift ExpConcat)* +ExpConcat <- ExpAdds (Concat ExpAdds)* +ExpAdds <- ExpMuls (Adds ExpMuls)* +ExpMuls <- ExpUnary (Muls ExpUnary)* +ExpUnary <- (Unary ExpPower) + / ExpPower +ExpPower <- ExpUnit (POWER ExpUnary)* +ExpUnit <- Nil + / Boolean + / String + / Number + / DOTS + / Table + / Function + / Simple + +Simple <- (Prefix (Suffix)*) + -> Simple +Prefix <- PL Exp PR + / Name +ColonName <- (COLON Name) + -> ColonName +Suffix <- DOT Name + / ColonName + / Table + / String + / BL Exp BR + / PL ArgList? PR + +ArgList <- (Arg (COMMA Arg)*)? + -> ArgList +Arg <- DOTS + / Exp + +Table <- TL TableFields? TR +TableFields <- TableField (TableSep TableField)* TableSep? +TableSep <- COMMA / SEMICOLON +TableField <- NewIndex / NewField / Exp +NewIndex <- BL Exp BR ASSIGN Exp +NewField <- Name ASSIGN Exp + +Function <- FunctionLoc / FunctionDef +FunctionLoc <- (LOCAL FUNCTION FuncName PL ArgList PR) -> FunctionLoc + (!END Action)* -> Function + END +FunctionDef <- (FUNCTION FuncName PL ArgList PR) -> FunctionDef + (!END Action)* -> Function + END +FuncName <- (Name (FuncSuffix)*)? + -> FuncName +FuncSuffix <- DOT Name + / ColonName + +-- 纯占位,修改了 `relabel.lua` 使重复定义不抛错 +Action <- !. . +]] + +grammar 'Action' [[ +Action <- SEMICOLON / Do / Break / Return / Label / GoTo / If / For / While / Repeat / Function / Set / Local / Call + +ExpList <- Exp (COMMA Exp)* +NameList <- (Name (COMMA Name)*) -> NameList +SimpleList <- (Simple (COMMA Simple)*) -> SimpleList + +Do <- DO -> DoDef + (!END Action)* -> Do + END + +Break <- BREAK + +Return <- RETURN !END ExpList? + +Label <- LABEL Name LABEL + +GoTo <- GOTO Name + +If <- IfPart + ElseIfPart* + ElsePart? + END + -> EndIf +IfPart <- (IF Exp THEN) -> IfDef + (!ELSEIF !ELSE !END Action)* -> If +ElseIfPart <- (ELSEIF Exp THEN) -> ElseIfDef + (!ELSE !ELSEIF !END Action)* -> ElseIf +ElsePart <- ELSE -> ElseDef + (!END Action)* -> Else + +For <- Loop / In + +Loop <- (FOR LoopStart LoopFinish LoopStep? DO) -> LoopDef + (!END Action)* -> Loop + END +LoopStart <- (Name ASSIGN Exp) -> LoopStart +LoopFinish <- COMMA Exp +LoopStep <- COMMA Exp + +In <- (FOR NameList IN ExpList DO) -> InDef + (!END Action)* -> In + END + +While <- (WHILE Exp DO) -> WhileDef + (!END Action)* -> While + END + +Repeat <- REPEAT -> RepeatDef + (!UNTIL Action)* -> Repeat + (UNTIL Exp) -> Until + +Set <- (LOCAL NameList ASSIGN ExpList) + -> LocalSet + / (SimpleList ASSIGN ExpList) + -> Set + +Local <- LOCAL NameList + -> LocalVar + +Call <- Prefix (Suffix)* +]] + +grammar 'Lua' [[ +Lua <- (Sp Action)* Sp +]] + +return function (lua, mode, parser_) + parser = parser_ or {} + mode = mode or 'lua' + local r, e, pos = compiled[mode]:match(lua) + if not r then + local err = errorpos(lua, pos, e) + return nil, err + end + + return r +end diff --git a/server/src/parser/init.lua b/server/src/parser/init.lua new file mode 100644 index 00000000..3216fa39 --- /dev/null +++ b/server/src/parser/init.lua @@ -0,0 +1,7 @@ +local api = { + grammar = require 'parser.grammar', + split = require 'parser.split', + calcline = require 'parser.calcline', +} + +return api diff --git a/server/src/parser/relabel.lua b/server/src/parser/relabel.lua new file mode 100644 index 00000000..bc832017 --- /dev/null +++ b/server/src/parser/relabel.lua @@ -0,0 +1,364 @@ +-- $Id: re.lua,v 1.44 2013/03/26 20:11:40 roberto Exp $ + +-- imported functions and modules +local tonumber, type, print, error = tonumber, type, print, error +local pcall = pcall +local setmetatable = setmetatable +local tinsert, concat = table.insert, table.concat +local rep = string.rep +local m = require"lpeglabel" + +-- 'm' will be used to parse expressions, and 'mm' will be used to +-- create expressions; that is, 're' runs on 'm', creating patterns +-- on 'mm' +local mm = m + +-- pattern's metatable +local mt = getmetatable(mm.P(0)) + + + +-- No more global accesses after this point +local version = _VERSION +if version == "Lua 5.2" then _ENV = nil end + + +local any = m.P(1) +local dummy = mm.P(false) + + +local errinfo = { + NoPatt = "no pattern found", + ExtraChars = "unexpected characters after the pattern", + + ExpPatt1 = "expected a pattern after '/'", + + ExpPatt2 = "expected a pattern after '&'", + ExpPatt3 = "expected a pattern after '!'", + + ExpPatt4 = "expected a pattern after '('", + ExpPatt5 = "expected a pattern after ':'", + ExpPatt6 = "expected a pattern after '{~'", + ExpPatt7 = "expected a pattern after '{|'", + + ExpPatt8 = "expected a pattern after '<-'", + + ExpPattOrClose = "expected a pattern or closing '}' after '{'", + + ExpNumName = "expected a number, '+', '-' or a name (no space) after '^'", + ExpCap = "expected a string, number, '{}' or name after '->'", + + ExpName1 = "expected the name of a rule after '=>'", + ExpName2 = "expected the name of a rule after '=' (no space)", + ExpName3 = "expected the name of a rule after '<' (no space)", + + ExpLab1 = "expected a label after '{'", + + ExpNameOrLab = "expected a name or label after '%' (no space)", + + ExpItem = "expected at least one item after '[' or '^'", + + MisClose1 = "missing closing ')'", + MisClose2 = "missing closing ':}'", + MisClose3 = "missing closing '~}'", + MisClose4 = "missing closing '|}'", + MisClose5 = "missing closing '}'", -- for the captures + + MisClose6 = "missing closing '>'", + MisClose7 = "missing closing '}'", -- for the labels + + MisClose8 = "missing closing ']'", + + MisTerm1 = "missing terminating single quote", + MisTerm2 = "missing terminating double quote", +} + +local function expect (pattern, label) + return pattern + m.T(label) +end + + +-- Pre-defined names +local Predef = { nl = m.P"\n" } + + +local mem +local fmem +local gmem + + +local function updatelocale () + mm.locale(Predef) + Predef.a = Predef.alpha + Predef.c = Predef.cntrl + Predef.d = Predef.digit + Predef.g = Predef.graph + Predef.l = Predef.lower + Predef.p = Predef.punct + Predef.s = Predef.space + Predef.u = Predef.upper + Predef.w = Predef.alnum + Predef.x = Predef.xdigit + Predef.A = any - Predef.a + Predef.C = any - Predef.c + Predef.D = any - Predef.d + Predef.G = any - Predef.g + Predef.L = any - Predef.l + Predef.P = any - Predef.p + Predef.S = any - Predef.s + Predef.U = any - Predef.u + Predef.W = any - Predef.w + Predef.X = any - Predef.x + mem = {} -- restart memoization + fmem = {} + gmem = {} + local mt = {__mode = "v"} + setmetatable(mem, mt) + setmetatable(fmem, mt) + setmetatable(gmem, mt) +end + + +updatelocale() + + + +local I = m.P(function (s,i) print(i, s:sub(1, i-1)); return i end) + + +local function getdef (id, defs) + local c = defs and defs[id] + if not c then + error("undefined name: " .. id) + end + return c +end + + +local function mult (p, n) + local np = mm.P(true) + while n >= 1 do + if n%2 >= 1 then np = np * p end + p = p * p + n = n/2 + end + return np +end + +local function equalcap (s, i, c) + if type(c) ~= "string" then return nil end + local e = #c + i + if s:sub(i, e - 1) == c then return e else return nil end +end + + +local S = (Predef.space + "--" * (any - Predef.nl)^0)^0 + +local name = m.C(m.R("AZ", "az", "__") * m.R("AZ", "az", "__", "09")^0) + +local arrow = S * "<-" + +-- a defined name only have meaning in a given environment +local Def = name * m.Carg(1) + +local num = m.C(m.R"09"^1) * S / tonumber + +local String = "'" * m.C((any - "'" - m.P"\n")^0) * expect("'", "MisTerm1") + + '"' * m.C((any - '"' - m.P"\n")^0) * expect('"', "MisTerm2") + + +local defined = "%" * Def / function (c,Defs) + local cat = Defs and Defs[c] or Predef[c] + if not cat then + error("name '" .. c .. "' undefined") + end + return cat +end + +local Range = m.Cs(any * (m.P"-"/"") * (any - "]")) / mm.R + +local item = defined + Range + m.C(any - m.P"\n") + +local Class = + "[" + * (m.C(m.P"^"^-1)) -- optional complement symbol + * m.Cf(expect(item, "ExpItem") * (item - "]")^0, mt.__add) + / function (c, p) return c == "^" and any - p or p end + * expect("]", "MisClose8") + +local function adddef (t, k, exp) + if t[k] then + -- TODO 改了一下这里的代码,重复定义不会抛错 + --error("'"..k.."' already defined as a rule") + else + t[k] = exp + end + return t +end + +local function firstdef (n, r) return adddef({n}, n, r) end + + +local function NT (n, b) + if not b then + error("rule '"..n.."' used outside a grammar") + else return mm.V(n) + end +end + + +local exp = m.P{ "Exp", + Exp = S * ( m.V"Grammar" + + m.Cf(m.V"Seq" * (S * "/" * expect(S * m.V"Seq", "ExpPatt1"))^0, mt.__add) ); + Seq = m.Cf(m.Cc(m.P"") * m.V"Prefix" * (S * m.V"Prefix")^0, mt.__mul); + Prefix = "&" * expect(S * m.V"Prefix", "ExpPatt2") / mt.__len + + "!" * expect(S * m.V"Prefix", "ExpPatt3") / mt.__unm + + m.V"Suffix"; + Suffix = m.Cf(m.V"Primary" * + ( S * ( m.P"+" * m.Cc(1, mt.__pow) + + m.P"*" * m.Cc(0, mt.__pow) + + m.P"?" * m.Cc(-1, mt.__pow) + + "^" * expect( m.Cg(num * m.Cc(mult)) + + m.Cg(m.C(m.S"+-" * m.R"09"^1) * m.Cc(mt.__pow) + + name * m.Cc"lab" + ), + "ExpNumName") + + "->" * expect(S * ( m.Cg((String + num) * m.Cc(mt.__div)) + + m.P"{}" * m.Cc(nil, m.Ct) + + m.Cg(Def / getdef * m.Cc(mt.__div)) + ), + "ExpCap") + + "=>" * expect(S * m.Cg(Def / getdef * m.Cc(m.Cmt)), + "ExpName1") + ) + )^0, function (a,b,f) if f == "lab" then return a + mm.T(b) else return f(a,b) end end ); + Primary = "(" * expect(m.V"Exp", "ExpPatt4") * expect(S * ")", "MisClose1") + + String / mm.P + + Class + + defined + + "%" * expect(m.P"{", "ExpNameOrLab") + * expect(S * m.V"Label", "ExpLab1") + * expect(S * "}", "MisClose7") / mm.T + + "{:" * (name * ":" + m.Cc(nil)) * expect(m.V"Exp", "ExpPatt5") + * expect(S * ":}", "MisClose2") + / function (n, p) return mm.Cg(p, n) end + + "=" * expect(name, "ExpName2") + / function (n) return mm.Cmt(mm.Cb(n), equalcap) end + + m.P"{}" / mm.Cp + + "{~" * expect(m.V"Exp", "ExpPatt6") + * expect(S * "~}", "MisClose3") / mm.Cs + + "{|" * expect(m.V"Exp", "ExpPatt7") + * expect(S * "|}", "MisClose4") / mm.Ct + + "{" * expect(m.V"Exp", "ExpPattOrClose") + * expect(S * "}", "MisClose5") / mm.C + + m.P"." * m.Cc(any) + + (name * -arrow + "<" * expect(name, "ExpName3") + * expect(">", "MisClose6")) * m.Cb("G") / NT; + Label = num + name; + Definition = name * arrow * expect(m.V"Exp", "ExpPatt8"); + Grammar = m.Cg(m.Cc(true), "G") + * m.Cf(m.V"Definition" / firstdef * (S * m.Cg(m.V"Definition"))^0, + adddef) / mm.P; +} + +local pattern = S * m.Cg(m.Cc(false), "G") * expect(exp, "NoPatt") / mm.P + * S * expect(-any, "ExtraChars") + +local function lineno (s, i) + if i == 1 then return 1, 1 end + local adjustment = 0 + -- report the current line if at end of line, not the next + if s:sub(i,i) == '\n' then + i = i-1 + adjustment = 1 + end + local rest, num = s:sub(1,i):gsub("[^\n]*\n", "") + local r = #rest + return 1 + num, (r ~= 0 and r or 1) + adjustment +end + +local function calcline (s, i) + if i == 1 then return 1, 1 end + local rest, line = s:sub(1,i):gsub("[^\n]*\n", "") + local col = #rest + return 1 + line, col ~= 0 and col or 1 +end + + +local function splitlines(str) + local t = {} + local function helper(line) tinsert(t, line) return "" end + helper((str:gsub("(.-)\r?\n", helper))) + return t +end + +local function compile (p, defs) + if mm.type(p) == "pattern" then return p end -- already compiled + p = p .. " " -- for better reporting of column numbers in errors when at EOF + local ok, cp, label, poserr = pcall(function() return pattern:match(p, 1, defs) end) + if not ok and cp then + if type(cp) == "string" then + cp = cp:gsub("^[^:]+:[^:]+: ", "") + end + error(cp, 3) + end + if not cp then + local lines = splitlines(p) + local line, col = lineno(p, poserr) + local err = {} + tinsert(err, "L" .. line .. ":C" .. col .. ": " .. errinfo[label]) + tinsert(err, lines[line]) + tinsert(err, rep(" ", col-1) .. "^") + error("syntax error(s) in pattern\n" .. concat(err, "\n"), 3) + end + return cp +end + +local function match (s, p, i) + local cp = mem[p] + if not cp then + cp = compile(p) + mem[p] = cp + end + return cp:match(s, i or 1) +end + +local function find (s, p, i) + local cp = fmem[p] + if not cp then + cp = compile(p) / 0 + cp = mm.P{ mm.Cp() * cp * mm.Cp() + 1 * mm.V(1) } + fmem[p] = cp + end + local i, e = cp:match(s, i or 1) + if i then return i, e - 1 + else return i + end +end + +local function gsub (s, p, rep) + local g = gmem[p] or {} -- ensure gmem[p] is not collected while here + gmem[p] = g + local cp = g[rep] + if not cp then + cp = compile(p) + cp = mm.Cs((cp / rep + 1)^0) + g[rep] = cp + end + return cp:match(s) +end + + +-- exported names +local re = { + compile = compile, + match = match, + find = find, + gsub = gsub, + updatelocale = updatelocale, + calcline = calcline +} + +if version == "Lua 5.1" then _G.re = re end + +return re diff --git a/server/src/parser/split.lua b/server/src/parser/split.lua new file mode 100644 index 00000000..6ce4a4e7 --- /dev/null +++ b/server/src/parser/split.lua @@ -0,0 +1,9 @@ +local m = require 'lpeglabel' + +local NL = m.P'\r\n' + m.S'\r\n' +local LINE = m.C(1 - NL) + +return function (str) + local MATCH = m.Ct((LINE * NL)^0 * LINE) + return MATCH:match(str) +end diff --git a/server/src/service.lua b/server/src/service.lua new file mode 100644 index 00000000..46a9ca37 --- /dev/null +++ b/server/src/service.lua @@ -0,0 +1,52 @@ +local sleep = require 'ffi.sleep' +local ext = require 'process.ext' +local lsp = require 'lsp' +local Method= require 'method' + +local function listen(self, input, output) + if input then + log.info('指定输入文件,路径为:', input) + fs.create_directories(input:parent_path()) + io.input(io.open(input:string(), 'rb')) + else + ext.set_filemode(io.stdin, 'b') + end + if output then + log.info('指定输出文件,路径为:', output) + fs.create_directories(output:parent_path()) + io.output(io.open(output:string(), 'wb')) + else + ext.set_filemode(io.stdout, 'b') + end + io.output():setvbuf 'no' + + local session = lsp() + session:setInput(function (mode) + return io.read(mode) + end) + session:setOutput(function (buf) + io.write(buf) + end) + session:start(function (method, params) + local f = Method[method] + if f then + local suc, res, res2 = pcall(f, session, params) + if suc then + return res, res2 + else + return nil, '发生运行时错误:' .. res + end + end + return nil, '没有注册方法:' .. method + end) +end + +local mt = { + listen = listen, +} +mt.__index = mt + +return function () + local session = setmetatable({}, mt) + return session +end diff --git a/server/src/utility/init.lua b/server/src/utility/init.lua new file mode 100644 index 00000000..55bd6b0d --- /dev/null +++ b/server/src/utility/init.lua @@ -0,0 +1,2 @@ +require 'utility.io' +require 'utility.table' diff --git a/server/src/utility/io.lua b/server/src/utility/io.lua new file mode 100644 index 00000000..73c65237 --- /dev/null +++ b/server/src/utility/io.lua @@ -0,0 +1,21 @@ +function io.load(file_path) + local f, e = io.open(file_path:string(), 'rb') + if not f then + return nil, e + end + local buf = f:read 'a' + f:close() + return buf +end + +function io.save(file_path, content) + local f, e = io.open(file_path:string(), "wb") + + if f then + f:write(content) + f:close() + return true + else + return false, e + end +end diff --git a/server/src/utility/table.lua b/server/src/utility/table.lua new file mode 100644 index 00000000..37a74632 --- /dev/null +++ b/server/src/utility/table.lua @@ -0,0 +1,62 @@ +local table_sort = table.sort +local string_rep = string.rep +local type = type +local pairs = pairs +local ipairs = ipairs +local math_type = math.type + +local TAB = setmetatable({}, { __index = function (self, n) + self[n] = string_rep('\t', n) + return self[n] +end}) + +local KEY = {} + +function table.dump(tbl) + if type(tbl) ~= 'table' then + error('必须是表') + end + local table_mark = {} + local lines = {} + lines[#lines+1] = '{' + local function unpack(tbl, tab) + if table_mark[tbl] then + error('不能循环引用') + end + table_mark[tbl] = true + local keys = {} + for key in pairs(tbl) do + if type(key) == 'string' then + if key:find('[^%w_]') then + KEY[key] = ('[%q]'):format(key) + else + KEY[key] = key + end + elseif math_type(key) == 'integer' then + KEY[key] = ('[%d]'):format(key) + else + error('必须使用字符串或整数作为键') + end + keys[#keys+1] = key + end + table_sort(keys, function (a, b) + return KEY[a] < KEY[b] + end) + for _, key in ipairs(keys) do + local value = tbl[key] + local tp = type(value) + if tp == 'table' then + lines[#lines+1] = ('%s%s = {'):format(TAB[tab+1], KEY[key]) + unpack(value, tab+1) + lines[#lines+1] = ('%s},'):format(TAB[tab+1]) + elseif tp == 'string' or tp == 'number' or tp == 'boolean' then + lines[#lines+1] = ('%s%s = %q,'):format(TAB[tab+1], KEY[key], value) + else + error(('不支持的值类型[%s]'):format(tp)) + end + end + end + unpack(tbl, 0) + lines[#lines+1] = '}' + return table.concat(lines, '\n') +end diff --git a/server/test/definition/arg.lua b/server/test/definition/arg.lua new file mode 100644 index 00000000..2004d666 --- /dev/null +++ b/server/test/definition/arg.lua @@ -0,0 +1,23 @@ +TEST [[ +local function xx (<!xx!>) + <?xx?> = 1 +end +]] + +TEST [[ +local function x (x, <!...!>) + x = <?...?> +end +]] + +TEST [[ +function mt<!:!>x() + <?self?> = 1 +end +]] + +TEST [[ +function mt:x(<!self!>) + <?self?> = 1 +end +]] diff --git a/server/test/definition/bug.lua b/server/test/definition/bug.lua new file mode 100644 index 00000000..b0e890ca --- /dev/null +++ b/server/test/definition/bug.lua @@ -0,0 +1,15 @@ +TEST [[ +local <!x!> +function _(x) +end +function _() + <?x?> +end +]] + +TEST [[ +function _(<!x!>) + do return end + <?x?> = 1 +end +]] diff --git a/server/test/definition/function.lua b/server/test/definition/function.lua new file mode 100644 index 00000000..1ef6a463 --- /dev/null +++ b/server/test/definition/function.lua @@ -0,0 +1,24 @@ + +TEST [[ +function <!x!> () end +<?x?> = 1 +]] + +TEST [[ +local function <!x!> () end +<?x?> = 1 +]] + +TEST [[ +local x +local function <!x!> () + <?x?> = 1 +end +]] + +TEST [[ +local <!x!> +function x() +end +<?x?> = 1 +]] diff --git a/server/test/definition/init.lua b/server/test/definition/init.lua new file mode 100644 index 00000000..9bd7a1d8 --- /dev/null +++ b/server/test/definition/init.lua @@ -0,0 +1,22 @@ +local matcher = require 'matcher' + +rawset(_G, 'TEST', true) + +function TEST(script) + local start = script:find('<!', 1, true) + 2 + local finish = script:find('!>', 1, true) - 1 + local pos = script:find('<?', 1, true) + 2 + local new_script = script:gsub('<[!?]', ' '):gsub('[!?]>', ' ') + + local suc, a, b = matcher.definition(new_script, pos) + assert(suc) + assert(a == start) + assert(b == finish) +end + +require 'definition.set' +require 'definition.local' +require 'definition.arg' +require 'definition.function' +--require 'definition.table' +require 'definition.bug' diff --git a/server/test/definition/local.lua b/server/test/definition/local.lua new file mode 100644 index 00000000..0737443d --- /dev/null +++ b/server/test/definition/local.lua @@ -0,0 +1,191 @@ +TEST [[ +local <!x!> +<?x?> = 1 +]] + +TEST [[ +local z, y, <!x!> +<?x?> = 1 +]] + +TEST [[ +local <!x!> = 1 +<?x?> = 1 +]] + +TEST [[ +local z, y, <!x!> = 1 +<?x?> = 1 +]] + +TEST [[ +local x +local <!x!> +<?x?> = 1 +]] + +TEST [[ +local <!x!> +do + <?x?> = 1 +end +]] + +TEST [[ +local <!x!> +do + local x +end +<?x?> = 1 +]] + +TEST [[ +local <!x!> +if <?x?> then + local x +end +]] + +TEST [[ +local <!x!> +if x then + local x +elseif <?x?> then + local x +end +]] + +TEST [[ +local <!x!> +if x then + local x +elseif x then + local x +else + local x +end +<?x?> = 1 +]] + +TEST [[ +local <!x!> +if x then + <?x?> = 1 +elseif x then + local x +else + local x +end +]] + +TEST [[ +local <!x!> +for x = 1, 10 do +end +<?x?> = 1 +]] + +TEST [[ +local x +for <!x!> = 1, 10 do + <?x?> = 1 +end +]] + +TEST [[ +local <!x!> +for x in x do +end +<?x?> = 1 +]] + +TEST [[ +local <!x!> +for x in <?x?> do +end +]] + +TEST [[ +local x +for <!x!> in x do + <?x?> = 1 +end +]] + +TEST [[ +local x +for z, y, <!x!> in x do + <?x?> = 1 +end +]] + +TEST [[ +local <!x!> +while <?x?> do +end +]] + +TEST [[ +local <!x!> +while x do + <?x?> = 1 +end +]] + +TEST [[ +local <!x!> +while x do + local x +end +<?x?> = 1 +]] + +TEST [[ +local <!x!> +repeat + <?x?> = 1 +until true +]] + +TEST [[ +local <!x!> +repeat + local x +until true +<?x?> = 1 +]] + +TEST [[ +local <!x!> +repeat +until <?x?> +]] + +TEST [[ +local x +repeat + local <!x!> +until <?x?> +]] + +TEST [[ +local <!x!> +function _() + local x +end +<?x?> = 1 +]] + +TEST [[ +local <!x!> +return function () + <?x?> = 1 +end +]] + +TEST [[ +local <!x!> +local x = function () + <?x?> = 1 +end +]] diff --git a/server/test/definition/set.lua b/server/test/definition/set.lua new file mode 100644 index 00000000..2e48e490 --- /dev/null +++ b/server/test/definition/set.lua @@ -0,0 +1,30 @@ +TEST [[ +<!x!> = 1 +<?x?> = 1 +]] + +TEST [[ +do + <!global!> = 1 +end +<?global?> = 1 +]] + +TEST [[ +<!x!> = 1 +do + local x = 1 +end +<?x?> = 1 +]] + +TEST [[ +x = 1 +do + local <!x!> = 1 + do + x = 2 + end + <?x?> = 1 +end +]] diff --git a/server/test/definition/table.lua b/server/test/definition/table.lua new file mode 100644 index 00000000..13a3b555 --- /dev/null +++ b/server/test/definition/table.lua @@ -0,0 +1,6 @@ +TEST [[ +local t = { + <!x!> = 1, +} +t.<?x?> = 1 +]] diff --git a/server/test/implementation/arg.lua b/server/test/implementation/arg.lua new file mode 100644 index 00000000..2004d666 --- /dev/null +++ b/server/test/implementation/arg.lua @@ -0,0 +1,23 @@ +TEST [[ +local function xx (<!xx!>) + <?xx?> = 1 +end +]] + +TEST [[ +local function x (x, <!...!>) + x = <?...?> +end +]] + +TEST [[ +function mt<!:!>x() + <?self?> = 1 +end +]] + +TEST [[ +function mt:x(<!self!>) + <?self?> = 1 +end +]] diff --git a/server/test/implementation/bug.lua b/server/test/implementation/bug.lua new file mode 100644 index 00000000..b0e890ca --- /dev/null +++ b/server/test/implementation/bug.lua @@ -0,0 +1,15 @@ +TEST [[ +local <!x!> +function _(x) +end +function _() + <?x?> +end +]] + +TEST [[ +function _(<!x!>) + do return end + <?x?> = 1 +end +]] diff --git a/server/test/implementation/function.lua b/server/test/implementation/function.lua new file mode 100644 index 00000000..90b75da8 --- /dev/null +++ b/server/test/implementation/function.lua @@ -0,0 +1,24 @@ + +TEST [[ +function <!x!> () end +<?x?> = 1 +]] + +TEST [[ +local function <!x!> () end +<?x?> = 1 +]] + +TEST [[ +local x +local function <!x!> () + <?x?> = 1 +end +]] + +TEST [[ +local x +function <!x!>() +end +<?x?> = 1 +]] diff --git a/server/test/implementation/if.lua b/server/test/implementation/if.lua new file mode 100644 index 00000000..0da8be1a --- /dev/null +++ b/server/test/implementation/if.lua @@ -0,0 +1,106 @@ +TEST [[ +<!x!> = 1 +if 1 then + x = 1 +else + <?x?> = 1 +end +]] + +TEST [[ +<!x!> = 1 +if 1 then + <!x!> = 1 +else + <!x!> = 1 +end +<?x?> = 1 +]] + +TEST [[ +<!x!> = 1 +if 1 then + <!x!> = 1 +elseif 1 then + <!x!> = 1 +else + <!x!> = 1 +end +<?x?> = 1 +]] + +TEST [[ +<!x!> = 1 +if 1 then + <!x!> = 1 +elseif 1 then + <!x!> = 1 + if 1 then + <!x!> = 1 + end +else + <!x!> = 1 +end +<?x?> = 1 +]] + +TEST [[ +<!x!> = 1 +while true do + <!x!> = 1 +end +<?x?> = 1 +]] + +TEST [[ +<!x!> = 1 +for _ in _ do + <!x!> = 1 +end +<?x?> = 1 +]] + +TEST [[ +<!x!> = 1 +for _ = 1, 1 do + <!x!> = 1 +end +<?x?> = 1 +]] + +TEST [[ +x3 = 1 +repeat + <!x3!> = 1 +until <?x3?> == 1 +]] + +TEST [[ +<!x!> = 1 +repeat + <!x!> = 1 +until 1 +<?x?> = 1 +]] + +TEST [[ +<!x!> = 1 +while 1 do + x = 1 + <!x!> = 1 +end +<?x?> = 1 +]] + +TEST [[ +<!x!> = 1 +if 1 then + if 1 then + x = 1 + end +else + if 1 then + <?x?> = 1 + end +end +]] diff --git a/server/test/implementation/init.lua b/server/test/implementation/init.lua new file mode 100644 index 00000000..de8d9d63 --- /dev/null +++ b/server/test/implementation/init.lua @@ -0,0 +1,57 @@ +local matcher = require 'matcher' + +rawset(_G, 'TEST', true) + +local function catch_target(script) + local list = {} + local cur = 1 + while true do + local start, finish = script:find('<!.-!>', cur) + if not start then + break + end + list[#list+1] = { start + 2, finish - 2 } + cur = finish + 1 + end + return list +end + +local function founded(targets, results) + while true do + local target = table.remove(targets) + if not target then + break + end + for i, result in ipairs(results) do + if target[1] == result[1] and target[2] == result[2] then + table.remove(results, i) + goto CONTINUE + end + end + do return false end + ::CONTINUE:: + end + if #results == 0 then + return true + else + return false + end +end + +function TEST(script) + local target = catch_target(script) + local pos = script:find('<?', 1, true) + 2 + local new_script = script:gsub('<[!?]', ' '):gsub('[!?]>', ' ') + + local suc, result = matcher.implementation(new_script, pos) + assert(suc) + assert(founded(target, result)) +end + +require 'implementation.set' +require 'implementation.local' +require 'implementation.arg' +require 'implementation.function' +require 'implementation.if' +--require 'implementation.table' +require 'implementation.bug' diff --git a/server/test/implementation/local.lua b/server/test/implementation/local.lua new file mode 100644 index 00000000..7e9b3db0 --- /dev/null +++ b/server/test/implementation/local.lua @@ -0,0 +1,191 @@ +TEST [[ +local <!x!> +<?x?> = 1 +]] + +TEST [[ +local z, y, <!x!> +<?x?> = 1 +]] + +TEST [[ +local <!x!> = 1 +<?x?> = 1 +]] + +TEST [[ +local z, y, <!x!> = 1 +<?x?> = 1 +]] + +TEST [[ +local x +local <!x!> +<?x?> = 1 +]] + +TEST [[ +local <!x!> +do + <?x?> = 1 +end +]] + +TEST [[ +local <!x!> +do + local x +end +<?x?> = 1 +]] + +TEST [[ +local <!x!> +if <?x?> then + local x +end +]] + +TEST [[ +local <!x2!> +if x2 then + local x2 +elseif <?x2?> then + local x2 +end +]] + +TEST [[ +local <!x!> +if x then + local x +elseif x then + local x +else + local x +end +<?x?> = 1 +]] + +TEST [[ +local <!x!> +if x then + <?x?> = 1 +elseif x then + local x +else + local x +end +]] + +TEST [[ +local <!x!> +for x = 1, 10 do +end +<?x?> = 1 +]] + +TEST [[ +local x +for <!x!> = 1, 10 do + <?x?> = 1 +end +]] + +TEST [[ +local <!x!> +for x in x do +end +<?x?> = 1 +]] + +TEST [[ +local <!x!> +for x in <?x?> do +end +]] + +TEST [[ +local x +for <!x!> in x do + <?x?> = 1 +end +]] + +TEST [[ +local x +for z, y, <!x!> in x do + <?x?> = 1 +end +]] + +TEST [[ +local <!x!> +while <?x?> do +end +]] + +TEST [[ +local <!x!> +while x do + <?x?> = 1 +end +]] + +TEST [[ +local <!x!> +while x do + local x +end +<?x?> = 1 +]] + +TEST [[ +local <!x!> +repeat + <?x?> = 1 +until true +]] + +TEST [[ +local <!x!> +repeat + local x +until true +<?x?> = 1 +]] + +TEST [[ +local <!x!> +repeat +until <?x?> +]] + +TEST [[ +local x +repeat + local <!x!> +until <?x?> +]] + +TEST [[ +local <!x!> +function _() + local x +end +<?x?> = 1 +]] + +TEST [[ +local <!x!> +return function () + <?x?> = 1 +end +]] + +TEST [[ +local <!x!> +local x = function () + <?x?> = 1 +end +]] diff --git a/server/test/implementation/set.lua b/server/test/implementation/set.lua new file mode 100644 index 00000000..5c4a1a2e --- /dev/null +++ b/server/test/implementation/set.lua @@ -0,0 +1,31 @@ +TEST [[ +<!x!> = 1 +<?x?> = 1 +]] + +TEST [[ +global = 1 +do + <!global!> = 2 +end +<?global?> = 3 +]] + +TEST [[ +<!x!> = 1 +do + local x = 1 +end +<?x?> = 1 +]] + +TEST [[ +x = 1 +do + local x = 1 + do + <!x!> = 2 + end + <?x?> = 1 +end +]] diff --git a/server/test/implementation/table.lua b/server/test/implementation/table.lua new file mode 100644 index 00000000..13a3b555 --- /dev/null +++ b/server/test/implementation/table.lua @@ -0,0 +1,6 @@ +TEST [[ +local t = { + <!x!> = 1, +} +t.<?x?> = 1 +]] diff --git a/server/test/main.lua b/server/test/main.lua new file mode 100644 index 00000000..c33526c6 --- /dev/null +++ b/server/test/main.lua @@ -0,0 +1,31 @@ +require 'filesystem' +ROOT = fs.current_path() +package.path = (ROOT / 'src' / '?.lua'):string() + .. ';' .. (ROOT / 'src' / '?' / 'init.lua'):string() + .. ';' .. (ROOT / 'test' / '?.lua'):string() + .. ';' .. (ROOT / 'test' / '?' / 'init.lua'):string() + +log = require 'log' +log.init(ROOT, ROOT / 'log' / 'test.log') +log.debug('测试开始') + +require 'utility' +require 'global_protect' + +local function main() + local function test(name) + local clock = os.clock() + print(('测试[%s]...'):format(name)) + require(name) + print(('测试[%s]用时[%.3f]'):format(name, os.clock() - clock)) + end + + test 'definition' + test 'implementation' + + print('测试完成') +end + +main() + +log.debug('测试完成') |