title logo

back contents forward

Chapter 7

modularization

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.

text manipulation

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:

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

databases

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] .
files

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.
input

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 ("%.[^%.]+$","")

back contents forward