• No results found

Precompiled Code

In document Programming in Lua 3ed (Page 94-98)

Compilation, Execution, and Errors

8.2 Precompiled Code

In a production-quality program that needs to run external code, you should handle any errors reported when loading a chunk. Moreover, you may want to run the new chunk in a protected environment, to avoid unpleasant side effects.

We will discuss environments in detail in Chapter 14.

8.2 Precompiled Code

As I mentioned in the beginning of this chapter, Lua precompiles source code before running it. Lua also allows us to distribute code in precompiled form.

The simplest way to produce a precompiled file — also called a binary chunk in Lua jargon — is with theluac program, which comes in the standard distri-bution. For instance, the next call creates a new fileprog.lc with a precompiled version of a fileprog.lua:

$ luac -o prog.lc prog.lua

The interpreter can execute this new file just like normal Lua code, performing exactly as with the original source:

$ lua prog.lc

Lua accepts precompiled code mostly anywhere it accepts source code. In partic-ular, bothloadfile and load accept precompiled code.

We can write a poor man’sluac directly in Lua:

p = loadfile(arg[1]) f = io.open(arg[2], "wb") f:write(string.dump(p)) f:close()

The key function here isstring.dump: it receives a Lua function and returns its precompiled code as a string, properly formatted to be loaded back by Lua.

Theluac program offers some other interesting options. In particular, op-tion-l lists the opcodes that the compiler generates for a given chunk. As an example, Listing 8.1 shows the output ofluac with option -l on the following one-line file:

a = x + y - z

(We will not discuss the internals of Lua in this book; if you are interested in more details about those opcodes, a web search for “lua opcode” should give you relevant material.)

Code in precompiled form is not always smaller than the original, but it loads faster. Another benefit is that it gives a protection against accidental changes in sources. Unlike source code, however, maliciously corrupted binary code can crash the Lua interpreter or even execute user-provided machine code. When running usual code, there is nothing to worry about. However, you should avoid running untrusted code in precompiled form. Theload function has an option exactly for this task.

Listing 8.1. Example of output fromluac -l:

main <stdin:0,0> (7 instructions, 28 bytes at 0x988cb30)

0+ params, 2 slots, 0 upvalues, 0 locals, 4 constants, 0 functions 1 [1] GETGLOBAL 0 -2 ; x

2 [1] GETGLOBAL 1 -3 ; y 3 [1] ADD 0 0 1 4 [1] GETGLOBAL 1 -4 ; z 5 [1] SUB 0 0 1 6 [1] SETGLOBAL 0 -1 ; a 7 [1] RETURN 0 1

Besides its required first argument, load has three more arguments, all of them optional. The second is a name for the chunk, which is used only in error messages. The fourth argument is an environment, which we will discuss in Chapter 14. The third argument is the one we are interested here; it controls what kinds of chunks can be loaded. If present, this argument must be a string: the string “t” allows only textual (normal) chunks; “b” allows only binary (precompiled) chunks; “bt”, the default, allows both formats.

8.3 C Code

Unlike code written in Lua, C code needs to be linked with an application before use. In several popular operating systems, the easiest way to do this link is with a dynamic linking facility. However, this facility is not part of the ANSI C specification; therefore, there is no portable way to implement it.

Normally, Lua does not include facilities that cannot be implemented in ANSI C. However, dynamic linking is different. We can view it as the mother of all other facilities: once we have it, we can dynamically load any other facility that is not in Lua. Therefore, in this particular case, Lua breaks its portability rules and implements a dynamic linking facility for several platforms. The standard implementation offers this support for Windows, Mac OS X, Linux, FreeBSD, Solaris, and most other UNIX implementations. It should not be difficult to extend this facility to other platforms; check your distribution. (To check it, runprint(package.loadlib("a","b")) from the Lua prompt and see the result. If it complains about a non-existent file, then you have dynamic linking facility. Otherwise, the error message should indicate that this facility is not supported or not installed.)

Lua provides all the functionality of dynamic linking through a single func-tion, calledpackage.loadlib. It has two string arguments: the complete path of a library and the name of a function in that library. So, a typical call to it looks like the next fragment:

8.4 Errors 77

local path = "/usr/local/lib/lua/5.1/socket.so"

local f = package.loadlib(path, "luaopen_socket")

Theloadlib function loads the given library and links Lua to it. However, it does not call the given function. Instead, it returns the C function as a Lua function. If there is any error loading the library or finding the initialization function,loadlib returns nil plus an error message.

Theloadlib function is a very low-level function. We must provide the full path of the library and the correct name for the function (including occasional leading underscores included by the compiler). More often than not, we load C libraries usingrequire. This function searches for the library and uses loadlib to load an initialization function for the library. When called, this initialization function builds and returns a table with the functions from that library, much as a typical Lua library does. We will discussrequire in Section 15.1, and more details about C libraries in Section 27.3.

8.4 Errors

Errare humanum est. Therefore, we must handle errors the best way we can.

Because Lua is an extension language, frequently embedded in an application, it cannot simply crash or exit when an error happens. Instead, whenever an error occurs, Lua ends the current chunk and returns to the application.

Any unexpected condition that Lua encounters raises an error. Errors occur when you (that is, your program) try to add values that are not numbers, call values that are not functions, index values that are not tables, and so on. (You can modify this behavior using metatables, as we will see later.) You can also explicitly raise an error calling theerror function with an error message as an argument. Usually, this function is the appropriate way to signal errors in your code:

print "enter a number:"

n = io.read("*n")

if not n then error("invalid input") end

This construction of callingerror subject to some condition is so common that Lua has a built-in function just for this job, calledassert:

print "enter a number:"

n = assert(io.read("*n"), "invalid input")

Theassert function checks whether its first argument is not false and simply returns this argument; if the argument is false, assert raises an error. Its second argument, the message, is optional. Beware, however, that assert is a regular function. As such, Lua always evaluates its arguments before calling the function. Therefore, if you have something like

n = io.read()

assert(tonumber(n), "invalid input: " .. n .. " is not a number")

Lua will always do the concatenation, even whenn is a number. It may be wiser to use an explicit test in such cases.

When a function finds an unexpected situation (an exception), it can assume two basic behaviors: it can return an error code (typically nil) or it can raise an error, calling functionerror. There are no fixed rules for choosing between these two options, but we can provide a general guideline: an exception that is easily avoided should raise an error; otherwise, it should return an error code.

For instance, let us consider the sin function. How should it behave when called on a table? Suppose it returns an error code. If we need to check for errors, we would have to write something like

local res = math.sin(x) if not res then -- error?

<error-handling code>

However, we could as easily check this exception before calling the function:

if not tonumber(x) then -- x is not a number?

<error-handling code>

Frequently we check neither the argument nor the result of a call tosin; if the argument is not a number, it means that probably there is something wrong in our program. In such situations, to stop the computation and to issue an error message is the simplest and most practical way to handle the exception.

On the other hand, let us consider theio.open function, which opens a file.

How should it behave when asked to read a file that does not exist? In this case, there is no simple way to check for the exception before calling the function. In many systems, the only way of knowing whether a file exists is by trying to open it. Therefore, ifio.open cannot open a file because of an external reason (such as “file does not exist” or “permission denied”), it returns nil, plus a string with the error message. In this way, you have a chance to handle the situation in an appropriate way, for instance by asking the user for another file name:

local file, msg repeat

print "enter a file name:"

local name = io.read()

if not name then return end -- no input file, msg = io.open(name, "r")

if not file then print(msg) end until file

If you do not want to handle such situations, but still want to play safe, you simply useassert to guard the operation:

file = assert(io.open(name, "r"))

This is a typical Lua idiom: ifio.open fails, assert will raise an error.

file = assert(io.open("no-file", "r"))

--> stdin:1: no-file: No such file or directory

Notice how the error message, which is the second result fromio.open, goes as the second argument toassert.

In document Programming in Lua 3ed (Page 94-98)