Handling and Avoiding Errors - Beginning Lua Programming (2007)

Beginning Lua Programming (2007)

Chapter 6. Handling and Avoiding Errors

Program correctness matters on every count, from user acceptance and trust to the downstream consequences of program output. In this chapter, you learn about the following:

· The kinds of errors that can occur in your programs

· Lua’s mechanisms for handling errors

· Techniques to avoid program errors

· Techniques to locate program errors

Along the way, you’ll become familiar with Lua’s I/O library, which is included in some of the examples.

Kinds of Errors

Some flaws can turn up before a program is executed. These are compile-time errors, which Lua can find as it converts your source code into executable bytecode. Often these result from simple misspellings or typing slips as you edit your source code. Other flaws are more insidious — Lua recognizes them as syntactically correct so they make their way into the executable bytecode. These errors await discovery at run time, preferably by you during testing rather than by someone who’s actually using a product after it’s been released with the erroneous code.

Syntax Errors

Syntax refers to the way various language elements are allowed to fit together, completely independent from the meaning of those elements. A programming language like Lua is necessarily fussy about syntax, because source code expressed in it needs to translate unambiguously to byte-code, the instructions that Lua’s virtual machine can deal with. During the 1960s (the renaissance era of modern programming languages) computer science practitioners such as Niklaus Wirth understood the importance of specifying syntactic rules that express naturally and simply the intent of a programmer. Lua’s syntax falls neatly into this heritage. Take time to study the page in the Lua manual that describes Lua’s complete syntax. Doing so can provide much insight into the Lua language and the way the Lua parser operates.

Essentially, it is the job of Lua’s parser to recognize syntactically correct Lua programs and, in the case of an incorrect program, to let you know where the problem is. Of course, the program you submit to Lua might have more than one syntax error, but Lua will stop at the first one it encounters and print a message that should allow you to pinpoint the location of the problem. Usually, but not always, you’ll instantly recognize the offending construction, make a quick fix, and compile the code again.

Here is an example of a syntax error:

a = 1 + + 2

This results in an "unexpected symbol near ‘ + ‘ ” message.

Here’s another example:

for do

Lua expects a variable name to follow for, so it generates a ”‘<name>’ expected near 'do' ” message.

And here’s an erroneous script example:

Str = 'hello, world

The result is the message "unfinished string near ''hello, world' ". When you run this as a non-interactive Lua script, this error is generated immediately. When you type it in interactively, Lua presents you with a continuation prompt and defers the error until you provide more input. Strings that begin with a single or double quote must end, on the same line, with a matching quote. Lua supports multiline strings, for which you use the [[ ]] notation (or represent each newline with a backslash-escaped n or backslash-escaped newline, as described in Chapter 2).

Now take a look at this example:

break

By itself, this command results in the message "no loop to break near ‘<eof>‘ " (where <eof> refers to "end of file").

Here’s a function call example:

Fnc

(42)

Lua generally allows you to put whitespace, including line endings, wherever you like. As mentioned in Chapter 3, however, calling a function is an exception: The opening parenthesis must occur on the same line as the function expression. The preceding construction leads to the message"ambiguous syntax (function call x new statement) near ‘(‘."

Now look at this:

Tbl:Fnc 4

This results in the message "function arguments expected near ‘4'." When Lua sets up a method call, it expects the usual function argument specification: a list of zero or more arguments wrapped in parentheses, a single quoted string, or a single table constructor.

In the following example, the vararg expression ... is used inside a function whose parameter list does not end in ...:

function Fnc() print(...) end

This results in the message "cannot use outside a vararg function near ‘...' "A Lua script, whether it is a single line entered interactively in the Lua interpreter or a complete file that has been loaded noninteractively, has an implicit ... that contains any arguments with which it is called.

In the case of a single line entered in the Lua interpreter, there can’t be any arguments, but still you can see that the following statement is valid:

print(... )

Unlike named parameters, the ... expression has limited visibility and cannot be used as an upvalue.

For more on upvalue, see Chapter 3

These are contrived examples—you’ll rarely, if ever, see some of these syntax error messages. Or if you do get an error message from Lua, it may not make immediate sense. For example, if you enter the following:

string.sub9"Hello", 1, 1)

it will return the message "unexpected symbol near ‘,’." You can easily recognize the last problem as a typing mistake in which "9" was typed instead of "(", so what accounts for Lua’s error message? Recall that Lua permits the parentheses to be omitted when calling a function with a single string argument. In this case, Lua treats string.sub9 as a global function that, at run time, will be passed the string "Hello". In this context, the comma that follows "Hello" doesn’t make sense, and Lua lets you know about it.

Some programming languages require variables to be declared before use. When programs written in these languages are compiled, any reference to an undeclared variable will be reported as an error. This is not the case with Lua. Any variable name that does not match an in-scope local variable name is considered to be one that will be resolved at run time in the global environment.

Some very uncommon compile-time errors have to do with internal limits rather than syntax violations. We’ve never encountered any of these in practice or heard of them occurring. But, in the spirit of inquiry and with a hammer in hand, give it a try. The following Lua program generates another Lua program, one that contains an impracticably large number of nested do blocks. In the first program, a routine is called recursively to generate a text file that simulates a handwritten Lua script. Here are the contents of the recurse.lua script:

function Recurse(Count, Indent)

Indent = Indent or ""

if Count > 0 then

io.write(Indent, "do\n")

Recurse(Count - 1, Indent .. " ")

io.write(Indent, "end\n")

end

end

Recurse(299)

Now attempt to compile the program that this script generates:

lua recurse.lua | lua

The pipe directs the output of the first program to the input of the second. Note that, in the spirit of the Unix tool philosophy, the Lua interpreter acts like a filter when its standard input is not a terminal. This results in the following message:

stdin:200: chunk has too many syntax levels

Notice the line number that seems to indicate that Lua was okay with nesting blocks just up to that limit. In practice, your nested blocks will never exceed even a small fraction of that number. But this limit and other limits involving expression complexity are worth keeping in mind if you ever automatically generate Lua programs.

The Call Stack

When a function is called in Lua, space needs to be set aside to hold local variables and certain bookkeeping information such as where program control should resume when the function returns. Because Lua functions are reentrant (that is, there can be multiple outstanding calls to the same function at a given time), this storage must be bound to a particular function call rather than just to the function itself. When Lua compiles a program, it knows how much space needs to be reserved for a particular function, but the actual contents of that space aren’t known until the function is actually called. The simplest solution that meets these requirements is a last-in first-out stack. This is a data structure in which items to be stored are pushed onto the top of the stack, increasing its size, and items to be retrieved are popped from the top of the stack, decreasing its size. When a function is called, its stack frame is pushed onto the call stack, and when it returns, that frame is popped, making the vacated space available for other function calls.

The details of the call stack are hidden from general view. As a Lua programmer, you want to keep in mind that a stack is being used behind the scenes to make sure that function calls work as expected, but in general you won’t need to be aware of its implementation or details. For the times when you do need a window into your program’s internal environment, Lua provides a debugging library. You can use one of its functions, debug.traceback, to generate a list of pending function calls, essentially an overview of the call stack. The debug.traceback function augments the string you pass it with such a list.

1 function B()

2 print(debug.traceback("B"))

3 end

4

5 function A()

6 print(debug.traceback("A 1"))

7 B()

8 print(debug.traceback("A 2"))

9 end

10

11 A()

The output that this program generates has a strong resemblance to the error messages that were shown earlier:

A 1

stack traceback:

trace.lua:6: in function 'A'

trace.lua:11: in main chunk

[C]: ?

B

stack traceback:

trace.lua:2: in function 'B'

trace.lua:7: in function 'A'

trace.lua:11: in main chunk

[C]: ?

A 2

stack traceback:

trace.lua:8: in function 'A'

trace.lua:11: in main chunk

[C]: ?

Notice that the traceback doesn’t give you a history of function calls. For example, you won’t find any reference to the print function. What it does show you is a list of functions that have been called but that have not yet returned. The topmost indented line indicates the location in the Lua script where debug.traceback was called. Each line beneath that shows a pending function and the line number from which it was called. These lines are shown with the most recently called functions first. The bottom line indicates that the first function call originated in a C function for which no line number information is available.

Runtime Errors

When a Lua source file is successfully compiled into bytecode and executed by the interpreter, an error results in a descriptive message followed by a stack traceback. Here’s an example:

function C()

print(1 + nil)

end

function B()

C()

end

function A()

B()

end

A()

When this runs, Lua prints the following:

lua: err.lua:2: attempt to perform arithmetic on a nil value

stack traceback:

err.lua:2: in function 'C'

err.lua:6: in function 'B'

err.lua:10: in function 'A'

err.lua:13: in main chunk [C]: ?

In addition to the message that describes what went wrong during program execution and on which line, there is also a stack traceback section. That portion of the output is a snapshot of pending func-tion calls and provides you with the context of the error. It’s important to have a good grasp of how call stacks operate in Lua, not only for reading stack tracebacks, but also for writing programs that work well with Lua’s error-handling system.

Handling Errors

As you develop a program, there are a number of ways you can deal with the inevitable errors that crop up. Your best course of action will be dictated by the following:

· How the program is to be used and its targeted user

· Whether the error is surmountable

· Whether the error is code-related or data-related

Default Error Behavior

The default behavior of the standalone Lua interpreter when it encounters an error condition is to print a message followed by a stack traceback and then to terminate the program, like this:

local function Fnc(A, B, C)

print(A + B + C)

end

print("Position 1")

Fnc(1, nil, 3)

print("Position 2")

The output includes the expected error and also shows that "Position 2" was never reached:

Position 1

lua: err.lua:2: attempt to perform arithmetic on local 'B' (a nil value)

stack traceback:

err.lua:2: in function 'Fnc'

err.lua:6: in main chunk

[C]: ?

For many quickly written scripts that are intended to be run by the developer, this behavior is acceptable. In these cases, the runtime error can be treated as if it was a syntax error —that is, you can identify the problem, correct it, and run the script again, repeating the process until the program does what you want it to do.

In the context of a program used in the real world, Lua’s default response to errors is rather draconian. It is bad form to abruptly terminate a program while a network connection, database connection, or file is still open. Buffers may not have been flushed and system resources may not be freed in a timely or consistent manner. In the case of a program with a user interface, it can be more than annoying to have a program abnormally end. A considerable amount of work may be lost when a program crashes.

Checking Assumptions

When you track down the source of runtime errors, you may find that certain assumptions you made have proved to be invalid. For example, you may have implemented a loop in which you assume the value of some variable will never exceed a certain number, or you may have written a function that requires a string as its first argument. The problem is that the consequences of an invalid assumption might occur far from its source. Sometimes, errors occur with such irregularity that it is hard to even know where to start looking. These can stem from multiple invalid assumptions that by themselves don’t cause apparent problems, but wreak havoc in combinations that might occur infrequently. Avoid these oblique issues by testing your assumptions directly. Lua makes this easy for you with the assert function, as shown in this example:

local function Quote(Str)

assert(type(Str) == "string", "Str is not a string")

return string.format("%q", Str)

end

print(Quote('Huckleberry "Blue" Hound'))

print(Quote(1))

This outputs the following:

"Huckleberry \"Blue\" Hound"

lua: test.lua:2: Str is not a string

stack traceback:

[C]: in function 'assert'

test.lua:2: in function 'Quote'

test.lua:7: in main chunk

[C]: ?

The idea here is that it is far better to terminate an errant program the moment you know something has gone wrong than to let the problem become clouded by further processing. Keep in mind that this is a development technique that is intended to address problems with program infrastructure —that is, things over which you as the developer have control.

Stack Tracebacks and End Users

Abnormal program termination may occur repeatedly as you develop a script and consequently you can become accustomed to stack tracebacks. However, an end user who encounters a stack traceback won’t have a clue what it means and will inevitably lose some trust in your program. Treat stack tracebacks as a symptom of a bug that should not make it past the development stage. To emphasize this, you may want to display stack tracebacks with the header Programmer error. Later in this chapter, you’ll see how to avoid the conditions that generate stack tracebacks, but first you’ll learn a technique to intentionally generate them and the circumstance in which you should.

Code Errors

View your program as comprising code on one hand and data on the other. Code is the fixed deliverable made up of program statements and data is the material your program processes at run time. Calls to assert are an appropriate way of dealing with code errors. The program you deliver should be free of coding errors, and if abnormal endings and stack tracebacks get you closer to eliminating them, then using assert is fully warranted.

Data Errors

Data errors are by their nature different than code errors. Essentially, your job here is to make your program respond gracefully when garbage is shoveled into it, because sooner or later, intentionally or not, your program’s input domain will be taxed grievously. Your best approach is to mistrust all data that your program reads—the closer to the source, the better.

In the preceding example, you have control over the type of value that you pass to the Quote function. You may not, however, have control over the string’s value, which may have been entered by the user into a text field. If the string itself needs to conform to certain patterns for the program to function correctly, that should be checked and handled by some user-friendlier means than assert. Of course, you may use assert to ensure that your string screening logic is working as expected, because if it isn’t, you’ve got some code adjustments to make.

The assert and error Functions

You don’t have to leave it up to Lua to issue errors. There may be circumstances in which your program should issue an error itself, and for these cases, Lua provides the assert and error functions. Use the assert function to issue an error if an assumption proves to be invalid at run time. To issue an error unconditionally, no questions asked, use the error function.

One or more values are passed to the assert function. These values may be passed in directly, but usually they are the results of an expression such as a function call. If the first argument is either false or nil , assert generates an error and prints an error message followed by a stack traceback. If a second argument is present, it is used as the error message; otherwise, the string "assertion failed!" is used. If the first argument is neither false nor nil , then assert returns every argument that was passed in:

function Reflect(A)

return A, 1, 2, 3

end

print(assert(Reflect(0)))

print(assert(Reflect(nil)))

This displays the following:

0 1 2 3

lua: test.lua:6: 1

stack traceback:

[C]: in function ‘assert’

test.lua:6: in main chunk

[C]: ?

The assert function is a wrapper for the more general error function, which you can be use to unconditionally generate an error.

Defining Your Own Error Condition

Lua has a well-defined set of conditions under which an error will occur. Using the error function, you can define your own as well. Here’s how:

function Answer(Question)

local Res

if Question == "no bananas" then

Res = "yes"

elseif Question == "everything" then

Res = 42

elseif Question == "Tuesday" then

Res = "Belgium"

else

error("No answer for " .. tostring(Question))

end

return Res

end

print(Answer("this statement is false"))print(Answer("this statement is false"))

Running this results in the following:

lua: answer.lua:10: No answer for this statement is false

stack traceback:

[C]: in function 'error'

answer.lua:10: in function 'Answer'

answer.lua:15: in main chunk

[C]: ?

The same error-handling mechanism is used for user-generated errors as for those originating in the Lua core.

You can pass an integer as an optional second argument to error. Passing 0 suppresses line number information in the error message. If you specify the value 1 (the default if the argument is missing), the line number where error was called will be displayed. A higher value tells Lua how far down on the call stack to reach to determine which line number to display. This is useful if you write an error handler that in turn calls error. In this case, you don’t want to mislead the programmer by displaying a line in your handler; you want to show the line where the call to your handler was made, so a value of 2 is appropriate.

Anticipating Error Conditions

One way to cope with the problem of runtime errors is to diligently check for the conditions that can lead to an error. For example, a function that prints the sum of its three arguments could be written as follows:

local function Fnc(A, B, C)

A, B, C = tonumber(A), tonumber(B), tonumber(C)

print(A and B and C and A + B + C

or "Three numbers expected")

end

print("Position 1")

Fnc(1, "2", 3)

print("Position 2")

Fnc(1, "Lua", 3)

print("Position 3")

Here, where the conditions needed to calculate and print the sum of three numbers are not met, an alternative is printed instead:

Position 1

6

Position 2

Three numbers expected

Position 3

After reaching position 3, the program terminates normally, even though the values passed in the second call to Fnc included a nonnumeric value.

Anticipating an error condition allows you to handle the situation appropriately. For example, in an interactive program you could allow the user to specify an alternate course of action or to repeat an action with different input values. If it is appropriate to terminate the program, at least this can be done gracefully, closing open resources as needed and reporting the condition in an expected fashion.

Working with Return Values

Because the preceding example uses the print function, its use is restricted to programs that can work with the standard output channel. Its output is directed to the user rather than the caller. In all but the simplest cases, you’ll want to avoid this kind of restriction by making your functions independent of the kind of program that uses them. This practice enables you to build up a library of functions that should be portable to different platforms. The key to this is to have functions and their callers communicate by means of parameter lists and return values. The actual input and output to the system can then be handled at a higher level by the caller of the platform-independent functions.

One of Lua’s many distinctive features is that it allows functions to return multiple values. A convention has evolved regarding how functions should indicate success and failure. If a function succeeds, its first return value should be something other than false or nil. Usually this return value is the principal value the function was called on to produce, like a file handle or string capture. This value can be followed by other pertinent values. If the function fails, its first value should be nil. This is followed by something that explains why the error occurred, usually an error message but possibly something else like a table or numeric error code.

The example can be reworked to follow this convention:

local function Fnc(A, B, C)

local Sum, ErrStr

A, B, C = tonumber(A), tonumber(B), tonumber(C)

if A and B and C then

Sum = A + B + C

else

ErrStr = "Three numbers expected"

end

return Sum, ErrStr

end

print("Position 1")

local Sum, ErrStr = Fnc(1, "2", 3)

print(Sum or ErrStr)

print("Position 2")

local Sum, ErrStr = Fnc(1, nil, 3)

print(Sum or ErrStr)

print("Position 3")

The output is like that of the previous example:

Position 1

6

Position 2

Three numbers expected

Position 3

You’ll occasionally see a clever use of assert that utilizes Lua’s return value convention. For example:

Hnd = assert(io.open("index.html", "r"))

The io.open function returns nil followed by an error message if the file cannot be opened; otherwise, it returns the handle to the opened file. In either case, these return values are passed directly to assert. In the event that io.open fails, the first argument to assert will be nil. This causes assert to issue a runtime error using as its error value the second argument it receives, namely the error message returned by io.open. If, on the other hand, io.open succeeds, assert receives as its first argument the handle to the open file. Because this value is neither false nor nil, it simply returns this value and does not issue an error.

The problem with using assert is that, in the event of a failed assertion, the default action is to terminate the program. In the case of the example shown here, there may be good reasons why the file index.html cannot be opened, and to bring the entire program to a halt just because the file can’t beopened is especially heavy-handed. In the next section, you’ll learn about structured programming techniques that let you recover gracefully and simply from conditions like this. The assert function is extremely useful for ferreting out wrong assumptions in your code, but it’s not the best approach to handle conditions that are beyond the programmer’s control.

Another convention has evolved to handle the returns values of a function that is called indirectly through another function. Like all values in Lua, a function is a first class value and can be passed as an argument, something you’ve already seen with the comparison function that can be optionally passed to table.sort. In some cases, a function’s job can simply be to call another function in a modified runtime environment. The pcall and xpcall functions (described later in this chapter) are examples of this. However, the added indirection requires some means to distinguish between errors caused by the function that is called directly and the one that is called indirectly. Typically in this case, the directly-called function returns true if it succeeds; this is followed by the return values of the indirectly-called function whether it succeeds or fails. If the directly-called function fails, it returns false followed by an error message.

Structuring Code

In the examples you’ve seen so far, the program code has been structured to visually indicate the blocks as Lua sees them. For example, the statements following an if expression are indented to show what gets executed if the expression evaluates to true. The benefit to this structuring is that you can tell at a glance what state the program is in—for example, which files are open and which conditions are currently met.

Following is an example that uses Lua’s return value convention with structured programming to generate a file listing with line numbers. It shows how error handling can be integrated with block structuring to handle errors in a predictable and robust way. As you study the example, ask yourself what happens under different scenarios. For example, what happens to the source file handle if the destination file cannot be opened?

local function FileCopyLineNum(SrcFileStr, DstFileStr)

local SrcHnd, DstHnd, ErrStr, Line

SrcHnd, ErrStr = io.open(SrcFileStr, "r")

if SrcHnd then

DstHnd, ErrStr = io.open(DstFileStr, "w")

if DstHnd then

Line = 0

for Str in SrcHnd:lines() do

Line = Line + 1

DstHnd:write(Line, " ", Str, "\n")

end

if Line == 0 then

ErrStr = SrcFileStr .. ": File is empty"

Line = nil

end

DstHnd:close()

end

SrcHnd:close()

end

return Line, ErrStr

end

local Count, ErrStr = FileCopyLineNum("index.html", "index.lst")

io.write(Count and ("OK: count " .. Count) or ErrStr, "\n")

If index.html does not exist, this script outputs the following:

index.html: No such file or directory

If this file exists but is empty, the output is this:

index.html: File is empty

If index.html exists and has lines in it, and index.lst can be opened for writing, the output looks like this:

OK: count 243

The general idea behind structuring your code is to place operations in their correct position. For example, an indented block follows the conditional statement that tests whether the source file has been successfully opened. You can bring much clarity to your code by ensuring that this condition remains valid through the entire block, and that the condition is invalid outside of the block. In this example, that’s done by closing the source file at the very end of the conditional block. You don’t need to check whether the file is currently open and ready to be closed —the block structure guarantees that it is, regardless of . what happened inside the block. Whether or not the destination file is successfully opened, you know .

from the visual representation of the code where the source file is open and where it needs to be closed.

Why would it be a problem to close the source file as soon as its contents have been copied? A glance at the indentation of the code should tell you immediately: the source file would not be properly closed if an error occurred while opening the destination file.

Lua enables you to keep your program clear by declaring local variables only where you need them, that is, to keep their scope at a minimum. Here, the declaration of DstHnd could have been deferred to the point after the source file has been opened. However, the declaration of Line and ErrStrneed to be where they are, because they are used as return values. Furthermore, neither should be redeclared in an inner block because this would mask the outer values. Beginners to Lua often wish that variables would be local by default, but doing so would make this powerful form of scope control impossible.

Notice that no assumptions about the user interface are made in the FileCopyLineNum function. Communication with this function occur through its parameter list and its return values.

A slight visual problem occurs in the destination file when the number of digits in the line number changes. This can be remedied with string formatting, as described in Chapter 5.

Bigger problems have to do with guarding against unintended input. What if the function is called with nonstring arguments? What if a binary file is specified as the source file? Worse still, what if an important system file is specified as the destination file?

Calling the function with nonstring arguments is a code issue, and in this case you’ll learn about the problem when you call io.open. Like a syntax error, after you correct the problem, it won’t be an issue anymore. If this routine merely stored the arguments for later processing, some assertions might be necessary to ensure that each argument is a string.

As written, the FileCopyLineNumfunction assumes that the source file is a text file. In the copy loop, you could check for the presence of unexpected characters or unexpectedly long source lines. If either of these occurs, you could terminate the loop and proceed appropriately.

The risk of overwriting an important file is more difficult to address. The relevant question is whether this routine is at the right level to implement a safeguard. Clearly, io.open isn’t making the check, and it could be convincingly argued that FileCopyLineNum shouldn’t either. If it doesn’t, the burden is on the caller to make sure that the destination file is safe to create or overwrite. The important lesson is that you need to consider these issues and strive to cover them at the right place in your code.

Even if the block following a resource test does not itself contain nested indented blocks, it is still a good policy to defer closing the resource until the end of the block, because as you refine the program and possibly add new conditionals, knowing that the resource is open throughout the block is one less thing you have to verify.

Some programmers criticize, sometimes with alarming fervor, this type of code structuring, complaining that indentation levels can become excessive and that even small changes to a routine’s logic can necessitate shifting large blocks to the left or right (something that is easy to do in a decent text editor). In practice, deep nesting indicates a need to break blocks into functions. Doing so makes your program more modular and reduces the degree of indentation. The functions in the following example are fictitious and, for simplicity’s sake, are called without any arguments and portrayed without error handling:

A = ResourceOpen()

if A then

B = ItemFirst()

while B do

C = ResourceOpen()

if C then

for D in E() do

if F then

DoSomething()

end

end

ResourceClose(C)

end

B = ItemNext(B)

end

ResourceClose(A)

end

This can be rewritten as follows:

function Moon(B, C)

for D in E() do

if F then

DoSomething()

end

end

end

function Sun(B)

C = ResourceOpen()

if C then

Moon(B, C)

ResourceClose(C)

end

end

A = ResourceOpen()

if A then

B = ItemFirst()

while B do

Sun(B)

B = ItemNext(B)

end

ResourceClose(A)

end

Error-Containment Functions

In the examples you have seen so far, runtime errors result in the termination of the entire program. Fortunately, this can be avoided. Lua provides a mechanism — the protected environment—to contain the damage caused by an error condition. The Lua pcall and the xpcall functions enable you to suppress the propagation of an error.

The pcall Function

Any Lua function, including Lua scripts loaded from disk and converted to a function by loadfile, can be run in a protected environment. You do this by calling pcall with the function to be called as its first argument. Additional arguments to pcall are passed as arguments to this function. The principal difference between calling a function directly and calling it through pcall is the way errors are handled. As you’ve seen so far, if an error occurs in a function that is called directly, the Lua interpreter responds by displaying the stack traceback and terminating the program. If, on the other hand, an error occurs in a function that has been invoked by pcall, the error is reported as one of the return values of pcall. You can handle the error in whatever way you consider appropriate.

Try It Out

Using pcall

In this Try It Out, you’ll see the pcall function in action. The following code is a revision of the first example from this chapter adapted to run in a protected environment.

1. With your text editor, create a new file with the following contents:

function C()

print("C 1")

print(1 + nil)

print("C 2")

end

function B()

print("B 1")

C()

print("B 2")

end

function A()

print("A 1")

B()

print("A 2")

end

print("Main 1")

local Code, ErrStr = pcall(A)

print("Main 2", Code and "Success" or ErrStr)

2. Save this script as err.lua.

3. Run the script with the Lua interpreter, like this:

lua err.lua

The output is as follows:

Main 1

A 1

B 1

C 1

Main 2 err.lua:3: attempt to perform arithmetic on a nil value

How It Works

The main clue to understanding what happened is the lack of "C2", "B2", and "A 2" markers. If this gives you the notion that when the error occurred control was transferred directly back to pcall, you’re entirely correct.

Remember, it is the call stack that allows functions to return properly. In effect, the stack is Lua’s only memory regarding where it came from and where it should return to. The pcall function effectively marks the current position on the stack and arranges with Lua to return to that mark—the recover point—in the event of an error. Up until the error occurred, the stack functioned as expected. You can follow this in more detail by adding a call to debug.traceback in the print statements. For example, replace print("A 1") with print(debug.traceback("A 1")).

A protected call will keep your program from sinking after a runtime error, but it may tilt frightfully to starboard. Imagine that the "A 1", "B1", and "C 1" markers indicate positions where resources such as database connections and files are opened, and the markers "A 2", "B 2", and "C 2" indicate the positions where the resources are closed. When pcall returns with a return code indicating that an error took place, it may be difficult to programmatically determine which resources are in need of closing.

Some resources—in such as userdata resources that have been specially programmed—will close themselves when they are no longer accessible to your program and are consequently collected as garbage. Using Lua’s C programming interface, you can create resource handles to behave this way. However, you should not depend on this behavior to close resources. A very large number of open resources may accumulate between garbage collection cycles, and this can have an adverse effect on the operation of your program.

Another method to manage dangling resources is to pass a newly constructed local table to your function by means of pcall. You can use this table to store handles for open resources, among other things. When a resource is opened, its handle is stored in the table. After the resource is closed, its handle is set to nil . This table would be passed as an argument to all functions in the call chain. Unfortunately, this approach couples these functions in a way that may be undesirable. It also means that the caller of the protected function must know, given an assortment of handles, how to close the resources. A variation on this theme would be to store a closure that would close the resource instead of a resource handle.

The caller of a protected function must also, in the event of an error, determine the best subsequent course of action. Should the program clean up and terminate? Should it invoke the protected function again with different arguments? One action your program should definitely not take is to proceed as if nothing happened.

The xpcall Function

You may have noticed that the error message returned by pcall does not have a stack traceback. After pcall returns, all stack levels between pcall and the place where the error occurred are no longer . accessible, so no traceback can be constructed. The xpcall function is like pcall, except that you specify as its second argument an error handler that is called before Lua transfers control back to the place where xpcall was called. This handler receives the error message and returns the new error value, usually the same error message after it has been augmented with a stack traceback. Thexpcall function differs from the pcall function in two respects:

· It uses an error handler.

· It is unable to pass arguments to the function it calls.

Here’s an example of its use:

function A()

print(1 + nil)

end

print(xpcall(A, debug.traceback))

This outputs the following:

false err.lua:2: attempt to perform arithmetic on a nil value

stack traceback:

err.lua:2: in function <err.lua:1>

[C]: in function 'xpcall'

err.lua:5: in main chunk

[C]: ?

Here, the debug.traceback function was used as a handler, but you can write your own as well. It can return something other than an error string if you want.

User-Written Scripts

Many applications employ Lua so that users can prepare and run their own extension scripts. However, you should run these scripts in protected mode to contain any errors that may occur.

Most users have experienced programs that run fine as long as nothing unexpected occurs, but operate erratically after they attempt to recover from an error. If your application supports the execution of user-provided Lua scripts, you need to guard against destabilizing your entire application as a result of running an errant script. As you learned in Chapter 4, Lua provides sandboxing mechanisms to isolate these user-written scripts from each other and from the host application. In the next chapter, you’ll see that Lua supports a way to require that only local variables be created by a script. Doing this helps to ensure that resources are cleaned up properly in the event of an error. Similarly, functions that could pose a risk to the operating environment are easily made unavailable to the user-written scripts.

Locating Errors

The diagnostic message and stack traceback that Lua presents when a runtime error occurs are often sufficient for you to identify exactly what went wrong. However, sometimes an error occurs long after the source of the problem has been executed. For example, an error might indicate that arithmetic was attempted on a nil value, and you are left wondering how a certain variable ever became nil. A good approach when you don’t have a clue where something like this may have occurred is to use the print function or a message box routine to temporarily display important values at various points where you think the problem may have originated. But rather than sprinkling such calls haphazardly throughout your code, you’ll usually do better by attempting to repeatedly divide the problem area in half.

Summary

In this chapter, you’ve learned to do the following:

· Look for and correct syntax errors.

· Handle runtime errors (both code-based and data-based).

· Understand Lua’s call stack.

· Use assert when it’s desirable to do so.

· Read stack tracebacks.

· Structure your code with an emphasis on resource lifespan.

· Protect a function call so that the entire application isn’t abnormally ended if it causes a runtime error.

Any mechanism that enables you to understand the internal state of your program is valuable in ensuring that it is functioning as expected or in uncovering errors. In the next chapter, you’ll learn about Lua modules, and one of the examples shows you how to generate a tree diagram of Lua tables. A rendering like that can provide a lot more information than a simple print statement can and is consequently a good tool to have when tracking down program errors.

Exercises

1. In the Lua interpreter, issue the following statement:

> print(1 + nil)

Why doesn’t print show up in the resulting stack traceback?

2. How would you add the prefix ‘Programmer error’ to the stack traceback?

3. The pcall function accepts arguments, and the xpcall function accepts an error handler. How can you write a protected function caller that accepts both arguments and an error handler?