A Lua iterator that fails silently? - lua

I have a very simple problem with a simple iterator.
Let's say that I design a function, files(), that iterates over all the files in a folder:
for file in files("/path/to/folder") do
print(file)
end
Now, this seems perfect, but there's a problem here: What if the the folder doesn't exist, or we don't have read permission to it?
How would we indicate such error?
One solution would be to have files() return nil, "no read permission" in this case. We'd then be able to wrap the call to files() inside assert():
for file in assert(files("/path/to/folder")) do
print(file)
end
This seemingly solves the problem. But this forces our users to always use assert(). What if the user doesn't care about errors? For this kind of users we'd want our files() to behave as if the folder is empty. But Lua --in case files() indicates error-- would try to call the returned nil and this will result in an error ("attempt to call a nil value").
So,
How can we design an iterator, files(), that would cater to both users that care about errors and users that don't?
If it's not possible, what alternative would you suggest?

First: Instead of returning nil + error message consider raising an error in the files function (using error). This way you can't forget the assert call, and you won't get the confusing "attempt to call a nil value" error.
You could pass an extra boolean parameter to files when you don't want to raise errors -- you should return an empty function (function() end) instead of calling error in this case.
A more general approach is the following:
-- an iterator that immediately stops a for loop
local function dummy_iter() end
-- catch errors and skip for loop in that case
function iterpcall( g, ... )
local ok, f, st, var = pcall( g, ... )
if ok then
return f, st, var
else
return dummy_iter
end
end
for file in iterpcall( files, "/path/to/folder" ) do
print( file )
for line in iterpcall( io.lines, file ) do -- works for other iterators as well
print( line )
end
end
The implementation of iterpcall above only handles errors raised in the iterator generator (files or io.lines), not in the iterator function (f) itself. You would have to wrap f in a closure with a pcall to do that.

There also question what you whant to do if you get error in the middle of iteration (e.g. access deny for subfolder with recurcive iteration). In this case assert does not help.
In this case I create 2 variant of iterators (inner and outer).
-- raise error
for file in files(...) do ... end
-- return error
files(...,function(file) ... end)
Or just create 2 different iterators.

Related

Handling lfs.dir when access to directory is denied

The lfs module allows reading a directory using the lfs.dir iterator. If the directory cannot be accessed (e.g. because permissions do not allow it), an error is raised.
I could check that directory can be read like this:
local status, err = pcall(lfs.dir, "mydir")
if not status then
print("error occured", err)
else
for file in lfs.dir("mydir") do
print("file:", file)
end
end
However, this means calling lfs.dir twice. Not sure how much overhead that is, but given that I am working on a tool that scans huge directory structures very quickly, I would very much like to avoid it. Another solution would be to wrap the loop in a function and pcall that function. However, I was wondering whether the pcall can be built in the iterator itself, like creating a custom iterator as a "safe" wrapper around lfs.dir() that would allow me to silently ignore directories that cannot be read. How can I do that? And how much overhead would that be?
Using pcall
You don't have to look at thee implementation details of LFS to use pcall: The Lua reference manual tells you that there will be exactly 4 values evaluated to iterate a generic for:
The loop starts by evaluating explist to produce four values: an iterator function, a state, an initial value for the control variable, and a closing value.
Thus you can use pcall for any generic for iterator as follows:
local status, iterator_or_err, state, control_var, closing_val = pcall(lfs.dir, "mydir")
if status then
for file in iterator_or_err, state, control_var, closing_val do
print("file:", file)
end
else
print("error occurred", iterator_or_err)
end
Perhaps slightly more elegant would be capturing the return values in a table:
local t = {pcall(lfs.dir, "mydir")}
if t[1] then
for file in table.unpack(t, 2) do
print("file:", file)
end
else
print("error occurred", t[2])
end
Downside: This creates a garbage table. The most elegant solution is to use xpcall:
Using xpcall
Whenever you have to handle vararg returns, xpcall is handy because Lua will call a handler you provide with the status & the vararg.
xpcall(lfs.dir, function(status, ...)
if status then
for file in ... do
print("file:", file)
end
else
print("error occurred", ...)
end
end, "mydir")
Yes, you can invoke lfs.dir only once:
local status, err_or_iter, ud, v3, v4 = pcall(lfs.dir, "mydir")
if not status then
print("error occured", err_or_iter)
else
for file in err_or_iter, ud, v3, v4 do
print("file:", file)
end
end

Vim global variable check throws error in lua (neovim plugin development)

I'm trying write a neovim plugin using lua, when checking if a variable exists, lua throws an error like: Undefined variable: g:my_var
Method 1:
local function open_bmax_term()
if (vim.api.nvim_eval("g:my_var")) then
print('has the last buff')
else
print('has no the last buff')
end
end
Method 2:
local function open_bmax_term()
if (vim.api.nvim_get_var("my_var")) then
print('has the last buff')
else
print('has no the last buff')
end
end
this is a similar function written in viml which does works: (this does not throw any error)
fun! OpenBmaxTerm()
if exists("g:my_var")
echo "has the last buff"
else
echo "has no the last buff"
endif
endfun
any idea how to get this working in lua? I tried wrapping the condition inside a pcall which had an effect like making it always truthy.
vim.api.nvim_eval("g:my_var") just evaluates a vimscript expression, so accessing a non-existent variable would error just like in vimscript. Have you tried vim.api.nvim_eval('exists("g:my_var")') instead?
Edit: Using vim.g as #andrewk suggested is probably the better solution as using the dedicated API is more elegant than evaluating strings of vim script.
You can use the global g: dictionary via vim.g to reference your variable:
if vim.g.my_var == nil then
print("g:my_var does not exist")
else
print("g:my_var was set to "..vim.g.my_var)
end
You can reference :h lua-vim-variables to see other global Vim dictionaries that are available as well!

Why does if then return break end not work in Lua inside for loop

I have the following function which checks if the given parameter is found as a key in a key-value table. If that is the case it should return true and break out of the loop. If nothing is found then do nothing.
function checkId(id)
for k,v in pairs(info) do
if id == tostring(k) then
return true
break -- break out of loop. mission accomplished.
end
end
end
I get an
'end' expected (to close 'do' at line 192) near 'break'
when I try to run this script. What am I missing?
Logically you can't return and break like that.
return exits the function immediately (so you don't need the break).
That specific error is because in lua return has to be the last statement in a block.

Why is 'name' nil for debug.getinfo(1)

I'm trying to put together a lua testing framework that lets you know the function that had the problem, but when I switched from loadstring to _G, (I switched so my test harness could see the results of the function call) my functions started using 'nil' for the function name
Why can _G not detect the name of the current function in the following code? Also, how can I get the return results from loadstring (ie the 'false' from the blah call) or set the function name when using _G (ie. Tell the lua interpreter what the function name should be)?
function run_test(one, two)
if one ~= two then
print(debug.getinfo(2).name..' Failed')
end
end
function blah()
run_test(false, true)
return false
end
local fname = 'blah'
local status, result = pcall(_G[fname]) -- Outputs 'nil'; result is 'false'
local status, result = pcall(loadstring(fname..'()')) -- Outputs 'blah', result is 'nil'
The main thing I need is a way to call a function using a string of the function name, be able to see the function name inside the call (for test failures to point to the function that failed, like fname = 'blah' in the code above) and be able to get the return value
local fname = 'blah'
status, result = pcall(??Call fname somehow??)
assert(status)
assert(not result)
--stdout should be "blah Failed"
This is a limitation of the heuristics used by Lua to provide names for functions.
In Lua, all functions are anonymous. A given function can be the value of several variables: global, local, and table fields. The Lua debug system tries to find a reasonable name for a value based on where it came from by looking into the bytecode being executed.
Consider the simpler example
blah()
pcall(blah)
In the first call, the debug system sees that the function being called comes from the global blah and debug.getinfo(1).name gives the expected result, blah.
In the second call, the debug system sees that the function being called comes from the first argument to pcall but it does not look further to see where that argument came from, and debug.getinfo(1).name gives nil.
The same thing happens when you call _G[name](). All the debug system sees is a field of a table and the name of the field is too far off.
Try adding print(debug.traceback()) as the first line of blah to see another take on this explanation.

Lua Arguments and Numbers

function OnChat(PlayerId, Type, Message)
if Message == "!usecarepackage" then
if ReadKS1 == "CP" or ReadKS2 == "CP" or ReadKS3 == "CP" then
InputConsole("msg %s has requested a care package!", Get_Player_Name_By_ID(pID))
local pos = Get_Position(Get_GameObj(pID))
pos:AssignY(pos:GetY()+1)
building = Create_Object("SignalFlare_Gold_Phys3", pos)
Attach_Script(building, "Test_Cinematic", "caredrop.txt")
if building == nil then
InputConsole("ppage %d Cannot spawn Care Package here.", pID)
elseif math.random(50, 64) == 50 then
Attach_Script(building, "z_Set_Team", Get_Team(pID))
Attach_Script(building, "M00_No_Falling_Damage_DME")
--Add in rest of numbers and corresponding streak, such as UAV if math.random = 50
end
elseif ReadKS1 ~= "CP" or ReadKS2 ~= "CP" or ReadKS3 ~= "CP" then
InputConsole("ppage %d You are not allowed to use that with your current streak selection", pID)
end
end
return 1
end
I know this is scruffy code, but I'm receiving a "Bad argument #2 to 'format' (number expected, got no value)". This relates to this piece of code which prefixes all other pieces:
function InputConsole(...)
Console_Input(string.format(unpack(arg)))
end
Finally, this is for the game Command and Conquer Renegade (should you wish to look up API etc). Any help in what I'm doing wrong would be much appreciated.
The likely problem with this code is the use of the deprecated arg feature in your function InputConsole(). Lua 5.0 used arg as a way to access the actual arguments to a function declared as variadic using the ... token in the argument list.
Edit: But likely doesn't mean correct.
The actual problem looks like the switch in idiom from PlayerId to pID. The former is the named parameter of the function OnChat(), while the latter is a global variable used in the function body without further initialization. Uninitialized globals are nil, so nil is passed to InputConsole() and the error message is telling you the truth.
Old answer that didn't solve it
Lua 5.1 deprecated that usage, and Lua 5.2 removed it entirely.
I'm not sure from the code fragments provided which version of Lua is actually used in this game, but the symptom is consistent with missing the automatically generated arg table.
I would write the function like this:
function InputConsole(...)
Console_Input(string.format(...))
end
But you could also add local arg = {...} as the first line of the function and get much the same effect as provided by Lua 5.0 at the expense of creating (and discarding) the temporary table. The differences are subtle, and mostly have to do with the treatment of nil in tables.
For clarity, I would prefer name the first argument, as it is not really optional.
function InputConsole(fmt, ...)
Console_Input(string.format(fmt, ...))
end
And if you can count on that argument being a string, that could be simplified further to
function InputConsole(fmt,...)
Console_Input(fmt:format(...))
end
If its stringiness is of concern, just say fmt = tostring(fmt) or possibly assert(type(fmt)=="string") before the call to Console_Input().

Resources