diff --git a/.styluaignore b/.styluaignore index 10482525f2..11aa24df3a 100644 --- a/.styluaignore +++ b/.styluaignore @@ -3,3 +3,4 @@ /test /runtime/lua/vim/re.lua /runtime/lua/vim/_meta/options.lua +/runtime/lua/coxpcall.lua diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e4beb3448..24ca416dbc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -224,7 +224,9 @@ add_glob_target( FLAGS -ll ${PROJECT_SOURCE_DIR}/test/lua_runner.lua ${CMAKE_BINARY_DIR}/usr luacheck -q GLOB_DIRS runtime/ scripts/ src/ test/ GLOB_PAT *.lua - EXCLUDE runtime/lua/vim/_meta/.* + EXCLUDE + runtime/lua/vim/_meta/.* + runtime/lua/coxpcall.lua TOUCH_STRATEGY SINGLE) add_dependencies(lintlua-luacheck lua-dev-deps) @@ -237,6 +239,7 @@ add_glob_target( EXCLUDE /runtime/lua/vim/re.lua /runtime/lua/vim/_meta/.* + /runtime/lua/coxpcall.lua TOUCH_STRATEGY SINGLE) add_custom_target(lintlua) diff --git a/MAINTAIN.md b/MAINTAIN.md index 07e8796ac8..f3fe873a84 100644 --- a/MAINTAIN.md +++ b/MAINTAIN.md @@ -143,6 +143,7 @@ These dependencies are "vendored" (inlined), we must update the sources manually * Run `scripts/gen_lsp.lua` to update. * `src/bit.c`: only for PUC lua: port of `require'bit'` from luajit https://bitop.luajit.org/ * [treesitter parsers](https://github.com/neovim/neovim/blob/fcc24e43e0b5f9d801a01ff2b8f78ce8c16dd551/cmake.deps/CMakeLists.txt#L197-L210) +* `runtime/lua/coxpcall.lua`: coxpcall (only needed for PUC lua, builtin to luajit) ### Forks diff --git a/cmake/BuildLuarocks.cmake b/cmake/BuildLuarocks.cmake index 57d2666d36..0ab4143b75 100644 --- a/cmake/BuildLuarocks.cmake +++ b/cmake/BuildLuarocks.cmake @@ -97,8 +97,3 @@ endif() add_custom_target(test_deps) Download(luacheck 1.1.0-1 ${LUACHECK_EXE}) - -if(PREFER_LUA) - Download(coxpcall 1.17.0-1) - add_dependencies(test_deps coxpcall) -endif() diff --git a/runtime/lua/coxpcall.lua b/runtime/lua/coxpcall.lua new file mode 100644 index 0000000000..6b179f1ef0 --- /dev/null +++ b/runtime/lua/coxpcall.lua @@ -0,0 +1,108 @@ +------------------------------------------------------------------------------- +-- Coroutine safe xpcall and pcall versions +-- +-- Encapsulates the protected calls with a coroutine based loop, so errors can +-- be dealed without the usual Lua 5.x pcall/xpcall issues with coroutines +-- yielding inside the call to pcall or xpcall. +-- +-- Authors: Roberto Ierusalimschy and Andre Carregal +-- Contributors: Thomas Harning Jr., Ignacio BurgueƱo, Fabio Mascarenhas +-- +-- Copyright 2005 - Kepler Project +-- +-- $Id: coxpcall.lua,v 1.13 2008/05/19 19:20:02 mascarenhas Exp $ +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +-- Checks if (x)pcall function is coroutine safe +------------------------------------------------------------------------------- +local function isCoroutineSafe(func) + local co = coroutine.create(function() + return func(coroutine.yield, function() end) + end) + + coroutine.resume(co) + return coroutine.resume(co) +end + +-- No need to do anything if pcall and xpcall are already safe. +if isCoroutineSafe(pcall) and isCoroutineSafe(xpcall) then + copcall = pcall + coxpcall = xpcall + return { pcall = pcall, xpcall = xpcall, running = coroutine.running } +end + +------------------------------------------------------------------------------- +-- Implements xpcall with coroutines +------------------------------------------------------------------------------- +local performResume, handleReturnValue +local oldpcall, oldxpcall = pcall, xpcall +local pack = table.pack or function(...) return {n = select("#", ...), ...} end +local unpack = table.unpack or unpack +local running = coroutine.running +local coromap = setmetatable({}, { __mode = "k" }) + +function handleReturnValue(err, co, status, ...) + if not status then + return false, err(debug.traceback(co, (...)), ...) + end + if coroutine.status(co) == 'suspended' then + return performResume(err, co, coroutine.yield(...)) + else + return true, ... + end +end + +function performResume(err, co, ...) + return handleReturnValue(err, co, coroutine.resume(co, ...)) +end + +local function id(trace, ...) + return trace +end + +function coxpcall(f, err, ...) + local current = running() + if not current then + if err == id then + return oldpcall(f, ...) + else + if select("#", ...) > 0 then + local oldf, params = f, pack(...) + f = function() return oldf(unpack(params, 1, params.n)) end + end + return oldxpcall(f, err) + end + else + local res, co = oldpcall(coroutine.create, f) + if not res then + local newf = function(...) return f(...) end + co = coroutine.create(newf) + end + coromap[co] = current + return performResume(err, co, ...) + end +end + +local function corunning(coro) + if coro ~= nil then + assert(type(coro)=="thread", "Bad argument; expected thread, got: "..type(coro)) + else + coro = running() + end + while coromap[coro] do + coro = coromap[coro] + end + if coro == "mainthread" then return nil end + return coro +end + +------------------------------------------------------------------------------- +-- Implements pcall with coroutines +------------------------------------------------------------------------------- + +function copcall(f, ...) + return coxpcall(f, id, ...) +end + +return { pcall = copcall, xpcall = coxpcall, running = corunning }