7

I am learning Lua from a book, and I am NOT a programmer. I am trying to save a table of data to a file using the following functions (that were copied directly from the book), but the function is getting an error when trying to get a string from _G[resTable]. Why?

function readFromFile(filename,resTable)
    local hfile = io.open(filename)
    if hfile == nil then return end
    local results = {} -why is this table here?
    local a = 1
    for line in hfile:lines() do-- debug shows this loop doesn't run (no lines in hfile?)
        _G[resTable[a]] = line
        a = a + 1
    end
end

function writeToFile(filename, resTable)
    local hfile = io.open(filename, "w")
    if hfile == nil then return end
    local i
    for i=1, #resTable do
        hfile:write(_G[resTable[i]])--bad argument #1 to 'write' (string expected, got nil)
    end
end

'writeToFile" gets an error when trying to :write to _G[resTable[i]]. In the two previous functions listed here, I don't understand why they are referencing _G[resTable[i]] since I don't see any code that is writing to _G.

So here is the order of execution:

local aryTable = {
"Score",
"Lives",
"Health",
}

readFromFile("datafile", aryTable)

writeToFile("datafile", aryTable)

and I get an error:

bad argument #1 to 'write' (string expected, got nil)
stack traceback:
[C]: in function 'write'
test.lua:45: in function 'writeToFile'
test.lua:82: in main chunk
hjpotter92
  • 78,589
  • 36
  • 144
  • 183
PHazer
  • 109
  • 1
  • 5

3 Answers3

3

Apparently the author has implemented a way of saving a list of global variables to file and restore them.

The function writeToFile expects a filename and a list of global variables names (resTable). Then it opens a the filename for writing and iterates over the provided names:

for i=1, #resTable do
    hfile:write(_G[resTable[i]])
end

in this loop resTable[i] is the i-th name and _G[resTable[i]] is the corresponding value, taken from the table _G, which stores all the globals. If a global with that name is not defined, _G[resTable[i]] will return nil, which is the cause of the failure you experienced. Thus you must provide a resTable that is filled with names of existing globals to avoid this error.

Apart from this, the serialization strategy of the author is really naive, since it handles only variables with string values. In fact by saving the variables to file like that the type information is lost, thus a variable having the value "100" (a string) and another with value 100 (a number) will be stored the same on disk.

The problem is evident analyzing the readFromFile function. After opening the file for reading, it scans it line by line, creating a new variable for each name mentioned in its resTable list:

local a = 1
for line in hfile:lines() do
    _G[resTable[a]] = line
    a = a + 1
end

the problem is manyfold:

  • the loop variable line will always have a string value, thus the recreated globals will be all strings, even if they were numbers originally;
  • it assumes that the variables are recreated in the same order, thus you must provide the same names in resTable you used when you saved the file;
  • it assumes that the values are stored one per line, but this is a false assumption, since the writeToFile function doesn't write a newline character after each value;

Moreover that local results = {} is useless and in both functions the file handle hfile is not closed. This latter is very bad practice: it could waste system resources and if your script fails part of the supposedly written data could never make its way to disk, since it may be still stuck in some buffer. File handles are automatically closed when the script ends, but only if it ends in a sane way.

Unless you did some error in pasting the code or omitted significant parts of it or the book is building some example incrementally, I dare say it is fairly crappy.


If you want a quick and dirty way to save and retrieve some globals you could use this:

function writeToFile( filename, resTable )
    local hfile = io.open(filename, "w")
    if hfile == nil then return end
    for _, name in ipairs( resTable ) do
        local value = _G[name]
        if value ~= nil then
            hfile:write( name, " = ")
            local vtype = type( value )
            if vtype == 'string' then
                hfile:write( string.format( "%q", value ) )
            elseif vtype == 'number' or vtype == 'boolean' then
                hfile:write( tostring( value ) )
            else
                -- do nothing - unsupported type
            end
            hfile:write( "\n" )
        end
    end
    hfile:close()
end

readFromFile = dofile

It saves the globals as a Lua script and reads them back by executing the script using Lua dofile function. Its main limitation is that it can only save strings, booleans an numbers, but usually this is enough while learning.

You can test it with the following statements:

a = 10
b = "20"
c = "hello"
d = true
print( a, b, c, d )
writeToFile( "datafile", { "a", "b", "c", "d" } )
a, b, c, d = nil
print( a, b, c, d )
readFromFile( "datafile" )
print( a, b, c, d )

If you need more advanced serialization techniques you can refer to Lua WIKI page on table serialization.

  • I should mention that in the subsequent paragraph in his book, he does admit, _If the data to be saved is anything else,_(besides strings or numbers) _the functions will start to fail, and more so if the variable is a table. The biggest issue with tables is iterating through the depths of the table._ He then advocates using the JSON library for encoding or decoding the information between the table object and a JSON encoded string. – PHazer Oct 10 '13 at 23:02
  • Also the author is sort of incrementally working through problems with the simplest functions to solve the problem, pointing out pitfalls, then introducing more comprehensive functions as the chapter progresses. That may or not be why these functions have problems as is. His ultimate solution is a function that saves tables as JSON encoded strings. Still, for a newbie like me that approach tends to confuse. – PHazer Oct 10 '13 at 23:09
  • @PHazer I cannot judge without seeing it, but I hate books that oversimplify things: they tend to make further learning harder because the reader, especially the novice one, in the end must unlearn bad habits or false approaches. – LorenzoDonati4Ukraine-OnStrike Oct 11 '13 at 04:00
  • 1
    I advice you to give [Programming in Lua](http://www.lua.org/pil/contents.html) a try. It is from one of the devs of Lua and freely available online. It is for Lua 5.0 so not completely relevant for Lua 5.1 or 5.2 (still useful). It could give you hints on whether the author style suits you and help you decide whether purchasing a copy of a [more up-to-date version](http://www.lua.org/pil/) is good for you. It won't teach you programming from scratch, but it is a **very** good intermediate-level book, with clear language and very detailed. – LorenzoDonati4Ukraine-OnStrike Oct 11 '13 at 04:06
0

Those aren't generalized "read/write any table from/to any file" functions. They apparently expect the name of a global table as an argument, not a [reference to a local] table itself. They look like the kind of one-off solution to a very specific problem that tends to show up in books. :-)

Your functions shouldn't be doing anything with _G. I don't have an API reference handy, but the read loop should be doing something like

resTable[a] = line

and the write loop would be doing

hfile:write(resTable[i])

Throw out that local "results" table too. :-)

Ti Strga
  • 1,353
  • 18
  • 40
  • Yes, the functions do expect the name of a global table as an argument. That is why it is so confusing to me why the author called the functions while passing a local table as the argument instead. – PHazer Oct 10 '13 at 20:15
  • ...and the mystery "results" table did not instill me with any confidence either. – PHazer Oct 10 '13 at 20:16
  • 1
    The author's reasoning behind "readFromFile": _In your app, the number of variables that you might store could be different for each application.You would never know the order in which your data is saved, and if the order of saving the data or reading the data is changed, you might have values that end up in the wrong variables. So what we are going to do to fix this issue is have a table with the data and filed names in it. This will provide us with the flexibility to expand this further in the future._ He then shows the `readFromFile` function. – PHazer Oct 10 '13 at 20:31
0

This code reads and writes data from a file into global variables whose names are specified in aryTable. Since your file is empty, readFromFile does not actually set the variable values. And then writeToFile fails when trying to get the variable values, because they haven't been set.

Try putting data in the file so that the variables do get set, or set the variable values yourself before writing them to the file (e.g. Score = 10, etc.)

interjay
  • 107,303
  • 21
  • 270
  • 254
  • You have identified my main problem with the author's writing. It seemed obvious to me that there was a problem with `readFromFile` on an empty datafile, yet at no point in the chapter did he mention any way of recording data to the datafile outside of using `writeToFile`. – PHazer Oct 10 '13 at 22:43
  • Also, when I put some lines of data in the datafile directly (text editor), the function just erases them all anyway. – PHazer Oct 10 '13 at 22:57