diff --git a/.vscode/launch.json b/.vscode/launch.json index c41948255..b39fa4b56 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,10 +8,11 @@ "request": "launch", "stopOnEntry": false, "program": "${workspaceRoot}/test.lua", - "cpath": "${workspaceFolder}/bin/Windows/?.dll", + "luaexe": "${workspaceFolder}/bin/Windows/lua-language-server.exe", + "cpath": null, "arg": [ ], - "luaVersion": "latest", + "luaVersion": "5.4", "consoleCoding": "utf8", "sourceCoding": "utf8", "outputCapture": [ diff --git a/changelog.md b/changelog.md index 96177af49..d7e0ee3b8 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,14 @@ # changelog +## 2.4.1 +`2021-10-2` +* `FIX` broken with single file +* `FIX` [#698](https://github.com/sumneko/lua-language-server/issues/698) +* `FIX` [#699](https://github.com/sumneko/lua-language-server/issues/699) + ## 2.4.0 +`2021-10-1` +* `NEW` loading settings from `.luarc.json` * `NEW` settings: + `Lua.diagnostics.libraryFiles` + `Lua.diagnostics.ignoredFiles` @@ -33,6 +41,7 @@ ---@module 'mylib' local lib -- the same as `local lib = require 'mylib'` ``` +* `NEW` add supports of `skynet` * `CHG` hover: improve showing multi defines * `CHG` hover: improve showing multi comments at enums * `CHG` hover: shows method diff --git a/locale/en-us/script.lua b/locale/en-us/script.lua index c75cfa7dc..5e3983b03 100644 --- a/locale/en-us/script.lua +++ b/locale/en-us/script.lua @@ -254,3 +254,14 @@ An error occurred in the plugin, please report it to the plugin author. Please check the details in the output or log. Plugin path: {} ]] +PLUGIN_TRUST_LOAD = [[ +The current settings try to load the plugin at this location:{} + +Note that malicious plugin may harm your computer +]] +PLUGIN_TRUST_YES = [[ +Trust and load this plugin +]] +PLUGIN_TRUST_NO = [[ +Don't load this plugin +]] diff --git a/locale/zh-cn/script.lua b/locale/zh-cn/script.lua index 025efa758..39cede8ae 100644 --- a/locale/zh-cn/script.lua +++ b/locale/zh-cn/script.lua @@ -253,3 +253,14 @@ PLUGIN_RUNTIME_ERROR = [[ 请在输出或日志中查看详细信息。 插件路径:{} ]] +PLUGIN_TRUST_LOAD = [[ +当前设置试图加载位于此位置的插件:{} + +注意,恶意的插件可能会危害您的电脑 +]] +PLUGIN_TRUST_YES = [[ +信任并加载插件 +]] +PLUGIN_TRUST_NO = [[ +不要加载此插件 +]] diff --git a/make.lua b/make.lua index 638c6b907..ab99d0d53 100644 --- a/make.lua +++ b/make.lua @@ -1,6 +1,12 @@ local lm = require 'luamake' +local platform = require 'bee.platform' +local fs = require 'bee.filesystem' +local exe = platform.OS == 'Windows' and ".exe" or "" -lm.EXE = "lua" +lm.bindir = "bin/"..platform.OS + +lm.EXE_NAME = "lua-language-server" +lm.EXE_DIR = lm.bindir lm.EXE_RESOURCE = "../../make/lua-language-server.rc" lm:import "3rd/bee.lua/make.lua" @@ -14,23 +20,37 @@ lm:lua_dll 'lpeglabel' { } lm:build 'install' { - '$luamake', 'lua', 'make/install.lua', lm.builddir, + '$luamake', 'lua', 'make/install.lua', +} + +lm:copy "copy_bootstrap" { + input = "make/bootstrap.lua", + output = lm.bindir.."/main.lua", +} + +lm:build "bee-test" { + lm.bindir.."/"..lm.EXE_NAME..exe, "3rd/bee.lua/test/test.lua", + pool = "console", deps = { - 'lua', - 'lpeglabel', - 'bee', - } + lm.EXE_NAME, + "copy_bootstrap" + }, } -local fs = require 'bee.filesystem' -local pf = require 'bee.platform' -local exe = pf.OS == 'Windows' and ".exe" or "" -lm:build 'unittest' { - fs.path 'bin' / pf.OS / ('lua-language-server' .. exe), 'test.lua', '-E', +lm:build 'unit-test' { + lm.bindir.."/"..lm.EXE_NAME..exe, 'test.lua', pool = "console", deps = { - 'install', + "bee-test", + "lpeglabel", + }, + windows = { + deps = "lua54" } } -lm:default 'unittest' +lm:default { + "bee-test", + "unit-test", + "install", +} diff --git a/make/bootstrap.lua b/make/bootstrap.lua new file mode 100644 index 000000000..2bbd64f0e --- /dev/null +++ b/make/bootstrap.lua @@ -0,0 +1,38 @@ +local i = 1 +while true do + if arg[i] == '-E' then + elseif arg[i] == '-e' then + i = i + 1 + local expr = assert(arg[i], "'-e' needs argument") + assert(load(expr, "=(command line)"))() + else + break + end + i = i + 1 +end + +if arg[i] == nil then + return +end + +for j = -1, #arg do + arg[j - i] = arg[j] +end +for j = #arg - i + 1, #arg do + arg[j] = nil +end + +local root; do + local sep = package.config:sub(1,1) + local pattern = "["..sep.."][^"..sep.."]+" + root = package.cpath:match("(.+)"..pattern..pattern..pattern.."$") +end + +local fs = require "bee.filesystem" +fs.current_path(fs.path(root)) + +package.path = table.concat({ + root .. "/script/?.lua", + root .. "/script/?/init.lua", +}, ";") +assert(loadfile(arg[0]))(table.unpack(arg)) diff --git a/make/install.lua b/make/install.lua index e80787707..c7f3b3a19 100644 --- a/make/install.lua +++ b/make/install.lua @@ -1,20 +1,8 @@ -local builddir = ... local fs = require 'bee.filesystem' local pf = require 'bee.platform' - local CWD = fs.current_path() local output = CWD / 'bin' / pf.OS -local bindir = CWD / builddir / 'bin' -local exe = pf.OS == 'Windows' and ".exe" or "" -local dll = pf.OS == 'Windows' and ".dll" or ".so" -local OVERWRITE = fs.copy_options.overwrite_existing - -fs.create_directories(output) -fs.copy_file(bindir / 'lpeglabel'..dll, output / 'lpeglabel'..dll, OVERWRITE) -fs.copy_file(bindir / 'bee'..dll, output / 'bee'..dll, OVERWRITE) -fs.copy_file(bindir / 'lua'..exe, output / 'lua-language-server'..exe, OVERWRITE) if pf.OS == 'Windows' then - fs.copy_file(bindir / 'lua54'..dll, output / 'lua54'..dll, OVERWRITE) require 'msvc'.copy_vcrt('x64', output) end diff --git a/meta/3rd/skynet/config.lua b/meta/3rd/skynet/config.lua new file mode 100644 index 000000000..ef137afef --- /dev/null +++ b/meta/3rd/skynet/config.lua @@ -0,0 +1 @@ +words = { "skynet.start" } diff --git a/meta/3rd/skynet/library/skynet.lua b/meta/3rd/skynet/library/skynet.lua new file mode 100644 index 000000000..be8cc4ba6 --- /dev/null +++ b/meta/3rd/skynet/library/skynet.lua @@ -0,0 +1,541 @@ +---@meta +---@alias MESSAGETYPE +---|+ PTYPE_TEXT = 0 文字类型 +---@alias MESSAGENAME +---|+'"lua"' +---|+'"socket"' +---|+'"client"' +---|+'"response"' +---|+'"muliticast"' +---|+'"system"' +---|+'"harbor"' +---|+'"error"' +---|+'"queue"' +---|+'"debug"' +---|+'"trace"' +---@alias SERVICEADDR '".servicename"' | '":0000000C"' | integer +local skynet = { + -- read skynet.h + PTYPE_TEXT = 0, + PTYPE_RESPONSE = 1, + PTYPE_MULTICAST = 2, + PTYPE_CLIENT = 3, + PTYPE_SYSTEM = 4, + PTYPE_HARBOR = 5, + PTYPE_SOCKET = 6, + PTYPE_ERROR = 7, + PTYPE_QUEUE = 8, -- used in deprecated mqueue, use skynet.queue instead + PTYPE_DEBUG = 9, + PTYPE_LUA = 10, + PTYPE_SNAX = 11, + PTYPE_TRACE = 12, -- use for debug trace + --- add by self + PTYPE_CLIENT_MERGE = 13, + --- 域逻辑信息,用与服务间通信 + PTYPE_LOGIC = 100, + PTYPE_ROUTER = 101, + PNAME_TEXT = "text", + PNAME_RESPONSE = "response", + PNAME_MULTICAST = "muliticast", + PNAME_CLIENT = "client", + PNAME_SYSTEM = "system", + PNAME_HARBOR = "harbor", + PNAME_SOCKET = "socket", + PNAME_ERROR = "error", + PNAME_QUEUE = "queue", + PNAME_DEBUG = "debug", + PNAME_LUA = "lua", + PNAME_SNAX = "snax", + PNAME_TRACE = "trace", + PNAME_CLIENT_MERGE = "client_merge", + PNAME_LOGIC = "logic", + PNAME_ROUTER = "router", + +} + +-------------- 地址相关 API --------------- + +---* 获取服务自己的整型服务地址 +---@return integer +function skynet.self() +end + +---* 获取服务所属的节点 +---@param addr number +---@return number +function skynet.harbor(addr) +end + +---* 将服务的整型地址转换成一个16进制字符串,若不是整型,则 tostring() 返回 +---* :0000000c +---@param addr SERVICEADDR +---@return string +function skynet.address(addr) +end + +---* skynet.localname(name) 用来查询一个 . 开头的名字对应的地址。它是一个非阻塞 API ,不可以查询跨节点的全局名字。 +---* 返回地址类似 :0000000b +---@return string +function skynet.localname(name) +end + +----------------消息分发和响应相关 API ----------- + +---* 注册一类消息的处理机制 +---* 需要 require 'skynet.manager' +---* skynet.register_protocol { +---* name = "text", +---* id = skynet.PTYPE_TEXT, +---* pack = function(m) return tostring(m) end, +---* unpack = skynet.tostring, +---* } +---@param class table +function skynet.register_protocol(class) +end + +---* 注册特定类消息的处理函数。大多数程序会注册 lua 类消息的处理函数,惯例的写法是: +---* local CMD = {} +---* skynet.dispatch("lua", function(session, source, cmd, ...) +---* local f = assert(CMD[cmd]) +---* f(...) +---* end) +---@param typename MESSAGENAME +---@param func fun(session:integer, source:integer,...) +function skynet.dispatch(typename, func) +end + +---* 向当前会话返回数据 +---* 会自动获取当前线程所关联的会话ID和返回地址 +---* 由于某些历史原因(早期的 skynet 默认消息类别是文本,而没有经过特殊编码),这个 API 被设计成传递一个 C 指针和长度,而不是经过当前消息的 pack 函数打包。或者你也可以省略 size 而传入一个字符串。 +---* 在同一个消息处理的 coroutine 中只可以被调用一次,多次调用会触发异常。 +---* 你需要挂起一个请求,等将来时机满足,再回应它。而回应的时候已经在别的 coroutine 中了。针对这种情况,你可以调用 skynet.response(skynet.pack) 获得一个闭包,以后调用这个闭包即可把回应消息发回。这里的参数 skynet.pack 是可选的,你可以传入其它打包函数,默认即是 skynet.pack 。 +---@param msg lightuserdata +---@param sz integer +function skynet.ret(msg, sz) +end + +---* 与 skynet.ret(skynet.pack(...)) 相等 +function skynet.retpack(...) +end + +---返回一个闭包以进行延迟回应 +---@param pack fun(...):string| userdata,sz +---@return fun(isOK:boolean | 'TEST', ...): +function skynet.response(pack) +end + +---* 一般来说每个请求都需要给出一个响应, +---* 但当我们不需要响应的时候调用这个,就不会给出警告了 +---* 如我们使用 C Gate 将套接字作为会话ID +function skynet.ignoreret() +end + +---设置未知请求的处理函数 +---@param unknown fun(session:number, source:number, msg:lightuserdata, sz:number, prototype:number) +function skynet.dispatch_unknown_request(unknown) +end + +---设置未知响应的处理函数 +---@param unknown fun(session:number, source:number, msg:lightuserdata , sz:number) +function skynet.dispatch_unknown_response(unknown) +end + +---SNLUA 默认的Lua回调 +function skynet.dispatch_message(...) +end + +--------------------序列化相关API--------------- + +---* 可以将一组 lua 对象序列化为一个由 malloc 分配出来的 C 指针加一个数字长度 +---* 你需要考虑 C 指针引用的数据块何时释放的问题。当然,如果你只是将 skynet.pack 填在消息处理框架里时,框架解决了这个管理问题。skynet 将 C 指针发送到其他服务,而接收方会在使用完后释放这个指针。 +---@vararg any +---@return lightuserdata, number +function skynet.pack(...) +end + +---* 将 Lua 多个数据打包成一个 Lua 字符串 +---* 这里不用考虑内存何时释放的问题,而 skynet.pack 如果在消息框架外调用就需要 +---@vararg any +---@return string +function skynet.packstring(...) +end + +---* 将 C 指针 或字符串转换成 Lua 数据 +---@param msg lightuserdata | string +---@param sz? number +---@return ... +function skynet.unpack(msg, sz) +end + +---* 将 C 指针转换成 Lua 字符串 +---@param msg lightuserdata +---@param sz number +---@return string +function skynet.tostring(msg, sz) +end + +---# 将 C 指针释放 +---@param msg lightuserdata +---@param sz number +function skynet.trash(msg, sz) +end + +------------------ 消息推送和远程调用 API ----------------- + +---* **非阻塞API** +---* 这条 API 可以把一条类别为 typename 的消息发送给 address 。它会先经过事先注册的 pack 函数打包 ... 的内容。 +---* 实际上也是利用了 c.send 不过传送的会话ID是0 +---* 接收端接收完毕消息后,默认情况下,消息会由 skynet 释放。具体可以查看 skynet-server.c 中的 dispatch_message 的代码 +---@param addr SERVICEADDR +---@param typename string @类型名 +---@vararg any @传输的数据 +function skynet.send(addr, typename, ...) +end + +---* 向一个服务发送消息 +---* 它和 skynet.send 功能类似,但更细节一些。它可以指定发送地址(把消息源伪装成另一个服务),指定发送的消息的 session 。 +---* 注:address 和 source 都必须是数字地址,不可以是别名。 +---* skynet.redirect 不会调用 pack ,所以这里的 ... 必须是一个编码过的字符串,或是 userdata 加一个长度。 +---@param address number @目标服务地址 +---@param source number @伪装的源地址 +---@param typename string @类型名 +---@param session number @会话ID +---@vararg any @传输的数据 +function skynet.redirect(address, source, typename, session, ...) +end + +---* 向一个服务发送不打包的消息 +---@param addr SERVICEADDR @目标服务地址 +---@param typename string @类型名 +---@param msg lightuserdata +---@param sz number +function skynet.rawsend(addr, typename, msg, sz) +end + +---* **阻塞API** +---* 向一个服务发送消息,并期待得到响应,用了此函数后,当前的线程会挂起,直到响应到达 +---* 若长时间没有响应,会有警告在控制台显示 +---* **挂起期间,状态可能会变更,造成重入** +---* 实际上,他也是利用 c.send 来发送消息,不过传送的会话 ID 是nil,会由引擎来生成这个会话ID +---@param addr SERVICEADDR @目标服务地址 +---@param typename string @类型名 +---@vararg any @传输的数据 +function skynet.call(addr, typename, ...) +end + +---* **阻塞API** +---* 向一个服务,不打包发送数据,并期待得到响应 +---* 收到回应后,也不走 unpack 流程。 +---* 调用了此函数后,当前的线程会挂起,直到响应到达 +---* 挂起期间,状态可能会变更,造成重入 +---@param addr SERVICEADDR @目标服务地址 +---@param typename string @类型名 +---@param msg lightuserdata +---@param sz number +function skynet.rawcall(addr, typename, msg, sz) +end + +--- https://blog.codingnow.com/2020/09/skynet_select.html +---@class request +local request = {} +request.__call = request.add + +---@param obj table # {addr, typename, ...} +function request:add(obj) +end +function request:close() +end +function request:select(timeout) +end + +---@param obj? +---@return request +function skynet.request(obj) +end +---* 返回一个唯一的会话ID +---@return number +function skynet.genid() +end + +---带跟踪的发送一条消息 +---@param tag string +---@param addr SERVICEADDR +---@param typename string +---@param msg lightuserdata +---@param sz number +function skynet.tracecall(tag, addr, typename, msg, sz) +end + +---------------- 服务的启动和退出API --------------- + +---* 为 snlua 服务注册一个启动函数,在 Lua 加载完服务脚本后,并不会立即进行执行,而是会注册一个 0 的定时器,再回来执行。 +---* 执行时,这个启动函数会在 skynet.init 注册的函数之后执行 +---* 这个函数执行完毕后,方可收发消息 +---* 注意,这个函数才会设置 snlua 服务的消息处理回调函数 skynet.dispatch_message,若在此之前就进行调用阻塞API,可能会卡住。 +---* 这是因为,消息回调处理函数才会唤醒挂起的协程 +---* **但是,不要在此函数外面调用 skynet 的阻塞 API ,因为框架将无法唤醒它们。** +---@param start_func fun() +function skynet.start(start_func) +end + +---* 初始化服务,执行 skynet.init 注册的函数 和 start 函数,并向 .launcher 上报结果 +---* 一般不直接使用,而是由 skynet.start 调用 +---@param start fun() +function skynet.init_service(start) +end + +---* 注册一个在 start_func 执行前的执行的函数 +---@param fun fun() +function skynet.init(fun) +end + +--- 用于退出当前的服务。skynet.exit 之后的代码都不会被运行。而且,当前服务被阻塞住的 coroutine 也会立刻中断退出。这些通常是一些 RPC 尚未收到回应。所以调用 skynet.exit() 请务必小心。 +function skynet.exit() +end + +--- 用于启动一个新的 Lua 服务。name 是脚本的名字(不用写 .lua 后缀)。只有被启动的脚本的 start 函数返回后,这个 API 才会返回启动的服务的地址,这是一个阻塞 API 。如果被启动的脚本在初始化环节抛出异常,或在初始化完成前就调用 skynet.exit 退出,skynet.newservice 都会抛出异常。如果被启动的脚本的 start 函数是一个永不结束的循环,那么 newservice 也会被永远阻塞住。 +--- > 启动参数其实是以字符串拼接的方式传递过去的。所以不要在参数中传递复杂的 Lua 对象。接收到的参数都是字符串,且字符串中不可以有空格(否则会被分割成多个参数)。这种参数传递方式是历史遗留下来的,有很多潜在的问题。目前推荐的惯例是,让你的服务响应一个启动消息。在 newservice 之后,立刻调用 skynet.call 发送启动请求。 +function skynet.newservice(name, ...) +end + +--- 启动一个全局唯一服务 +---* global 为 true 表示启动全局服务 ,信息从后面参数获取 +---* global 为其他的,表示在本地启动一个本地唯一的服务,global 就代表了服务名 +---@param global boolean|string +---@vararg any +function skynet.uniqueservice(global, ...) +end + +--- 查询一个全局服务 +---* global 为 true 表示启动全局服务 ,信息从后面参数获取 +---* global 为其他的,表示在本地启动一个本地唯一的服务,global 就代表了服务名 +---@param global boolean|string +---@vararg any +function skynet.queryservice(global, ...) +end + +------------------ 时钟和线程 ------------------------ + +---* 将返回 skynet 节点进程内部时间。这个返回值的数值不是真实时间, 本身意义不大。 +---* 不同节点在同一时刻取到的值也不相同。只有两次调用的差值才有意义。用来测量经过的时间, 单位是 1/100秒。 +---* **(注意:这里的时间片表示小于skynet内部时钟周期的时间片,假如执行了比较费时的操作如超长时间的循环,或者调用了外部的阻塞调用,** +---* **如os.execute('sleep 1'), 即使中间没有skynet的阻塞api调用,两次调用的返回值还是会不同的.)** +---@return number +function skynet.now() +end + +---* 如果你需要做性能分析,可以使用 skynet.hpc ,它会返回精度为 ns (1000000000 分之一 秒)的 64 位整数。 +---@return number +function skynet.hpc() +end + +---* 返回 skynet 节点进程启动的 UTC 时间,以秒为单位 +---@return number +function skynet.starttime() +end + +---返回以秒为单位(精度为小数点后两位)的 UTC 时间。它时间上等价于:skynet.now()/100 + skynet.starttime() +---@return number +function skynet.time() +end + +---* 相当于 skynet.sleep(0) 。 +---* 交出当前服务对 CPU 的控制权。通常在你想做大量的操作,又没有机会调用阻塞 API 时,可以选择调用 yield 让系统跑的更平滑。 +function skynet.yield() +end + +---* 让框架在 ti 个单位时间后,调用 func 这个函数。 +---* 这不是一个阻塞 API ,当前 coroutine 会继续向下运行,而 func 将来会在新的 coroutine 中执行。 +---* skynet 的定时器实现的非常高效,所以一般不用太担心性能问题。不过,如果你的服务想大量使用定时器的话,可以考虑一个更好的方法: +---* 即在一个service里,尽量只使用一个 skynet.timeout ,用它来触发自己的定时事件模块。 +---* 这样可以减少大量从框架发送到服务的消息数量。毕竟一个服务在同一个单位时间能处理的外部消息数量是有限的。 +---* 事实上,这个 API 相当于向引擎 调用 skynet.send 发送了一个请求,会由请求在定时器超时后进行响应, +---@param ti number @ ti 的单位是 0.01秒 +---@param func fun() @回调函数 +function skynet.timeout(ti, func) +end + +---* **阻塞API** +---* 将当前 coroutine 挂起 ti 个单位时间。一个单位是 1/100 秒。 +---* 它是向框架注册一个定时器实现的。框架会在 ti 时间后,发送一个定时器消息来唤醒这个 coroutine 。 +---* 这是一个阻塞 API 。它的返回值会告诉你是时间到了,还是被 skynet.wakeup 唤醒 (返回 "BREAK")。 +---@param ti number ti*0.01s +---@param token? any 默认coroutine.running() +function skynet.sleep(ti, token) +end + +---* skynet.wait(token) 把当前 coroutine 挂起,之后由 skynet.wakeup 唤醒。 +---* 将当前线程移入 sleep 队列 +---* token 必须是唯一的,默认为 coroutine.running() 。 +---@param token any +function skynet.wait(token) +end + +---* skynet.wakeup(token) 唤醒一个被 skynet.sleep 或 skynet.wait 挂起的 coroutine 。 +---* 这会将一个处于 挂起状态,sleep 队列中的线程移到待唤醒的队列中,一旦主线程有一个挂起其他线程的操作,就会进行唤醒。 +---* 在 1.0 版中 wakeup 不保证次序,目前的版本则可以保证。 +function skynet.wakeup(token) +end + +---* skynet.fork(func, ...) 从功能上,它等价于 skynet.timeout(0, function() func(...) end) +---* 但是比 timeout 高效一点。因为它并不需要向框架注册一个定时器。 +---* fork 出来的线程的执行时机,是在处理完一条消息时。(skynet.start 内调用此 API 可以保证被触发执行,因为 start 注册的函数是以定时器的形式触发执行的) +---@param func function +---@vararg any +function skynet.fork(func, ...) +end + +-------------- 日志跟踪 API ------------- + +---* 写日志 +---@vararg any +function skynet.error(...) +end + +---跟踪一条消息的处理 +---* tag = string.format(":%08x-%d",skynet.self(), traceid +---@param info string notifymsg +function skynet.trace(info) +end + +---返回当前线程的 trace tag +---* tag = string.format(":%08x-%d",skynet.self(), traceid +---@return string +function skynet.tracetag() +end + +---是否打开超时跟踪 +---@param on boolean +function skynet.trace_timeout(on) +end + +---设置消息类型的跟踪标志 +---* true: force on +---* false: force off +---* nil: optional (use skynet.trace() to trace one message) +---@param prototype string|number +---@param flag? boolean +function skynet.traceproto(prototype, flag) +end + +----------------- 其他 API ------------- +---设置回调 +---@param fun function +---@param forward boolean @设置是否是进行转发,如果是 true 那消息将不会由 skynet 释放 +function skynet.callback(fun, forward) + +end + +---干掉一个线程 +---@param thread string | table +function skynet.killthread(thread) +end + +---获取我们为 skynet 设置的环境变量 +---@param key string +---@return any +function skynet.getenv(key) +end + +---为 skynet 设置的环境变量 +---@param key string +---@param value any +function skynet.setenv(key, value) +end + +---返回当前线程的会话ID和协程地址 +---@return number co_session, number co_address +function skynet.context() +end + +----------------------状态相关 API ------------------ +---是否 (c.intcommand("STAT", "endless") == 1) +function skynet.endless() +end + +--- 返回队列长度 c.intcommand("STAT", "mqlen") +---@return number +function skynet.mqlen() +end + +--- 返回状态信息 c.intcommand("STAT", what) +--- 可以返回当前服务的性能统计信息,what 可以是以下字符串。 +---* "mqlen" 消息队列中堆积的消息数量。如果消息是均匀输入的,那么 mqlen 不断增长就说明已经过载。你可以在消息的 dispatch 函数中首先判断 mqlen ,在过载发生时做一些处理(至少 log 记录下来,方便定位问题)。 +---* "cpu" 占用的 cpu 总时间。需要在 [[Config]] 配置 profile 为 true 。 +---* "message" 处理的消息条数 +---@param what string +function skynet.stat(what) +end + +---返回任务信息 +---@param ret any +function skynet.task(ret) +end + +function skynet.uniqtask() +end + +function skynet.term(service) +end + +--- 只能调用一次,设置 lua 最多使用的内存字节属 +---@param bytes integer +function skynet.memlimit(bytes) +end + +------------------以下是属于 skynet.manager 中的 api +---* 启动一个C 服务,具体参数要看 C服务是怎么编写的 +---@vararg any +function skynet.launch(...) +end + +--- 可以用来强制关闭别的服务。但强烈不推荐这样做。因为对象会在任意一条消息处理完毕后,毫无征兆的退出。所以推荐的做法是,发送一条消息,让对方自己善后以及调用 skynet.exit 。注:skynet.kill(skynet.self()) 不完全等价于 skynet.exit() ,后者更安全。 +---@param name number|string +function skynet.kill(name) +end + +---* 向引擎发送一个 ABORT 命令,退出自身服务 +function skynet.abort() +end + +---* 给服务注册一个名字 +---@param name string +function skynet.register(name) +end + +---* 给服务命名 以 . 开头的名字是在同一 skynet 节点下有效的 +---* skynet.name(name, skynet.self()) 和 skynet.register(name) 功能等价。 +---@param name string +---@param handle number +function skynet.name(name, handle) +end + +---* 将本服务实现为消息转发器,对一类消息进行转发 +---* 设置指定类型的消息,不由 skynet 框架释放 +---* 对于在 map 中的消息,不进行释放 +---* 不在 map 中的消息,由 此函数中调用 skynet.trash 进行释放 +---@param map table +---@param start_func function +function skynet.forward_type(map, start_func) +end + +---过滤消息再处理。(注:filter 可以将 type, msg, sz, session, source 五个参数先处理过再返回新的 5 个参数。) +---@param f any +---@param start_func any +function skynet.filter(f, start_func) +end + +---给当前 skynet 进程设置一个全局的服务监控。 +---@param service any +---@param query any +function skynet.monitor(service, query) +end + +-----------------------debug API-------------- +---注册一个响应 debug console 中 info 命令的函数 +---* 这个 API 不是由 skynet 模块定义,而是将 skynet 模块传递给 skynet.debug 模块后,由 skynet.debug 模块类似 mixin 的形式定义的 +---@param fun fun() +function skynet.info_func(fun) + +end +return skynet diff --git a/meta/3rd/skynet/library/skynet/cluster.lua b/meta/3rd/skynet/library/skynet/cluster.lua new file mode 100644 index 000000000..2b12b15bb --- /dev/null +++ b/meta/3rd/skynet/library/skynet/cluster.lua @@ -0,0 +1,52 @@ +---@meta +---* cluster 相关的库 +---* 这个库,使用了一个叫 clusterd 的服务来进行工作 +---@class cluster +local cluster = {} + +---* 对某个节点上的服务传输消息 +---@param node string @配置中给出的节点名 +---@param address string | number @推荐使用 . 开头本地服务名,因为没有必要再用 master/slave 模式 +---@varargs any @传输数据 +function cluster.call(node, address, ...) +end +---* 对某个节点上的服务传输消息 +---* 越节点推送消息有丢失消息的风险。因为 cluster 基于 tcp 连接,当 cluster 间的连接断开,cluster.send 的消息就可能丢失。而这个函数会立刻返回,所以调用者没有机会知道发送出错。 +---@param node string @配置中给出的节点名 +---@param address string | number @推荐使用 . 开头本地服务名,因为没有必要再用 master/slave 模式 +---@varargs any @传输数据 +function cluster.send(node, address, ...) +end +---* 打开节点 +---* 如果参数是一个节点名,那么会从配置文件中加载的名称对应的IP和端口进行监听 +---* 实际上就是开了一个 gate 服务,监听套接字 +---@param port string | number @节点名或者是端口号 +function cluster.open(port) +end +---* 重新加载节点配置信息 +---* Cluster 是去中心化的,所以需要在每台机器上都放置一份配置文件(通常是相同的)。 +---* 通过调用 cluster.reload 可以让本进程重新加载配置。 +---* 如果你修改了每个节点名字对应的地址,那么 reload 之后的请求都会发到新的地址。 +---* 而之前没有收到回应的请求还是会在老地址上等待。如果你老的地址已经无效(通常是主动关闭了进程)那么请求方会收到一个错误。 +---@param config table @ 名称=IP:端口键值对 +function cluster.reload(config) +end +---* 为某个节点上的服务生成一个代理服务 clusterproxy +---@param node string +---@param name string +function cluster.proxy(node, name) +end +function cluster.snax(node, name, address) +end +---* 可以把 addr 注册为 cluster 可见的一个字符串名字 name 。如果不传 addr 表示把自身注册为 name 。 +---@param name string +---@param addr number +function cluster.register(name, addr) +end +---* 查询节点上服务的地址 +---@param node string +---@param name string +---@return number +function cluster.query(node, name) +end +return cluster diff --git a/meta/3rd/skynet/library/skynet/crypt.lua b/meta/3rd/skynet/library/skynet/crypt.lua new file mode 100644 index 000000000..17e516312 --- /dev/null +++ b/meta/3rd/skynet/library/skynet/crypt.lua @@ -0,0 +1,90 @@ +---@meta +---@class crypt +local crypt = {} + +---计算 hash +---@param key any +---@return string +function crypt.hashkey(key) +end +---生成一个8位的 key +---@return string +function crypt.randomkey() +end +---des 加密 +---@param key number +---@param data string +---@param padding number | nil @对齐模式 默认 iso7816_4 +---@return string +function crypt.desencode(key, data, padding) +end +---desc 解密 +---@param key number +---@param data string +---@param padding number | nil @对齐模式 默认 iso7816_4 +---@return string +function crypt.desdecode(key, data, padding) +end +---hex 编码 +---@param data string +---@return string +function crypt.hexencode(data) +end +---hex 解码 +---@param data string +---@return string +function crypt.hexdecode(data) +end +---hmac 签名 +---@param challenge string @挑战消息 +---@param secret string @密钥 +---@return string +function crypt.hmac64(challenge, secret) +end +---hmac md5签名 +---@param msg string +---@param secret string +---@return string +function crypt.hmac64_md5(msg, secret) +end +---dh交换 +---@param key string +---@return string +function crypt.dhexchange(key) +end +---密钥计算 +---@param dhkey string @经过 exchange 后的密钥 +---@param selfkey string @原始 +function crypt.dhsecret(dhkey, selfkey) +end +---base64编码 +---@param msg string +---@return string +function crypt.base64encode(msg) +end +---base64解码 +---@param msg string +---@return string +function crypt.base64decode(msg) +end +---sha1 +---@param msg string +---@return string +function crypt.sha1(msg) +end +function crypt.hmac_sha1() +end +---hmac hash +---@param key string +---@param msg string +---@return string +function crypt.hmac_hash(key, msg) +end +---xor 字符串 +---@param s1 string +---@param key string +---@return lightuserdata +function crypt.xor_str(s1, key) +end + +return crypt diff --git a/meta/3rd/skynet/library/skynet/db/mongo.lua b/meta/3rd/skynet/library/skynet/db/mongo.lua new file mode 100644 index 000000000..7908c97d3 --- /dev/null +++ b/meta/3rd/skynet/library/skynet/db/mongo.lua @@ -0,0 +1,207 @@ +---@meta +local mongo = {} + +---@class mongo_client +---@field host string +---@field port number +---@field username string +---@field password string +---@field authdb string +---@field authmod string +---@field __id number +---@field __sock socketchannel +local mongo_client = {} + +---@class mongo_db +---@field connection mongo_client +---@field name string +---@field full_name string +---@field database mongo_db +---@field _cmd string dbname.$cmd +local mongo_db = {} + +---@class mongo_collection +---@field connection mongo_client +---@field name string +---@field full_name string +---@field database boolean +local mongo_collection = {} + +---@class mongo_cursor +local mongo_cursor = {} +---建立一个客户端 +---@param conf table {host, port, username, password, authdb, authmod} +---@return mongo_client +function mongo.client(conf) +end +---获取一个 mongo_db 对象 +---@param dbname string +---@return mongo_db +function mongo_client:getDB(dbname) +end + +---断开连接 +function mongo_client:disconnect() +end +---以 self.admin:runCommand(...) 来执行命令 +function mongo_client:runCommand(...) +end + +---退出登录 +function mongo_client:logout() +end + +---验证登录 +---@param user string +---@param pass string +function mongo_db:auth(user, pass) + +end +---执行命令 +function mongo_db:runCommand(cmd, cmd_v, ...) +end +---获取集合 +---@param collection string +---@return mongo_collection +function mongo_db:getCollection(collection) +end +---获取集合 +---@param collection string +---@return mongo_collection +function mongo_collection:getCollection(collection) +end + +---向集合插入文档 +---@param doc table +function mongo_collection:insert(doc) +end +---向集合安全的插入数据 +---@param dco table +function mongo_collection:safe_insert(dco) +end + +---插入批量数据 +---@param docs table[] +function mongo_collection:batch_insert(docs) + +end +---安全插入批量数据 +---@param docs table[] +function mongo_collection:safe_batch_insert(docs) + +end +---更新数据 +---@param selector table +---@param update table +---@param upsert boolean +---@param multi boolean +function mongo_collection:update(selector, update, upsert, multi) + +end +---安全更新数据 +---@param selector table +---@param update table +---@param upsert boolean +---@param multi boolean +function mongo_collection:safe_update(selector, update, upsert, multi) + +end + +---删除数据 +---@param selector table +---@param single boolean +function mongo_collection:delete(selector, single) + +end +---安全删除数据 +---@param selector table +---@param single boolean +function mongo_collection:safe_delete(selector, single) + +end +---@param query table +---@param selector table +---@return mongo_cursor +function mongo_collection:find(query, selector) + +end +---@param query table +---@param selector table +---@return table +function mongo_collection:findOne(query, selector) + +end + +---建立索引 +---* collection:createIndex { { key1 = 1}, { key2 = 1 }, unique = true } +---* or collection:createIndex { "key1", "key2", unique = true } +---* or collection:createIndex( { key1 = 1} , { unique = true } ) -- For compatibility +---@param arg1 table +---@param arg2 table +function mongo_collection:createIndex(arg1, arg2) + +end +---建立多个索引 +---@vararg table +function mongo_collection:createIndexs(...) + +end +mongo_collection.ensureIndex = mongo_collection.createIndex + +---删除集合 +function mongo_collection:drop() + +end +--- 删除索引 +---* collection:dropIndex("age_1") +---* collection:dropIndex("*") +---@param indexName string +function mongo_collection:dropIndex(indexName) + +end + +---查找并修改 +---* collection:findAndModify({query = {name = "userid"}, update = {["$inc"] = {nextid = 1}}, }) +---* keys, value type +---* query, table +---* sort, table +---* remove, bool +---* update, table +---* new, bool +---* fields, bool +---* upsert, boolean +---@param doc table +function mongo_collection:findAndModify(doc) + +end + +---排序 +---* cursor:sort { key = 1 } or cursor:sort( {key1 = 1}, {key2 = -1}) +---@param key table +---@param key_v table +function mongo_cursor:sort(key, key_v, ...) +end +---跳过多少行 +---@param amount number +function mongo_cursor:skip(amount) +end +---限制行数 +---@param amount number +function mongo_cursor:limit(amount) +end +---统计行数 +---@param with_limit_and_skip boolean +function mongo_cursor:count(with_limit_and_skip) +end +---是否有下一行 +---@return boolean +function mongo_cursor:hasNext() +end +---下一行 +---@return table +function mongo_cursor:next() +end +---关闭游标 +function mongo_cursor:close() +end +return mongo diff --git a/meta/3rd/skynet/library/skynet/debug.lua b/meta/3rd/skynet/library/skynet/debug.lua new file mode 100644 index 000000000..020326490 --- /dev/null +++ b/meta/3rd/skynet/library/skynet/debug.lua @@ -0,0 +1,17 @@ +---@meta +---@class debug +local debug = {} + +---初始化 debug +---@param skynet table +---@param export table +function debug.init(skynet, export) + +end +---注册一个 debug 指令函数 +---@param name string +---@param fn fun() +function debug.reg_debugcmd(name, fn) + +end +return debug diff --git a/meta/3rd/skynet/library/skynet/habor.lua b/meta/3rd/skynet/library/skynet/habor.lua new file mode 100644 index 000000000..b5b0484df --- /dev/null +++ b/meta/3rd/skynet/library/skynet/habor.lua @@ -0,0 +1,27 @@ +---@meta +---* 多节点相关的 API +---@class harbor +local harbor = {} + +---* 注册一个全局名字。如果 handle 为空,则注册自己。skynet.name 和 skynet.register 是用其实现的。 +---@param name string @服务名称 +---@param handle number | nil @服务地址,可为空,表示注册自己 +function harbor.globalname(name, handle) +end +---* 可以用来查询全局名字或本地名字对应的服务地址。它是一个阻塞调用。 +---@param name string +function harbor.queryname(name) +end +---* 用来监控一个 slave 是否断开。如果 harbor id 对应的 slave 正常,这个 api 将阻塞。当 slave 断开时,会立刻返回。 +---@param id number @harbor id +function harbor.link(id) +end +---* 和 harbor.link 相反。如果 harbor id 对应的 slave 没有连接,这个 api 将阻塞,一直到它连上来才返回。 +---@param id number @harbor id +function harbor.connect(id) +end +---* 用来在 slave 上监控和 master 的连接是否正常。这个 api 多用于异常时的安全退出(因为当 slave 和 master 断开后,没有手段可以恢复)。 +function harbor.linkmaster() +end + +return harbor diff --git a/meta/3rd/skynet/library/skynet/muliticast.lua b/meta/3rd/skynet/library/skynet/muliticast.lua new file mode 100644 index 000000000..9924891e2 --- /dev/null +++ b/meta/3rd/skynet/library/skynet/muliticast.lua @@ -0,0 +1,24 @@ +---@meta +---* 此模块使用了一个唯一服务 multicastd 来进行通信 +---@class multicast +local multicast = {} + +---@class channel_meta +---@field delete fun() @删除频道自身 +---@field publish fun(...) @发布消息 +---@field subscribe fun() @订阅频道,这个必须调用之后才能收到消息 +---@field unsubscribe fun() @取消订阅 + +---@class multicastchannel : channel_meta +---@field channel number @频道ID +---@field __pack fun(...) @打包函数 默认 skynet.pack +---@field __unpack fun(...) @解包函数 默认 skynet.unpack +---@field __dispatch fun(...) @分发函数 + +---新建频道 +---@param conf table +---@return multicastchannel +function multicast.new(conf) +end + +return multicast diff --git a/meta/3rd/skynet/library/skynet/netpack.lua b/meta/3rd/skynet/library/skynet/netpack.lua new file mode 100644 index 000000000..fb0455fe2 --- /dev/null +++ b/meta/3rd/skynet/library/skynet/netpack.lua @@ -0,0 +1,43 @@ +---@meta +---*网络数据打包解包模块 +---*每个包就是 2 个字节 + 数据内容。这两个字节是 Big-Endian 编码的一个数字。数据内容可以是任意字节。 +---@class netpack +local netpack = {} + +---进行分包处理事件判断 +---* netpack的使用者可以通过filter返回的type来分别进行事件处理。 +---* 会返回 data, more, error, open, close, warning 表示事件类型及每个事件需要的参数 +---* 对于SOCKET_DATA事件,filter会进行数据分包,如果分包后刚好有一条完整消息,filter返回的type为”data”,其后跟fd msg size。 如果不止一条消息,那么数据将被依次压入queue参数中,并且仅返回一个type为”more”。 queue是一个userdata,可以通过netpack.pop 弹出queue中的一条消息。 +---* 其余type类型”open”,”error”, “close”分别SOCKET_ACCEPT, SOCKET_ERROR, SOCKET_CLOSE事件。 +---* netpack会尽可能多地分包,交给上层。并且通过一个哈希表保存每个套接字ID对应的粘包,在下次数据到达时,取出上次留下的粘包数据,重新分包. +---@param queue userdata @一个存放消息的队列 +---@param msg lightuserdata @收到的数据 +---@param sz number @收到的数据长度 +---@return string +function netpack.filter(queue, msg, sz) +end +---从队列中弹出一条消息 +---@param queue userdata +---@return fd, msg, sz +function netpack.pop(queue) +end +---* 把一个字符串(或一个 C 指针加一个长度)打包成带 2 字节包头的数据块。 +---* 这和我们我们用 string.pack('>I2') 打包字符串长度,再连上字符串是一样的,不过,这样打包后是在,string pack 是在 Lua 管理,而不是 C 管理 +---* 这个 api 返回一个lightuserdata 和一个 number 。你可以直接送到 socket.write 发送(socket.write 负责最终释放内存)。 +---@param msg string | lightuserdata @当是 lightuserdata 的时候,需要指定 sz +---@param sz number | nil @当是一个字符串的时候,不需要这个参数 +---@return lightuserdata,number +function netpack.pack(msg, sz) +end +---清除一个队列 +---@param queue any +function netpack.clear(queue) +end +---把 handler.message 方法收到的 msg,sz 转换成一个 lua string,并释放 msg 占用的 C 内存。 +---@param msg any +---@param sz any +---@return string +function netpack.tostring(msg, sz) +end + +return netpack diff --git a/meta/3rd/skynet/library/skynet/profile.lua b/meta/3rd/skynet/library/skynet/profile.lua new file mode 100644 index 000000000..24fa06b79 --- /dev/null +++ b/meta/3rd/skynet/library/skynet/profile.lua @@ -0,0 +1,31 @@ +---@meta +--- profile 模块可以帮助统计一个消息处理使用的系统时间 +--- 使用 skynet 内置的 profile 记时而不用系统带的 os.time 是因为 profile 可以剔除阻塞调用的时间,准确统计出当前 coroutine 真正的开销。 +--- > profile.start() 和 profile.stop() 必须在 skynet 线程中调用(记录当前线程),如果在 skynet [[Coroutine]] 中调用的话,请传入指定的 skynet 线程对象,通常可通过 skynet.coroutine.thread() 获得。 +--- 若是需要多服务的跟踪,请使用 +--- skynet.trace() 在一个消息处理流程中,如果调用了这个 api ,将开启消息跟踪日志。每次调用都会生成一个唯一 tag ,所有当前执行流,和调用到的其它服务,都会计入日志中。具体解释,可以参考 +local profile = {} + +---开始,返回的时间单位是 秒 +---@return number time +function profile.start() + +end +---结束,返回的时间单位是秒 +---@return number time +function profile.stop() + +end +function profile.resume() + +end +function profile.resume_co() + +end +function profile.yield() + +end +function profile.yield_co() + +end +return profile diff --git a/meta/3rd/skynet/library/skynet/sharedata.lua b/meta/3rd/skynet/library/skynet/sharedata.lua new file mode 100644 index 000000000..47417950c --- /dev/null +++ b/meta/3rd/skynet/library/skynet/sharedata.lua @@ -0,0 +1,39 @@ +---@meta +local sharedata = {} +---* 不可进行频繁的数据共享 +---* 在当前节点内创建一个共享数据对象。 +---* value 可以是一张 lua table ,但不可以有环。且 key 必须是字符串或 32bit 正整数。 +---* value 还可以是一段 lua 文本代码,而 sharedata 模块将解析这段代码,把它封装到一个沙盒中运行,最终取得它返回的 table。如果它不返回 table ,则采用它的沙盒全局环境。 +---* 如果 value 是一个以 @ 开头的字符串,这个字符串会被解释为一个文件名。sharedata 模块将加载该文件名指定的文件。 +---@param name string +---@param value table | string +function sharedata.new(name, value) +end +---* 更新当前节点的共享数据对象。 +---* 所有持有这个对象的服务都会自动去数据源头更新数据。但由于这是一个并行的过程,更新并不保证立刻生效。但使用共享数据的读取方一定能在同一个时间片(单次 skynet 消息处理过程)访问到同一版本的共享数据。 +---* 更新过程是惰性的,如果你持有一个代理对象,但在更新数据后没有访问里面的数据,那么该代理对象会一直持有老版本的数据直到第一次访问。这个行为的副作用是:老版本的 C 对象会一直占用内存。如果你需要频繁更新数据,那么,为了加快内存回收,可以通知持有代理对象的服务在更新后,主动调用 sharedata.flush() 。 +---@param name string +---@param value table | string +function sharedata.update(name, value) +end + +---通知代理对象来更新数据 +---一般在 update 后 +function sharedata.flush() +end + +---* 删除当前节点的共享数据对象。 +---@param name string +function sharedata.delete(name) +end +---深拷贝 +---@param name string +---@vararg string x.y.z +function sharedata.deepcopy(name, ...) +end + +---获取当前节点的共享数据对象。 +function sharedata.query(name) +end + +return sharedata diff --git a/meta/3rd/skynet/library/skynet/sharemap.lua b/meta/3rd/skynet/library/skynet/sharemap.lua new file mode 100644 index 000000000..9be194289 --- /dev/null +++ b/meta/3rd/skynet/library/skynet/sharemap.lua @@ -0,0 +1,52 @@ +---@meta +---@class sharemapwriter +local sharemapwriter = {} +---@class sharemapreader +local sharemapreader = {} +---* 一个使用 stm 模块实现的,用于在服务间共享数据的模块 +---* 这里使用了 sproto 来描述序列化数据 +---* 其内部引用了代表 Lua 的源数据 +---* 和由 stm 构造的 stmobj +---@class sharemap +local sharemap = {} +---* 注册 sproto 协议描述文件 +function sharemap.register(protofile) +end +---* 将 Lua 数据,按 sproto 描述中的 某一类型进行序列化后,构造 stm 对象 +---@param typename string +---@param obj any +---@return sharemapwriter +function sharemap.writer(typename, obj) +end +---* writer 实际上是将一分 Lua 数据,经过 sproto 序列化后,重新放到内存中 +function sharemap:commit() +end +---* writer 使用,生成一个指针,用来传递到其他服务 +function sharemap:copy() +end +---* reader 使用,若stm对象有更新,将内存重新序列化出来。 +function sharemap:update() +end +---* 为一 stm 对象构造一个 reader +---@param typename any +---@param stmcpy any +---@return sharemapreader +function sharemap.reader(typename, stmcpy) +end + +---@type stmobj +sharemapwriter.__obj = nil +sharemapwriter.__data = nil +---@type string +sharemapwriter.__typename = nil +sharemapwriter.copy = sharemap.copy +sharemapwriter.commit = sharemap.commit + +sharemapreader.__typename = nil +---@type stmobj +sharemapreader.__obj = nil +---反序列化后的数据 +sharemapreader.__data = nil +---* 调用此方法,会将内存中的数据重新序列化 +sharemapreader.__update = sharemap.update +return sharemap diff --git a/meta/3rd/skynet/library/skynet/sharetable.lua b/meta/3rd/skynet/library/skynet/sharetable.lua new file mode 100644 index 000000000..b65a28f27 --- /dev/null +++ b/meta/3rd/skynet/library/skynet/sharetable.lua @@ -0,0 +1,37 @@ +---@meta +--- 一张表一旦被 query 一次,其数据的生命期将一直维持调用 query 的该服务退出。目前没有手段主动消除对一张共享表的引用。 +---@class sharetable +local sharetable = {} +---* 从一个源文件读取一个共享表,这个文件需要返回一个 table ,这个 table 可以被多个不同的服务读取。... 是传给这个文件的参数。 +---* 可以多次 load 同一个 filename 的表,这样的话,对应的 table 会被更新。使用这张表的服务需要调用 update 更新。 +---@param filename string +---@vararg any 传递的参数 +---@return table +function sharetable.loadfile(filename, ...) +end +---* 和 loadfile 类似,但是是从一个字符串读取。 +---* 推荐使用 sharetable.loadfile 创建这个共享表。 +---* 因为使用 sharetable.loadtable 会经过一次序列化和拷贝,对于太大的表,这个过程非常的耗时。 +---@param filename string +---@param source string +---@vararg any +---@return table +function sharetable.loadstring(filename, source, ...) +end +---* 直接将一个 table 共享。 +---@param filename string +---@param tbl table +---@return table +function sharetable.loadtable(filename, tbl) +end +---* 以 filename 为 key 查找一个被共享的 table 。 +---@param filename string +---@return table +function sharetable.query(filename) +end +---* 更新一个或多个 key 。 +---@vararg ... keys +function sharetable.update(...) +end + +return sharetable diff --git a/meta/3rd/skynet/library/skynet/socket.lua b/meta/3rd/skynet/library/skynet/socket.lua new file mode 100644 index 000000000..53284b134 --- /dev/null +++ b/meta/3rd/skynet/library/skynet/socket.lua @@ -0,0 +1,120 @@ +---@meta +---* 所谓阻塞模式,实际上是利用了 lua 的 coroutine 机制。当你调用 socket api 时,服务有可能被挂起(时间片被让给其他业务处理),待结果通过 socket 消息返回,coroutine 将延续执行。 +---* socketdrver 的 Lua 层表示 +---* 注意与 gateserver 的区别, 他们都会接管 socket 类型的消息 +---@class socket +local socket = {} +---* 作为客户端,连接到一个 IP和端口 +---* 会返回一个代表了 skynet 内部和系统套接字文件描述符相关的结构索引 +---@param addr string @可以是一个IPV4地址,也可以是 'ip:port' 这样的形式,这种形式下,就可以不指定 Port +---@param port number @端口 +---@return number @skynet对套接字描述符的表示 +function socket.open(addr, port) +end +---* 绑定系统文件描述符 +---@param os_fd number +---@return number @skynet对套接字描述符的表示 +function socket.bind(os_fd) +end +---* 等价于 socket.bind(0),绑定到标准输出 +---@return number @skynet对套接字描述符的表示 +function socket.stdin() +end +---* accept 是一个函数。每当一个监听的 id 对应的 socket 上有连接接入的时候,都会调用 accept 函数。这个函数会得到接入连接的 id 以及 ip 地址。你可以做后续操作。 +---* 开始收发套接字数据,并设置一个回调 +---* 这个函数会将套接字索引与一个 Lua 的结构封装起来,并在协程内挂起 (skynet.wait) +---* 当有数据事件到达时,就会 skynet.wakeup 协程来 +---@param id number @skynet套接字索引 +---@param accept fun(...) @事件回调函数 +---@return number | nil, error +function socket.start(id, accept) +end +---* 暂停收发一个套接字上的数据 +---@param id number @skynet套接字索引 +function socket.pause(id) +end +---* 强行关闭一个连接。和 close 不同的是,它不会等待可能存在的其它 coroutine 的读操作。 +---* 一般不建议使用这个 API ,但如果你需要在 __gc 元方法中关闭连接的话,shutdown 是一个比 close 更好的选择(因为在 gc 过程中无法切换 coroutine) +---@param id number @skynet套接字索引 +function socket.shutdown(id) +end +---* 在极其罕见的情况下,需要粗暴的直接关闭某个连接,而避免 socket.close 的阻塞等待流程,可以使用它。 +---@param id number @skynet套接字索引 +function socket.close_fd(id) +end +---* 关闭一个连接,这个 API 有可能阻塞住执行流。因为如果有其它 coroutine 正在阻塞读这个 id 对应的连接,会先驱使读操作结束,close 操作才返回。 +---@param id number @skynet套接字索引 +function socket.close(id) +end +---从一个 socket 上读 sz 指定的字节数。如果读到了指定长度的字符串,它把这个字符串返回。如果连接断开导致字节数不够,将返回一个 false 加上读到的字符串。如果 sz 为 nil ,则返回尽可能多的字节数,但至少读一个字节(若无新数据,会阻塞)。 +---@param id number @skynet套接字索引 +---@param sz number | nil @要读取的字节数,nil 尽可能多的读 +---@return string | nil, string +function socket.read(id, sz) +end +---* 从一个 socket 上读所有的数据,直到 socket 主动断开,或在其它 coroutine 用 socket.close 关闭它。 +---@param id number @skynet套接字索引 +---@return buffer | nil, buffer +function socket.readall(id) +end +---* 从一个 socket 上读一行数据。sep 指行分割符。默认的 sep 为 "\n"。读到的字符串是不包含这个分割符的。 +---@param id number @skynet套接字索引 +---@return buffer | nil, buffer +function socket.readline(id, sep) +end + +---* 等待一个 socket 可读 +---@param id number @skynet套接字索引 +function socket.block(id) +end +---* 是否合法套接字 +---@param id number @skynet套接字索引 +---@return boolean +function socket.invalid(id) +end +---* 是否已断开 +---@param id number @skynet套接字索引 +---@return boolean +function socket.disconnected(id) +end + +---* 监听一个端口,返回一个 id ,供 start 使用。 +---@param host string @地址,可以是 ip:port +---@param port number @断开,可为 nil,此时从 host 内获取端口信息 +---@param backlog number @队列长度 +---@return number @skynet套接字索引 +function socket.listen(host, port, backlog) +end +---* 清除 socket id 在本服务内的数据结构,但并不关闭这个 socket 。这可以用于你把 id 发送给其它服务,以转交 socket 的控制权。 +function socket.abandon(id) +end +---* 设置缓冲区大小 +---@param id number @skynet套接字索引 +---@param limit number @缓冲区大小 +function socket.limit(id, limit) +end +function socket.udp(callback, host, port) +end +function socket.udp_connect(id, addr, port, callback) +end +function socket.warning(id, callback) +end +function socket.onclose(id, callback) +end +---* 把一个字符串置入正常的写队列,skynet 框架会在 socket 可写时发送它。 +---* 这和 socketdriver.send 是一个 +---@see socketdriver#send +---@param id number @skynet套接字索引 +---@param msg string @数据 +---@param sz number @大小 +function socket.write(id, msg, sz) +end +---* 把字符串写入低优先级队列。如果正常的写队列还有写操作未完成时,低优先级队列上的数据永远不会被发出。只有在正常写队列为空时,才会处理低优先级队列。但是,每次写的字符串都可以看成原子操作。不会只发送一半,然后转去发送正常写队列的数据。 +---@param id number @skynet套接字索引 +---@param msg string @数据 +function socket.lwrite() +end +function socket.header() +end + +return socket diff --git a/meta/3rd/skynet/library/skynet/socketchannel.lua b/meta/3rd/skynet/library/skynet/socketchannel.lua new file mode 100644 index 000000000..852e8091a --- /dev/null +++ b/meta/3rd/skynet/library/skynet/socketchannel.lua @@ -0,0 +1,66 @@ +---@meta +---socket channel 在创建时,并不会立即建立连接。如果你什么都不做,那么连接建立会推迟到第一次 request 请求时。这种被动建立连接的过程会不断的尝试,即使第一次没有连接上,也会重试。 +---@class socketchannel +local socket_channel = {} +socket_channel.error = setmetatable( + {}, { + __tostring = function() + return "[Error: socket]" + end, + }) + +---创建一个新的套接字频道 +---返回结构: +---* { +---* __host = assert(desc.host), +---* __port = assert(desc.port), +---* __backup = desc.backup, +---* __auth = desc.auth, +---* __response = desc.response, -- It's for session mode +---* __request = {}, -- request seq { response func or session } -- It's for order mode +---* __thread = {}, -- coroutine seq or session->coroutine map +---* __result = {}, -- response result { coroutine -> result } +---* __result_data = {}, +---* __connecting = {}, +---* __sock = false, +---* __closed = false, +---* __authcoroutine = false, +---* __nodelay = desc.nodelay, +---* __overload_notify = desc.overload, +---* __overload = false, +---* } +---@param desc table {host, port, backup, auth, response, nodelay, overload} +function socket_channel.channel(desc) + +end + +---连接频道 +---@param once boolean tryConnectOnceOrMulti +function socket_channel:connect(once) + +end +---发送请求 +---@param request string +---@param response? fun(sock:table): boolean, string +---@param padding? table +---@return string +function socket_channel:request(request, response, padding) +end + +function socket_channel:response(response) +end +---关闭频道 +function socket_channel:close() + +end +---改变目标主机[端口],打开状态会关闭 +---@param host string +---@param port? number +function socket_channel:changehost(host, port) +end +---改变备用 +---@param backup table +function socket_channel:changebackup(backup) +end +return socket_channel + diff --git a/meta/3rd/skynet/library/skynet/socketdriver.lua b/meta/3rd/skynet/library/skynet/socketdriver.lua new file mode 100644 index 000000000..0719136ee --- /dev/null +++ b/meta/3rd/skynet/library/skynet/socketdriver.lua @@ -0,0 +1,158 @@ +---@meta +---@class socketdriver +local socketdriver = {} +---* 作为客户端,连接到一个 IP和端口 +---* 会返回一个代表了 skynet 内部和系统套接字文件描述符相关的结构索引 +---@param addr string @可以是一个IPV4地址,也可以是 'ip:port' 这样的形式,这种形式下,就可以不指定 Port +---@param port number @端口 +---@return number @skynet对套接字描述符的表示 +function socketdriver.connect(addr, port) +end +---关闭连接 +---* close/shutdown 实际上都会构造指令发给 skynet 引擎,执行同一套逻辑 +---* 不同的是,如果是 shutdown 调用会强制关闭套接字 +---* 而 close 只会在没有更多需要发送的数据才会关闭 +---* 两个函数都会调用操作系统的系统调用 close +---@param id number @skynet对套接字描述符的表示 +function socketdriver.close(id) +end +---关闭连接 +---* close/shutdown 实际上都会构造指令发给 skynet 引擎,执行同一套逻辑 +---* 不同的是,如果是 shutdown 调用会强制关闭套接字 +---* 而 close 只会在没有更多需要发送的数据才会关闭 +---* 两个函数都会调用操作系统的系统调用 close +---@param id number @skynet对套接字描述符的表示 +function socketdriver.shutdown(id) +end +---监听套接字 +---@param host string +---@param port number +---@param backlog number +---@return number @skynet对套接字描述符的表示 +function socketdriver.listen(host, port, backlog) +end +---发送数据,这个函数会将我们要发送的数据放到一个 socket_sendbuffer 内,再丢给 skynet,由网络线程进行发送 +---* socket_sendbuffer 需要一个缓冲区指针、缓冲区类型、长度来表示 +---* 发送的数据,可以是 userdata/lightuserdata/string +---* 当传递的是 userdata 的时候,可以指定 sz ,否则由 lua_rawlen 来计算,由 VM 来释放 +---* 当传递的是 lightuserdata,若不指定 sz,会被当成 SOCKET_BUFFER_OBJECT,由 socket 相关的函数来释放,若指定,则当成SOCKET_BUFFER_MEMORY ,由 free 释放 +---* 当传递的是 table,那么会自动计算长度,SOCKET_BUFFER_MEMORY +---* 默认情况下是当成 string,自动计算长度 +---@param id number @skynet对套接字描述符的表示 +---@param msg table | lightuserdata | userdata | string @要传输的数据 +---@param sz number | nil @长度 +function socketdriver.send(id, msg, sz) +end +---低优先发送数据 +---* 当传递的是 userdata 的时候,可以指定 sz ,否则由 lua_rawlen 来计算,由 VM 来释放 +---* 当传递的是 lightuserdata,若不指定 sz,会被当成 SOCKET_BUFFER_OBJECT,由 socket 相关的函数来释放,若指定,则当成SOCKET_BUFFER_MEMORY ,由 free 释放 +---* 当传递的是 table,那么会自动计算长度 +---* 默认情况下是当成 string,自动计算长度 +---@param id number @skynet对套接字描述符的表示 +---@param msg table | lightuserdata | userdata | string @要传输的数据 +---@param sz number | nil @长度 +function socketdriver.lsend() +end +---绑定系统套接字到一个skynet的索引 +---@param fd number +---@return number @skynet索引 +function socketdriver.bind(fd) +end +---启动收发数据 +---@param id number @skynet对套接字描述符的表示 +function socketdriver.start(id) +end +---暂停收发数据 +---@param id number @skynet对套接字描述符的表示 +function socketdriver.pause(id) +end +---设置 TCP 套接字的 TCP_NODELAY 标志,尽可能快的将数据发出去,而不是等待缓冲区满或到达最大分组才发送 +---@param id number @skynet对套接字描述符的表示 +function socketdriver.nodelay(id) +end +---开启 udp 服务 +---@param addr string +---@param port number | nil +function socketdriver.udp(addr, port) +end +---连接 udp 服务 +---@param id any +---@param addr string +---@param port number | nil +function socketdriver.udp_connect(id, addr, port) +end +function socketdriver.udp_send(id, addr, msg) +end +function socketdriver.udp_address() +end +---解析主机IP +---@param host string +function socketdriver.resolve(host) +end +---新开一个 socket_buffer ,作为 userdata 返回回来 +---* socket_buffer 是一个 buffer_node 链表 +function socketdriver.buffer() +end +---数据放到缓冲区 +---* 表 pool 记录了所有的缓冲区块,位于索引 1 的是一个 lightuserdata: free_node +---* 我们总是可以使用将这个指针当成 buffer_node。 +---* 接下来的索引处都是 userdata,缓冲区块(buffer_node),只有在 VM 关闭的时候才会释放他们。 +---* 索引 2 处的第一块长度是 16 * buffer_node,第二块是 32*buffer_node。 +---* 这个函数会从 pool 处获取一个空闲的 buffer_node,然后将 msg/sz 放到里面。 +---* pop 会将 buffer_node 返回给 pool +---@param buffer userdata +---@param pool table +---@param msg lightuserdata +---@param sz number +---@return number +function socketdriver.push(buffer, pool, msg, sz) +end +---* pop 会将 buffer_node 返回给 pool +---@param buffer userdata +---@param pool table +---@param sz number +function socketdriver.pop(buffer, pool, sz) +end +---丢弃消息 +---@param msg userdata +---@param sz number +---@return string @返回的是 binary string +function socketdriver.drop(msg, sz) +end +---将数据全部读到Lua缓冲区 +---@param buffer userdata +---@param pool table +---@return string @返回的是 binary string +function socketdriver.readall(buffer, pool) +end +---清除缓冲区 +---@param buffer userdata +---@param pool table +function socketdriver.clear(buffer, pool) +end +---读取 sep 分隔符分隔的行 +---@param buffer userdata +---@param pool table +---@param sep string +function socketdriver.readline(buffer, pool, sep) +end +---字符串转 lightuserdata +---@param msg string +---@return lightuserdata,number +function socketdriver.str2p(msg) +end +---获取字符串的长度 +---@param str string +---@return number +function socketdriver.header(str) +end +function socketdriver.info() +end +---解包数据 +---@param msg lightuserdata +---@param sz number +---@return number,number,number, lightuserdata|string +function socketdriver.unpack(msg, sz) +end + +return socketdriver diff --git a/meta/3rd/skynet/library/skynet/stm.lua b/meta/3rd/skynet/library/skynet/stm.lua new file mode 100644 index 000000000..9ebe3f963 --- /dev/null +++ b/meta/3rd/skynet/library/skynet/stm.lua @@ -0,0 +1,36 @@ +---@meta +--- stm 构造的内存对象 +---@class stmobj :userdata +---@deprecated +local stmobj = {} +setmetatable(stmobj, stmobj) +---* 更新对象,实际上就是重新替换一个序列化对象 +stmobj.__call = function(pointer, sz) +end + +---@class stm +local stm = {} +---* 将一个指针,用来构造一个 stm 对象 +---* 任何 Lua 数据想要用 stm 共享,必须先序列化,如 sharemap 使用 sproto 来进行 +---* 我们也可以直接使用 skynet.pack skynet.unpack 来序列化 +---@param pointer userdata | string +---@param sz number +---@return stmobj +---@deprecated +function stm.new(pointer, sz) +end +---* 可以从一个共享对象中生成一份读拷贝。它返回一个 stmcopy ,是一个 lightuserdata 。 +---* 通常一定要把这个 lightuserdata 传到需要读取这份数据的服务。随意丢弃这个指针会导致内存泄露。 +---* 注:一个拷贝只能供一个读取者使用,你不可以把这个指针传递给多个服务。如果你有多个读取者,为每个人调用 copy 生成一份读拷贝。 +---@param stmobj stmobj +---@return lightuserdata +function stm.copy(stmobj) +end +---* 把一个 C 指针转换为一份读拷贝。只有经过转换,stm 才能正确的管理它的生命期。 +---* 一般来说,其他服务就传递这个指针到另外服务,收到了这个指针后,需要调用此 API 转换成一个 内存对象 +---* 再经过反序列化函数才能得到 Lua 的表示 +---@param stmcopy lightuserdata +---@return userdata +function stm.newcopy(stmcopy) +end +return stm diff --git a/meta/template/string.lua b/meta/template/string.lua index a2c809084..c06570e8c 100644 --- a/meta/template/string.lua +++ b/meta/template/string.lua @@ -78,7 +78,7 @@ function string.lower(s) end ---@param s string ---@param pattern string ---@param init? integer ----@return string captured +---@return string | number captured function string.match(s, pattern, init) end ---@version >5.3 diff --git a/script/config/loader.lua b/script/config/loader.lua index 436bbee98..5874601ef 100644 --- a/script/config/loader.lua +++ b/script/config/loader.lua @@ -16,33 +16,50 @@ end local m = {} +function m.loadRCConfig(filename) + local path = workspace.getAbsolutePath(filename) + if not path then + return + end + local buf = util.loadFile(path) + if not buf then + return + end + local suc, res = pcall(json.decode, buf) + if not suc then + errorMessage(lang.script('CONFIG_LOAD_ERROR', res)) + return + end + return res +end + function m.loadLocalConfig(filename) - local path = fs.path(workspace.getAbsolutePath(filename)) - local ext = path:extension():string():lower() - local buf = fsu.loadFile(path) + local path = workspace.getAbsolutePath(filename) + if not path then + return + end + local buf = util.loadFile(path) if not buf then - errorMessage(lang.script('CONFIG_LOAD_FAILED', path:string())) + errorMessage(lang.script('CONFIG_LOAD_FAILED', path)) return end - if ext == '.json' then + local firstChar = buf:match '%S' + if firstChar == '{' then local suc, res = pcall(json.decode, buf) if not suc then errorMessage(lang.script('CONFIG_LOAD_ERROR', res)) return end return res - elseif ext == '.lua' then + else local suc, res = pcall(function () - return assert(load(buf, '@' .. path:string(), 't'))() + return assert(load(buf, '@' .. path, 't'))() end) if not suc then errorMessage(lang.script('CONFIG_LOAD_ERROR', res)) return end return res - else - errorMessage(lang.script('CONFIG_TYPE_ERROR', path:string())) - return end end diff --git a/script/core/diagnostics/type-check.lua b/script/core/diagnostics/type-check.lua index ff3a9124d..11678b38f 100644 --- a/script/core/diagnostics/type-check.lua +++ b/script/core/diagnostics/type-check.lua @@ -3,27 +3,211 @@ local guide = require 'parser.guide' local vm = require 'vm' local infer = require 'core.infer' local await = require 'await' -local hasVarargs +local hasVarargs, errType -local function inTypes(param, args) +local tableMap = { + ['table'] = true, + ['array'] = true, + ['ltable'] = true, + ['[]'] = true, +} + +local typeNameMap = { + ['doc.extends.name'] = true, + ['doc.class.name'] = true, + ['doc.alias.name'] = true, + ['doc.type.name'] = true, + ['doc.type.enum'] = true, + ['doc.resume'] = true, + +} + +local function isTable(name) + if tableMap[name] + ---table table + or tableMap[name:sub(1, 5)] + ---string[] + or tableMap[name:sub(-2, -1)] then + return true + end + return false +end + +local function isUserDefineClass(name) + if vm.isBuiltinType(name) then + return false + else + local defs = vm.getDocDefines(name) + for _, v in ipairs(defs) do + if v.type == 'doc.class.name' then + return true + end + end + end + return false +end + +local function isClassOralias(typeName) + if not typeName then + return false + elseif typeNameMap[typeName] + or vm.isBuiltinType(typeName) then + return true + else + return false + end +end + +local function isGeneric(type) + if type.typeGeneric then + return true + end + return false +end + +local function compatibleType(param, args) + if string.sub(param.type, 1, 9) == 'doc.type.' + and not param[1] then + param[1] = string.sub(param.type, 10) + end for _, v in ipairs(args) do - if param[1] == v[1] then + if v[1] == 'any' then return true - elseif param[1] == 'number' - and v[1] == 'integer' then + elseif param[1] == v[1] then + return true + elseif (param[1] == 'number' or param[1] == 'integer') + and (v[1] == 'integer' or v[1] == 'number') then + return true + elseif v[1] == 'string' then + ---处理alias + --@alias searchmode '"ref"'|'"def"' + if param[1] and param[1]:sub(1,1) == '"' then + return true + end + elseif isTable(v.type or v[1]) and isTable(param[1] or param.type) then return true end end return false end -local function excludeSituations(types) - if not types[1] - or not types[1][1] - or types[1].typeGeneric then - return true +-- local function addFatherClass(types, type) +-- if not type[1] then +-- return +-- end +-- local docDefs = vm.getDocDefines(type[1]) +-- for _, doc in ipairs(docDefs) do +-- if doc.parent +-- and doc.parent.type == 'doc.class' +-- and doc.parent.extends then +-- for _, tp in ipairs(doc.parent.extends) do +-- if tp.type == 'doc.extends.name' then +-- types[#types+1] = { +-- [1] = tp[1], +-- type = 'doc.class.name' +-- } +-- end +-- end +-- end +-- end +-- end + +local function addFatherClass(infers) + for k in pairs(infers) do + local docDefs = vm.getDocDefines(k) + for _, doc in ipairs(docDefs) do + if doc.parent + and doc.parent.type == 'doc.class' + and doc.parent.extends then + for _, tp in ipairs(doc.parent.extends) do + if tp.type == 'doc.extends.name' then + infers[tp[1]] = true + end + end + end + end + end +end + +local function getParamTypes(arg) + if not arg then + return false + end + local types + ---处理doc.type.function + if arg.type == 'doc.type.arg' then + if arg.name and arg.name[1] == '...' then + types = { + [1] = { + [1] = '...', + type = 'varargs' + } + } + return true, types + end + types = arg.extends.types + return true, types + ---处理function + elseif arg.type == 'local' then + local argDefs = vm.getDefs(arg) + if #argDefs == 0 then + return false + end + types = {} + ---method, 如果self没有定义为一个class或者type,则认为它为any + if arg.tag == 'self' then + for _, argDef in ipairs(argDefs) do + if argDef.type == 'doc.class.name' + or argDef.type == 'doc.type.name' + or argDef.type == 'doc.type.enum' + or argDef.type == 'doc.type.ltable' then + types[#types+1] = argDef + end + end + if #types == 0 then + return false + end + return true, types + else + for _, argDef in ipairs(argDefs) do + if argDef.type == 'doc.param' and argDef.extends then + types = argDef.extends.types + if argDef.optional then + types[#types+1] = { + [1] = 'nil', + type = 'nil' + } + end + elseif argDef.type == 'doc.type.enum' + or argDef.type == 'doc.type.ltable' then + types[#types+1] = argDef + ---变长参数 + elseif argDef.name and argDef.name[1] == '...' then + types = { + [1] = { + [1] = '...', + type = 'varargs' + } + } + break + end + end + if #types == 0 then + return false + else + return true, types + end + end + ---处理只有一个可变参数 + elseif arg.type == '...' then + types = { + [1] = { + [1] = '...', + type = 'varargs' + } + } + return true, types end - return false end local function getInfoFromDefs(defs) @@ -35,66 +219,177 @@ local function getInfoFromDefs(defs) if def.value then def = def.value end - if mark[def] then - goto CONTINUE + if not mark[def] then + mark[def] = true + if def.type == 'function' + or def.type == 'doc.type.function' then + if def.args then + for _, arg in ipairs(def.args) do + local suc, types = getParamTypes(arg) + if suc then + local plusAlias = {} + for i, tp in ipairs(types) do + local aliasDefs = vm.getDefs(tp) + for _, v in ipairs(aliasDefs) do + ---TODO(arthur) + -- if not v.type then + -- end + if v[1] ~= tp[1] + and isClassOralias(v.type) then + plusAlias[#plusAlias+1] = v + end + if not v[1] or not v.type then + log.warn('type-check: if not v[1] or not v.type') + end + end + plusAlias[#plusAlias+1] = types[i] + end + funcArgsType[#funcArgsType+1] = plusAlias + else + ---如果有一个参数没有定义type,都会跳过检查 + funcArgsType = {} + break + end + end + end + if #funcArgsType > 0 then + paramsTypes[#paramsTypes+1] = funcArgsType + end + end end - mark[def] = true + end + return paramsTypes +end - if def.type == 'function' - or def.type == 'doc.type.function' then - if def.args then - for _, arg in ipairs(def.args) do - local types - if arg.docParam and arg.docParam.extends then - types = arg.docParam.extends.types - ---变长参数 - elseif arg.name and arg.name[1] == '...' then - types = { - [1] = { - [1] = '...', - type = 'varargs' - } - } - elseif arg.type == 'doc.type.arg' then - types = arg.extends.types - else - goto CONTINUE - end - ---如果是很复杂的type,比如泛型什么的,先不检查 - if excludeSituations(types) then - goto CONTINUE - end - funcArgsType[#funcArgsType+1] = types +local function getArgsInfo(callArgs) + local callArgsType = {} + for _, arg in ipairs(callArgs) do + -- local defs = vm.getDefs(arg) + local infers = infer.searchInfers(arg) + if infers['_G'] or infer['_ENV'] then + infers['_G'] = nil + infers['_ENV'] = nil + infers['table'] = true + end + local hasAny = infers['any'] + ---处理继承 + addFatherClass(infers) + if not hasAny then + infers['any'] = nil + infers['unknown'] = nil + end + local types = {} + if not infers['table'] then + for k in pairs(infers) do + if not vm.isBuiltinType(k) + and isUserDefineClass(k) then + infers['table'] = true + break end end - if #funcArgsType > 0 then - paramsTypes[#paramsTypes+1] = funcArgsType + end + for k in pairs(infers) do + if k then + types[#types+1] = { + [1] = k, + type = k + } end end - ::CONTINUE:: + if #types < 1 then + return false + end + types.start = arg.start + types.finish = arg.finish + callArgsType[#callArgsType+1] = types + -- local defs = vm.getDefs(arg) + -- local types = {} + -- types.typeMap = {} + -- for _, def in ipairs(defs) do + -- if vm.isBuiltinType(def.type) then + -- types[#types+1] = { + -- [1] = def.type, + -- type = def.type + -- } + -- elseif def.type == 'doc.class.name' + -- or def.type == 'doc.type.name' + -- or def.type == 'doc.type.enum' + -- or def.type == 'doc.type.ltable' then + -- if def[1] then + -- if not types.typeMap[def[1]] then + -- types[#types+1] = def + -- types.typeMap[def[1]] = true + -- end + -- else + -- types[#types+1] = def + -- end + -- elseif def.type == 'doc.type' then + -- print(1) + -- elseif def.type == 'doc.type.arg' then + -- for _, tp in ipairs(arg.extends.types) do + -- types[#types+1] = arg.extends.types[1] + -- end + -- elseif def.type == 'doc.param' then + -- for i, tp in ipairs(def.extends.types) do + -- types[#types+1] = def.extends.types[i] + -- end + -- if def.optional then + -- types[#types+1] = { + -- [1] = 'nil', + -- type = 'nil' + -- } + -- end + -- end + -- end + -- for _, tp in ipairs(types) do + -- if not vm.isBuiltinType(tp.type) then + -- addFatherClass(types, tp) + -- end + -- end + -- types.start = arg.start + -- types.finish = arg.finish + -- if #types == 0 then + -- types = { + -- [1] = { + -- [1] = 'any', + -- type = 'any', + -- } + -- } + -- end + -- callArgsType[#callArgsType+1] = types end - return paramsTypes + return true, callArgsType end local function matchParams(paramsTypes, i, arg) local flag = '' local messages = {} + ---paramsTypes 存的是多个定义的参数信息 + ---paramTypes 存的是单独一个定义的参数信息 + ---param 是某一个定义中的第i个参数的信息 for _, paramTypes in ipairs(paramsTypes) do if not paramTypes[i] then goto CONTINUE end + flag = '' for _, param in ipairs(paramTypes[i]) do + if param[1] == '...' then + hasVarargs = true + return true ---如果形参的类型在实参里面 - if inTypes(param, arg) - or param[1] == 'any' - or arg.type == 'any' then + elseif compatibleType(param, arg) + or param[1] == 'any' then flag = '' return true - elseif param[1] == '...' then - hasVarargs = true + ---如果是泛型,不检查 + elseif isGeneric(param) then return true else - flag = flag ..' ' .. param[1] + ---TODO(arthur) 什么时候param[1]是nil? + if param[1] and not errType[param[1]] then + errType[param[1]] = true + flag = flag ..' ' .. (param[1] or '') + end end end if flag ~= '' then @@ -113,6 +408,7 @@ local function matchParams(paramsTypes, i, arg) end return false, messages end + return function (uri, callback) local ast = files.getState(uri) if not ast then @@ -124,29 +420,9 @@ return function (uri, callback) end await.delay() local callArgs = source.args - local callArgsType = {} - for _, arg in ipairs(callArgs) do - local infers = infer.searchInfers(arg) - if infers['_G'] or infer['_ENV'] then - infers['_G'] = nil - infers['_ENV'] = nil - infers['table'] = true - end - local types = {} - for k in pairs(infers) do - if k then - types[#types+1] = { - [1] = k, - type = k - } - end - end - if #types < 1 then - return - end - types.start = arg.start - types.finish = arg.finish - callArgsType[#callArgsType+1] = types + local suc, callArgsType = getArgsInfo(callArgs) + if not suc then + return end local func = source.node local defs = vm.getDefs(func) @@ -156,6 +432,7 @@ return function (uri, callback) for i, arg in ipairs(callArgsType) do ---遍历形参 hasVarargs = false + errType = {} local match, messages = matchParams(paramsTypes, i, arg) if hasVarargs then return @@ -173,5 +450,4 @@ return function (uri, callback) end ---所有参数都匹配了 end) - end diff --git a/script/core/infer.lua b/script/core/infer.lua index 997df9a35..f1262d0e6 100644 --- a/script/core/infer.lua +++ b/script/core/infer.lua @@ -4,7 +4,6 @@ local noder = require 'core.noder' local util = require 'utility' local vm = require "vm.vm" -local BE_RETURN = {'BE_RETURN'} local CLASS = {'CLASS'} local TABLE = {'TABLE'} @@ -17,7 +16,6 @@ local TypeSort = { ['function'] = 6, ['true'] = 101, ['false'] = 102, - ['nil'] = 999, } local m = {} @@ -166,10 +164,6 @@ local function searchInferOfValue(value, infers, mark) infers['number'] = true return true end - if value.type == 'nil' then - infers['nil'] = true - return true - end if value.type == 'function' then infers['function'] = true return true @@ -241,9 +235,6 @@ local function cleanInfers(infers) local version = config.get 'Lua.runtime.version' local enableInteger = version == 'Lua 5.3' or version == 'Lua 5.4' infers['unknown'] = nil - if infers['any'] and infers['nil'] then - infers['nil'] = nil - end if infers['number'] then enableInteger = false end @@ -265,10 +256,6 @@ local function cleanInfers(infers) infers[TABLE] = nil infers['table'] = true end - if infers[BE_RETURN] then - infers[BE_RETURN] = nil - infers['nil'] = nil - end if infers['function'] then for k in pairs(infers) do if k:sub(1, 4) == 'fun(' then @@ -276,13 +263,6 @@ local function cleanInfers(infers) end end end - infers['any'] = nil - if infers['nil'] then - infers['nil'] = nil - if not next(infers) then - infers['nil'] = true - end - end end ---合并对象的推断类型 @@ -444,19 +424,15 @@ local function searchInfer(source, infers, mark) end -- check LuaDoc local docName = m.getDocName(source) - if docName then + if docName and docName ~= 'nil' and docName ~= 'unknown' then infers[docName] = true - if docName ~= 'unknown' then - infers[CLASS] = true + if not vm.isBuiltinType(docName) then + infers[CLASS] = true end if docName == 'table' then infers[TABLE] = true end end - -- return XX - if source.parent.type == 'return' then - infers[BE_RETURN] = true - end end local function searchLiteral(source, literals, mark) @@ -635,14 +611,14 @@ function m.getClass(source) local defs = vm.getDefs(source) for _, def in ipairs(defs) do if def.type == 'doc.class.name' then - infers[def[1]] = true + if not vm.isBuiltinType(def[1]) then + infers[def[1]] = true + end end end cleanInfers(infers) - infers['nil'] = nil local view = m.viewInfers(infers) - if view == 'any' - or view == 'nil' then + if view == 'any' then return nil end return view diff --git a/script/library.lua b/script/library.lua index e0c320232..434a3bcfb 100644 --- a/script/library.lua +++ b/script/library.lua @@ -313,8 +313,10 @@ end local function apply3rd(cfg, onlyMemory) local changes = {} - for _, change in ipairs(cfg.configs) do - changes[#changes+1] = change + if cfg.configs then + for _, change in ipairs(cfg.configs) do + changes[#changes+1] = change + end end if cfg.plugin then diff --git a/script/parser/guide.lua b/script/parser/guide.lua index 07bbc0cdf..19c27df5e 100644 --- a/script/parser/guide.lua +++ b/script/parser/guide.lua @@ -62,6 +62,7 @@ local type = type ---@field _noded boolean ---@field _initedNoders boolean ---@field _compiledGlobals boolean +---@field optional boolean ---@class guide ---@field debugMode boolean @@ -286,7 +287,7 @@ function m.getBlock(obj) return obj end if obj == obj.parent then - error('obj == obj.parent?', obj.type) + error('obj == obj.parent?' .. obj.type) end obj = obj.parent end @@ -1014,8 +1015,8 @@ end --- 否则返回 `false` --- --- 返回的2个 `list` 分别为基准block到达 a 与 b 的路径。 ----@param a table ----@param b table +---@param a parser.guide.object +---@param b parser.guide.object ---@return string|boolean mode ---@return table pathA? ---@return table pathB? diff --git a/script/parser/newparser.lua b/script/parser/newparser.lua index 3ef2a1d6a..1fd395e5a 100644 --- a/script/parser/newparser.lua +++ b/script/parser/newparser.lua @@ -54,7 +54,7 @@ local CharMapSign = stringToCharMap '+-' local CharMapSB = stringToCharMap 'ao|~&=<>.*/%^+-' local CharMapSU = stringToCharMap 'n#~!-' local CharMapSimple = stringToCharMap '.:([\'"{' -local CharMapStrSH = stringToCharMap '\'"' +local CharMapStrSH = stringToCharMap '\'"`' local CharMapStrLH = stringToCharMap '[' local CharMapTSep = stringToCharMap ',;' local CharMapWord = stringToCharMap '_a-zA-Z\x80-\xff' @@ -882,18 +882,6 @@ local function parseShortString() local startOffset = Tokens[Index] local startPos = getPosition(startOffset, 'left') Index = Index + 2 - -- empty string - if Tokens[Index+1] == mark then - local finishPos = getPosition(Tokens[Index], 'right') - Index = Index + 2 - return { - type = 'string', - start = startPos, - finish = finishPos, - [1] = '', - [2] = mark, - } - end local stringIndex = 0 local currentOffset = startOffset + 1 while true do @@ -1024,13 +1012,41 @@ local function parseShortString() ::CONTINUE:: end local stringResult = tconcat(stringPool, '', 1, stringIndex) - return { + local str = { type = 'string', start = startPos, finish = lastRightPosition(), [1] = stringResult, [2] = mark, } + if mark == '`' then + if State.options.nonstandardSymbol and State.options.nonstandardSymbol[mark] then + else + pushError { + type = 'ERR_NONSTANDARD_SYMBOL', + start = startPos, + finish = str.finish, + info = { + symbol = '"', + }, + fix = { + title = 'FIX_NONSTANDARD_SYMBOL', + symbol = '"', + { + start = startPos, + finish = startPos + 1, + text = '"', + }, + { + start = str.finish - 1, + finish = str.finish, + text = '"', + }, + } + } + end + end + return str end local function parseString() @@ -3629,7 +3645,9 @@ return function (lua, mode, version, options) elseif mode == 'Action' then State.ast = parseAction() end - State.ast.state = State + if State.ast then + State.ast.state = State + end return State end diff --git a/script/plugin.lua b/script/plugin.lua index 392b92af0..26f39226d 100644 --- a/script/plugin.lua +++ b/script/plugin.lua @@ -52,12 +52,14 @@ end local function checkTrustLoad() local filePath = LOGPATH .. '/trusted' - local trusted = util.loadFile(filePath) or '' + local trusted = util.loadFile(filePath) local lines = {} - for line in util.eachLine(trusted) do - lines[#lines+1] = line - if line == m.pluginPath then - return true + if trusted then + for line in util.eachLine(trusted) do + lines[#lines+1] = line + if line == m.pluginPath then + return true + end end end local _, index = client.awaitRequestMessage('Warning', lang.script('PLUGIN_TRUST_LOAD', m.pluginPath), { @@ -73,6 +75,10 @@ local function checkTrustLoad() end function m.init() + if m.hasInited then + return + end + m.hasInited = true await.call(function () local ws = require 'workspace' m.interface = {} diff --git a/script/provider/provider.lua b/script/provider/provider.lua index 0e4f7dddc..75ed4b4f4 100644 --- a/script/provider/provider.lua +++ b/script/provider/provider.lua @@ -14,6 +14,7 @@ local progress = require 'progress' local tm = require 'text-merger' local cfgLoader = require 'config.loader' local converter = require 'proto.converter' +local filewatch = require 'filewatch' local function updateConfig() local new @@ -21,10 +22,18 @@ local function updateConfig() new = cfgLoader.loadLocalConfig(CONFIGPATH) config.setSource 'path' log.debug('load config from local', CONFIGPATH) + -- watch directory + filewatch.watch(workspace.getAbsolutePath(CONFIGPATH):gsub('[^/\\]+$', '')) else - new = cfgLoader.loadClientConfig() - config.setSource 'client' - log.debug('load config from client') + new = cfgLoader.loadRCConfig('.luarc.json') + if new then + config.setSource 'luarc' + log.debug('load config from luarc') + else + new = cfgLoader.loadClientConfig() + config.setSource 'client' + log.debug('load config from client') + end end if not new then log.warn('load config failed!') @@ -34,6 +43,19 @@ local function updateConfig() log.debug('loaded config dump:', util.dump(new)) end +filewatch.event(function (changes) + local configPath = workspace.getAbsolutePath(CONFIGPATH or '.luarc.json') + if not configPath then + return + end + for _, change in ipairs(changes) do + if change.path == configPath then + updateConfig() + return + end + end +end) + proto.on('initialize', function (params) client.init(params) config.init() diff --git a/test.lua b/test.lua index 2f01bee62..21d10c5d7 100644 --- a/test.lua +++ b/test.lua @@ -1,11 +1,8 @@ -local currentPath = debug.getinfo(1, 'S').source:sub(2) -local rootPath = currentPath:gsub('[/\\]*[^/\\]-$', '') -rootPath = rootPath == '' and '.' or rootPath -loadfile(rootPath .. '/platform.lua')('script') package.path = package.path - .. ';' .. rootPath .. '/test/?.lua' - .. ';' .. rootPath .. '/test/?/init.lua' + .. ';./test/?.lua' + .. ';./test/?/init.lua' local fs = require 'bee.filesystem' +local rootPath = fs.exe_path():parent_path():parent_path():parent_path():string() ROOT = fs.path(rootPath) TEST = true DEVELOP = true diff --git a/test/diagnostics/init.lua b/test/diagnostics/init.lua index 574d93f9e..c526560a3 100644 --- a/test/diagnostics/init.lua +++ b/test/diagnostics/init.lua @@ -1152,9 +1152,6 @@ TEST [[ local emit = {} ]] --- TODO -do return end - TEST [[ ---@param table table ---@param metatable table @@ -1230,3 +1227,96 @@ f(true, true) -- OK f(0, 0) -- OK ]] + +TEST [[ +---@class bird +local m = {} +setmetatable(m, {}) -- OK +]] + +TEST [[ +---@class childString: string +local s +---@param name string +local function f(name) end +f(s) +]] + +TEST [[ +---@class childString: string + +---@type string +local s +---@param name childString +local function f(name) end +f() +]] + +TEST [[ +---@alias searchmode '"ref"'|'"def"'|'"field"'|'"allref"'|'"alldef"'|'"allfield"' + +---@param mode searchmode +local function searchRefs(mode)end +searchRefs('ref') +]] + +TEST [[ +---@class markdown +local mt = {} +---@param language string +---@param text string|markdown +function mt:add(language, text) + if not text then + return + end +end +---@type markdown +local desc + +desc:add('md', 'hover') +]] + +---可选参数和枚举 +TEST [[ +---@param str string +---@param mode? '"left"'|'"right"' +---@return string +local function trim(str, mode) + if mode == "left" then + print(1) + end +end +trim('str', 'left') +trim('str', nil) +]] + +---不完整的函数参数定义,会跳过检查 +TEST [[ +---@param mode string +local function status(source, field, mode) + print(source, field, mode) +end +status(1, 2, 'name') +]] + + +TEST [[ +---@alias range {start: number, end: number} +---@param uri string +---@param range range +local function location(uri, range) + print(uri, range) +end +---@type range +local val = {} +location('uri', val) +]] +---TODO(arthur) +do return end + +TEST [[ +---@type file* +local f +f:read '*a' +f:read('*a') +]] diff --git a/test/hover/init.lua b/test/hover/init.lua index a09f85833..893d86985 100644 --- a/test/hover/init.lua +++ b/test/hover/init.lua @@ -697,8 +697,8 @@ end ]] [[ function f() - -> nil - 2. nil + -> any + 2. any ]] TEST [[ @@ -708,7 +708,7 @@ end local = f() ]] [[ -local x: nil +local x: any ]] TEST [[ @@ -719,7 +719,7 @@ end ]] [[ function f() - -> integer|nil + -> integer ]] TEST [[ diff --git a/test/type_inference/init.lua b/test/type_inference/init.lua index d3ff258c5..ac300fcb2 100644 --- a/test/type_inference/init.lua +++ b/test/type_inference/init.lua @@ -233,6 +233,14 @@ end = x() ]] +TEST 'any' [[ +local function x() + return nil + return f() +end + = x() +]] + TEST 'integer' [[ local function x() return 1