Chapter 7
The built in function
loadfile takes a string as an argument. It tries
to interpret the string as the pathname of a file; if it is,
it tries to load its contents. If successful it then
tries to compile them as the body of a Lua function taking no arguments.
If all that succeeds, it returns the value of the resulting function. If it
fails, it returns nil followed by an error message. This means that
you can load in and execute code written in separate files.
It is convenient to have the built in function
dofile which is equivalent to
\ (filename)
local f = assert (loadfile (filename))
=> f ( )
end -- function
Values can be returned from the code in the file. So if the file
ends with a statement
=> val_1, . . . , val_n
the statement
x_1, . . . , x_n = dofile (filename)
will, if there is no error, assign the values returned from
the file.
If the code in the file is to be run more than once it is best to use
loadfile once and call the result many times rather than repeat the
loading and compilation many times. Here is an example of when you
would find this useful.
Suppose you are writing a book,thesis or article containing references
or footnotes. It is convenient to write these as you go along, but
for presentation you may want another arrangement: to gather
them together and put them in some special place,
at the end perhaps. How would you write a program to
scan the text, collect up the notes, and
replace each one by an index into a table to be
inserted into the text at a specified place? For the purposes of
the problem the source text consists of three sorts of data:
1. text with no notes in it,
2. notes,
3. a marker to indicate where the list of notes is to go.
Here is some source text, clearly split into these three categories.
text [[Pharasmenes]]
note [[King of the Hiberi]]
text [[ the king, visited him]]
note [[Antoninus Pius]]
text [[ at Rome and showed
him more respect than he had to Hadrian.
He appointed Pacorus king of the Lazi]]
note [[The Lazi lived on the south-eastern shore
of the Black Sea, south of the river Phasis]]
text [[, induced the king of the Parthians]]
note [[Vologases III]]
text [[ to forego a campaign against the Armenians
merely by writing him a letter, and solely by his
personal influence brought Abgarus the king]]
note [[of Osrhoene]]
text [[ back from the East.]]
foot()
Our program should read this text and convert it to this:
Pharasmenes[1] the king, visited him[2] at Rome
and showed him more respect than he had to Hadrian.
He appointed Pacorus king of the Lazi[3], induced
the king of the Parthians[4] to forego a campaign
against the Armenians merely by writing him a letter,
and solely by his personal influence brought
Abgarus the king[5] back from the East.
Footnotes
1. King of the Hiberi
2. Antoninus Pius
3. The Lazi lived on the south-eastern shore of the
Black Sea, south of the river Phasis
4. Vologases III
5. of Osrhoene
We assume our program
footnote is a script
to be used with StrongED. We have deliberately used a format
that makes sense as actual Lua code once the words
text, note, foot have been defined as functions. The program works in two
phases of evaluation. In the first phase
text and
foot do nothing, and
note constructs
a list of notes. In the second phase
text simply outputs its argument, while
note outputs a count, and
foot prints out
the list of notes. This idea, that we can assign different
functions to the variables for each phase, is a very useful one.
Our program should have the overall form:
do
local do_source = assert (loadfile (arg[1]))
... definitions ...
-- phase1
text, note, foot = nowt, addnote, nowt
do_source ( )
-- phase2
text, note, foot = io.write, counter, notelist
do_source ( )
end
The details of the definitions are:
local nowt = \ (x) end
local list = { }
local addnote = \ (x) list[#list + 1] = x end
local count = 0
local counter = \ (x)
count = count + 1
io.write (("[%d]"):format (count))
end
local notelist = \ ( )
io.write "\n\nFootnotes\n\n"
for i, x in ipairs (list) do
print (i, ". ", x)
end -- for
end -- function
It makes sense to keep re-usable data in separate files
as lists of tables. So, for example, a list of
speakers at a conference might be kept like this:
=> {
{
name = [[Basil of Bithynia]];
from = [[The Institute of Euxine Studies]];
title = [[Doctrinal influences of mustard]];
time = [[Wednesday 11.00]];
where = [[Seminar hall 3]];
};
{
name = [[Efthymia Palaeozetes]];
from = [[Tooting Centre of Excellence]];
title = [[Midgard in the sagas]];
time = [[Saturday 8.00]];
where = [[The snug]];
}
. . . .
}
The virtue of this labelled record format is that it does
not matter in what order the fields are written, and you can always
slip in more labels to cope with special exigencies; e.g.
nb = [[Dr Chenko is a vegetarian on Tuesdays]];
If you access the database with
db = dofile (dbase_file)
then the records are
db[1], db[2], . . . and for each
x = db[i] we have fields
x.name x.time x.title etc.
Sometimes it is useful to bundle methods in with the data in a database.
For example, you might want to include a list of links in a webpage. You
could keep the list in a database in a separate file like this:
=> {
show = \ (self)
local ul = "<ul>%s</ul>"
local link = '<li><a href="%s">%s</a>\n'
local list = { }
for _, item in ipairs (self) do
local url, label in item
list[1 + #list] = link:format (url, label)
end -- for
=> ul:format (table.concat (list))
end;
{
label = [[World Wide Web Consortium]];
url = [[http://www.w3.org/]];
};
{
label = [[Validate a webpage]];
url = [[http://validator.w3.org/]];
};
. . . .
}
Then if the file is read by
dofile into a variable
links the expression
links:show() will
evaluate to the html code giving the list. It is more efficient to
read each line as a string entry in
list and then to
use
table.concat than to concatenate each line as it
is made in the
for loop into a longer and longer string.
Generally speaking, it is a good idea to avoid creating intermediate
substrings. For this purpose, note how convenient the
format string-method is for inserting substrings. Note also how the
construction
local url,label in item
makes for minimal, but readable, code.
The following function uses this strategy.
stack = \ ( ) => { push = \ (self, x) self[1+#self] = x end } end
However, it suffers from the fact that each call creates its own
copy of the
push method. So better code is
do
local f = \ (self, x) self[1+#self] = x end
stack = \ ( ) => { push = f } end
end
which uses a single copy of the
push method.
It can be used like this:
mylist = stack()
mylist:push (item1)
mylist:push (item2)
.....
so that the i-th item is
mylist[i] .
There are two styles for dealing with input and output.
If you are only dealing with a single file at a time you
can redirect input from the default
stdin or output to the default
stdout using
the functions
io.input and
io.output . These take as argument the pathname of
the file to be read or written to. A nil argument resets
the defaults. So for example, to read a file in as a string
you could write:
file2string = \ (file)
local input, read in io
input (file)
local s = read "*all"
input ( )
=> s
end -- function
and to write a string out to a file:
string2file = \ (s, file)
local output, write in io
output (file)
write (s)
output ( )
end -- function
The other style involves opening files for reading or writing
using filehandles. Filehandles, which are userdata, are created
with the function
io.open and destroyed with
a
close method. The two previous functions could
be rewritten in this style as
file2string = \ (file)
local f = io.open (file, "r")
local s = f:read "*all"
f:close ( )
=> s
end -- function
string2file = \ (s, file)
local f = io.open (file, "w")
f:write (s)
f:close ( )
end -- function
In this style, which you need to use if you are handling
lots of files simultaneously, the functions of the io library
become methods for the filehandles.
How do you get information into your program? There are the following
possibilities: read it from ...
1. ... a file. You can read data either as a given number of
bytes, as a representation of a number, line by line with
io.lines , or the whole file as a single string.
You can also compile code in a separate file to a function,
using
loadfile .
2. ... a system variable using
os.getenv . For example
year = os.getenv "Sys$Year"
3. ... the commandline using the global array
arg . The
value
arg[0] returns the pathname of the file holding
the program. For positive n,
arg[n] returns the value
of the n-th word on the commandline that started the program. To get
the directory containing the program:
thisdir = (arg[0]):gsub ("%.[^%.]+$","")