Lua runner with rich builtin stdlib
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
a/lua/stdlib/utils.lua

104 lines
4.0 KiB

-- Lua-implemented part of the utils module, extending the global utils table.
-- The native parts of the module (dump, toJSON, fromJSON, NULL, isNull)
-- are declared in Rust before this file is loaded.
-- Substituted when a callback throws a nil error value (e.g. plain `error()`),
-- so that the returned err is always non-nil on failure.
local NIL_ERROR_PLACEHOLDER = "unspecified error"
--- @brief Call a function, capturing any error - like pcall, but with Go-style returns.
---
--- local value, err = utils.try(callback)
--- local value, err = utils.try(callback, arg1, arg2) -- pcall-style shorthand
--- if err then
--- print(err)
--- else
--- -- handle value
--- end
---
--- - err is nil if the callback succeeded, otherwise the error value
--- - value is the callback's first return value (nil on error)
--- - extra arguments are passed to the callback, like with pcall
---
--- This is less confusing than pcall when the return value is actually needed.
--- The thrown error value is passed through as-is, so a table thrown with
--- error({code = 42}) stays a table. The downside is that the result can't be
--- used directly as a condition in "if" or "while" - simply use pcall there.
---
--- @param callback function: Function to call
--- @param ... any: Arguments passed to the callback
--- @return any, any: The callback's first return value and nil, or nil and the error
function utils.try(callback, ...)
if type(callback) ~= "function" then
error("utils.try: callback is not a function (got " .. type(callback) .. ")")
end
local ok, result = pcall(callback, ...)
if ok then
return result, nil
end
if result == nil then
result = NIL_ERROR_PLACEHOLDER
end
return nil, result
end
local TRYN_MAX_COUNT = 64
--- @brief Call a function, capturing any error - like utils.try, but for N return values.
---
--- local value1, value2, err = utils.tryn(2, callback)
--- local value1, value2, err = utils.tryn(2, callback, arg1, arg2) -- pcall-style shorthand
--- if err then
--- print(err)
--- else
--- -- handle (value1, value2)
--- end
---
--- - err is nil if the callback succeeded, otherwise the error value
--- - values are the callback's return values, padded with nils or truncated to exactly N
--- - extra arguments are passed to the callback, like with pcall
---
--- This is less confusing than pcall for multiple return values, where the error
--- and the first value share a slot:
---
--- local status, errorOrValue1, value2 = pcall(callback)
---
--- @param expectedCount number: How many return values to pass through (integer, 0 to 64)
--- @param callback function: Function to call
--- @param ... any: Arguments passed to the callback
--- @return any ...: expectedCount values followed by the error or nil
function utils.tryn(expectedCount, callback, ...)
local count = math.tointeger(expectedCount)
if count == nil then
error("utils.tryn: expectedCount must be an integer number")
end
expectedCount = count
if expectedCount < 0 then
error("utils.tryn: expectedCount must not be negative")
end
if expectedCount > TRYN_MAX_COUNT then
error("utils.tryn: at most " .. TRYN_MAX_COUNT .. " return values are supported")
end
if type(callback) ~= "function" then
error("utils.tryn: callback is not a function (got " .. type(callback) .. ")")
end
local results = table.pack(pcall(callback, ...))
if results[1] then
-- Success: pass through values 2..expectedCount+1 (truncating or nil-padding),
-- with nil in the error slot. The explicit assignment overwrites a possible
-- extra return value so it cannot leak into the error slot.
results[expectedCount + 2] = nil
return table.unpack(results, 2, expectedCount + 2)
end
local err = results[2]
if err == nil then
err = NIL_ERROR_PLACEHOLDER
end
local out = {}
out[expectedCount + 1] = err
return table.unpack(out, 1, expectedCount + 1)
end