主页 > 软件开发  > 

跟着Lua5.1官方参考文档学习Lua(4)

跟着Lua5.1官方参考文档学习Lua(4)

文章目录 2.7 – Error Handling2.8 – Metatables2.9 – Environments

2.7 – Error Handling

Because Lua is an embedded extension language, all Lua actions start from C code in the host program calling a function from the Lua library (see lua_pcall). Whenever an error occurs during Lua compilation or execution, control returns to C, which can take appropriate measures (such as printing an error message).

Lua code can explicitly generate an error by calling the error function. If you need to catch errors in Lua, you can use the pcall function.

例子:使用 lua_call 调用 Lua 函数

#include <stdio.h> #include <lua.h> #include <lauxlib.h> #include <lualib.h> int main() { lua_State *L = luaL_newstate(); // 创建一个新的 Lua 状态机 luaL_openlibs(L); // 打开 Lua 标准库 // Lua 脚本代码,定义一个简单的函数,产生错误 const char *script = "function foo() \n" " error('test error')\n" "end\n"; // 加载并执行 Lua 脚本 if (luaL_dostring(L, script) != LUA_OK) { printf("Error loading script: %s\n", lua_tostring(L, -1)); lua_close(L); return 1; } // 调用 Lua 函数,并处理错误 lua_getglobal(L, "foo"); // 使用 lua_call,如果出现错误,会直接终止进程 lua_call(L, 0, 0); lua_close(L); // 关闭 Lua 状态机 return 0; }

输出

PANIC: unprotected error in call to Lua API ([string "function foo() ..."]:2: test error)

出现错误会直接终止进程

例子:使用 lua_pcall 调用 Lua 函数

#include <stdio.h> #include <lua.h> #include <lauxlib.h> #include <lualib.h> int main() { lua_State *L = luaL_newstate(); // 创建一个新的 Lua 状态机 luaL_openlibs(L); // 打开 Lua 标准库 // Lua 脚本代码,定义一个简单的函数,产生错误 const char *script = "function foo() \n" " error('test error')\n" "end\n"; // 加载并执行 Lua 脚本 if (luaL_dostring(L, script) != LUA_OK) { printf("Error loading script: %s\n", lua_tostring(L, -1)); lua_close(L); return 1; } // 调用 Lua 函数,并处理错误 lua_getglobal(L, "foo"); int ret = lua_pcall(L, 0, 0, 0); if (ret != LUA_OK) { // 如果发生错误,打印错误信息 printf("Error occurred: %s\n", lua_tostring(L, -1)); } lua_close(L); // 关闭 Lua 状态机 return 0; }

输出

Error occurred: [string "function foo() ..."]:2: test error

例子:使用 lua_pcall,自定义错误处理函数

#include <stdio.h> #include <lua.h> #include <lauxlib.h> #include <lualib.h> // 错误处理函数 int my_error_handler(lua_State *L) { const char *msg = lua_tostring(L, -1); // 获取错误信息 printf("Error in Lua code: %s\n", msg); // 输出错误信息 lua_pushliteral(L, "Error handled in my_error_handler"); return 1; // 返回 1,返回新的错误消息 } int main() { lua_State *L = luaL_newstate(); // 创建一个新的 Lua 状态机 luaL_openlibs(L); // 打开 Lua 标准库 // 将错误处理函数压入栈中 lua_pushcfunction(L, my_error_handler); // 定义 Lua 脚本,故意写一个错误的脚本(调用了未定义的函数 'foo') const char *script = "foo() -- 这是一个错误,foo 未定义\n"; if (luaL_loadstring(L, script) != 0) { printf("load error!\n"); return 1; } // 使用 lua_pcall 执行 Lua 代码,并指定错误处理函数 int ret = lua_pcall(L, 0, 0, -2); // 执行 Lua 代码 if (ret != LUA_OK) { // 如果发生错误,错误处理函数会将错误信息入栈 printf("error msg: %s\n", lua_tostring(L, -1)); } lua_close(L); // 关闭 Lua 状态机 return 0; }

输出

Error in Lua code: [string "foo() -- 这是一个错误,foo 未定义..."]:1: attempt to call global 'foo' (a nil value) error msg: Error handled in my_error_handler

注意:error() 函数可以返回任意的值,并不是只能返回字符串。

2.8 – Metatables

Every value in Lua can have a metatable. This metatable is an ordinary Lua table that defines the behavior of the original value under certain special operations. You can change several aspects of the behavior of operations over a value by setting specific fields in its metatable.

For instance, when a non-numeric value is the operand of an addition, Lua checks for a function in the field "__add" in its metatable. If it finds one, Lua calls this function to perform the addition.

We call the keys in a metatable events and the values metamethods. In the previous example, the event is "add" and the metamethod is the function that performs the addition.

You can query the metatable of any value through the getmetatable function.

You can replace the metatable of tables through the setmetatable function. You cannot change the metatable of other types from Lua (except by using the debug library); you must use the C API for that.

Tables and full userdata have individual metatables (although multiple tables and userdata can share their metatables). Values of all other types share one single metatable per type; that is, there is one single metatable for all numbers, one for all strings, etc.

A metatable controls how an object behaves in arithmetic operations, order comparisons, concatenation, length operation, and indexing. A metatable also can define a function to be called when a userdata is garbage collected. For each of these operations Lua associates a specific key called an event. When Lua performs one of these operations over a value, it checks whether this value has a metatable with the corresponding event. If so, the value associated with that key (the metamethod) controls how Lua will perform the operation.

Metatables control the operations listed next. Each operation is identified by its corresponding name. The key for each operation is a string with its name prefixed by two underscores, ‘__’; for instance, the key for operation “add” is the string "__add". The semantics of these operations is better explained by a Lua function describing how the interpreter executes the operation.

The code shown here in Lua is only illustrative; the real behavior is hard coded in the interpreter and it is much more efficient than this simulation. All functions used in these descriptions (rawget, tonumber, etc.) are described in §5.1. In particular, to retrieve the metamethod of a given object, we use the expression

metatable(obj)[event]

This should be read as

rawget(getmetatable(obj) or {}, event)

That is, the access to a metamethod does not invoke other metamethods, and the access to objects with no metatables does not fail (it simply results in nil).

“add”: the + operation.

The function getbinhandler below defines how Lua chooses a handler for a binary operation. First, Lua tries the first operand. If its type does not define a handler for the operation, then Lua tries the second operand.

function getbinhandler (op1, op2, event) return metatable(op1)[event] or metatable(op2)[event] end

By using this function, the behavior of the op1 + op2 is

function add_event (op1, op2) local o1, o2 = tonumber(op1), tonumber(op2) if o1 and o2 then -- both operands are numeric? return o1 + o2 -- '+' here is the primitive 'add' else -- at least one of the operands is not numeric local h = getbinhandler(op1, op2, "__add") if h then -- call the handler with both operands return (h(op1, op2)) else -- no handler available: default behavior error(···) end end end

“sub”: the - operation. Behavior similar to the “add” operation.

“mul”: the * operation. Behavior similar to the “add” operation.

“div”: the / operation. Behavior similar to the “add” operation.

“mod”: the % operation. Behavior similar to the “add” operation, with the operation o1 - floor(o1/o2)*o2 as the primitive operation.

“pow”: the ^ (exponentiation) operation. Behavior similar to the “add” operation, with the function pow (from the C math library) as the primitive operation.

“unm”: the unary - operation.

function unm_event (op) local o = tonumber(op) if o then -- operand is numeric? return -o -- '-' here is the primitive 'unm' else -- the operand is not numeric. -- Try to get a handler from the operand local h = metatable(op).__unm if h then -- call the handler with the operand return (h(op)) else -- no handler available: default behavior error(···) end end end

“concat”: the … (concatenation) operation.

function concat_event (op1, op2) if (type(op1) == "string" or type(op1) == "number") and (type(op2) == "string" or type(op2) == "number") then return op1 .. op2 -- primitive string concatenation else local h = getbinhandler(op1, op2, "__concat") if h then return (h(op1, op2)) else error(···) end end end

“len”: the # operation.

function len_event (op) if type(op) == "string" then return strlen(op) -- primitive string length elseif type(op) == "table" then return #op -- primitive table length else local h = metatable(op).__len if h then -- call the handler with the operand return (h(op)) else -- no handler available: default behavior error(···) end end end

See §2.5.5 for a description of the length of a table.

“eq”: the == operation. The function

getcomphandler

defines how Lua chooses a metamethod for comparison operators. A metamethod only is selected when both objects being compared have the same type and the same metamethod for the selected operation.

function getcomphandler (op1, op2, event) if type(op1) ~= type(op2) then return nil end local mm1 = metatable(op1)[event] local mm2 = metatable(op2)[event] if mm1 == mm2 then return mm1 else return nil end end

The “eq” event is defined as follows:

function eq_event (op1, op2) if type(op1) ~= type(op2) then -- different types? return false -- different objects end if op1 == op2 then -- primitive equal? return true -- objects are equal end -- try metamethod local h = getcomphandler(op1, op2, "__eq") if h then return (h(op1, op2)) else return false end end

a ~= b is equivalent to not (a == b).

“lt”: the < operation.

function lt_event (op1, op2) if type(op1) == "number" and type(op2) == "number" then return op1 < op2 -- numeric comparison elseif type(op1) == "string" and type(op2) == "string" then return op1 < op2 -- lexicographic comparison else local h = getcomphandler(op1, op2, "__lt") if h then return (h(op1, op2)) else error(···) end end end

a > b is equivalent to b < a.

“le”: the <= operation.

function le_event (op1, op2) if type(op1) == "number" and type(op2) == "number" then return op1 <= op2 -- numeric comparison elseif type(op1) == "string" and type(op2) == "string" then return op1 <= op2 -- lexicographic comparison else local h = getcomphandler(op1, op2, "__le") if h then return (h(op1, op2)) else h = getcomphandler(op1, op2, "__lt") if h then return not h(op2, op1) else error(···) end end end end

a >= b is equivalent to b <= a. Note that, in the absence of a “le” metamethod, Lua tries the “lt”, assuming that a <= b is equivalent to not (b < a).

补充:


As we will see later, in Chapter 20, the string library sets a metatable for strings. All other types by default have no metatable:

print(getmetatable("hi")) --> table: 0x80772e0 print(getmetatable(10)) --> nil

例子:元表的使用

Set = {} local mt = {} -- metatable for sets -- create a new set with the values of the given list function Set.new (l) local set = {} setmetatable(set, mt) for _, v in ipairs(l) do set[v] = true end return set end function Set.union (a, b) if getmetatable(a) ~= mt or getmetatable(b) ~= mt then error("attempt to ’add’ a set with a non-set value", 2) end local res = Set.new{} for k in pairs(a) do res[k] = true end for k in pairs(b) do res[k] = true end return res end function Set.intersection (a, b) local res = Set.new{} for k in pairs(a) do res[k] = b[k] end return res end function Set.tostring (set) local l = {} -- list to put all elements from the set for e in pairs(set) do l[#l + 1] = e end return "{" .. table.concat(l, ", ") .. "}" end mt.__tostring = Set.tostring mt.__add = Set.union mt.__mul = Set.intersection --[[ Metatables also allow us to give meaning to the relational operators, through the metamethods __eq (equal to), __lt (less than), and __le (less than or equal to). There are no separate metamethods for the other three relational operators, as Lua translates a~=b to not(a==b), a>b to b<a, and a>=b to b<=a. Until Lua 4.0, all order operators were translated to a single one, by translating a<=b to not(b<a). However, this translation is incorrect when we have a partial order, that is, when not all elements in our type are properly ordered. For instance, floating-point numbers are not totally ordered in most machines, because of the value Not a Number (NaN). According to the IEEE 754 standard, currently adopted by virtually all floating-point hardware, NaN represents undefined values, such as the result of 0=0. The standard specifies that any comparison that involves NaN should result in false. This means that NaN<=x is always false, but x<NaN is also false. It also implies that the translation from a<=b to not(b<a) is not valid in this case. In our example with sets, we have a similar problem. An obvious (and useful) meaning for <= in sets is set containment: a<=b means that a is a subset of b. With this meaning, again it is possible that both a<=b and b<a are false; therefore, we need separate implementations for __le (less or equal) and __lt (less than): --]] mt.__le = function (a, b) -- set containment for k in pairs(a) do if not b[k] then return false end end return true end mt.__lt = function (a, b) return a <= b and not (b <= a) end mt.__eq = function (a, b) return a <= b and b <= a end s1 = Set.new{10, 20, 30, 50} s2 = Set.new{30, 1} print(getmetatable(s1)) --> table: 00672B60 print(getmetatable(s2)) --> table: 00672B60 s3 = s1 + s2 --[[ (Function print always calls tostring to format its output.) However, when formatting any value, tostring first checks whether the value has a __tostring metamethod. In this case, tostring calls the metamethod to do its job, passing the object as an argument. Whatever this metamethod returns is the result of tostring. --]] print(s3) --> {1, 10, 20, 30, 50} s1 = Set.new{2, 4} s2 = Set.new{4, 10, 2} print(s1 <= s2) --> true print(s1 < s2) --> true print(s1 >= s1) --> true print(s1 > s1) --> false print(s1 == s2 * s1) --> true

Functions setmetatable and getmetatable also use a metafield, in this case to protect metatables. Suppose you want to protect your sets, so that users can neither see nor change their metatables. If you set a __metatable field in the metatable, getmetatable will return the value of this field, whereas setmetatable will raise an error:

mt.__metatable = "not your business" s1 = Set.new{} print(getmetatable(s1)) --> not your business setmetatable(s1, {}) stdin:1: cannot change protected metatable

“index”: The indexing access table[key].

function gettable_event (table, key) local h if type(table) == "table" then local v = rawget(table, key) if v ~= nil then return v end h = metatable(table).__index if h == nil then return nil end else h = metatable(table).__index if h == nil then error(···) end end if type(h) == "function" then return (h(table, key)) -- call the handler else return h[key] -- or repeat operation on it end end

补充:I said earlier that, when we access an absent field in a table, the result is nil. This is true, but it is not the whole truth. Actually, such accesses trigger the interpreter to look for an __index metamethod: if there is no such method, as usually happens, then the access results in nil; otherwise, the metamethod will provide the result.

The archetypal example here is inheritance. Suppose we want to create several tables describing windows. Each table must describe several window parameters, such as position, size, color scheme, and the like. All these parameters have default values and so we want to build window objects giving only the non-default parameters. A first alternative is to provide a constructor that fills in the absent fields. A second alternative is to arrange for the new windows to inherit any absent field from a prototype window. First, we declare the prototype and a constructor function, which creates new windows sharing a metatable:

Window = {} -- create a namespace -- create the prototype with default values Window.prototype = {x=0, y=0, width=100, height=100} Window.mt = {} -- create a metatable -- declare the constructor function function Window.new (o) setmetatable(o, Window.mt) return o end Window.mt.__index = Window.prototype w = Window.new{x=10, y=20} print(w.width) --> 100

“newindex”: The indexing assignment table[key] = value.

function settable_event (table, key, value) local h if type(table) == "table" then local v = rawget(table, key) if v ~= nil then rawset(table, key, value); return end h = metatable(table).__newindex if h == nil then rawset(table, key, value); return end else h = metatable(table).__newindex if h == nil then error(···) end end if type(h) == "function" then h(table, key,value) -- call the handler else h[key] = value -- or repeat operation on it end end

补充:The default value of any field in a regular table is nil. It is easy to change this default value with metatables:

local key = {} -- unique key local mt = {__index = function (t) return t[key] end} function setDefault (t, d) t[key] = d setmetatable(t, mt) end tab = {x=10, y=20} print(tab.x, tab.z) --> 10 nil setDefault(tab, 0) print(tab.x, tab.z) --> 10 0

Both __index and __newindex are relevant only when the index does not exist in the table. The only way to catch all accesses to a table is to keep it empty. So, if we want to monitor all accesses to a table, we should create a proxy for the real table. This proxy is an empty table, with proper __index and __newindex metamethods that track all accesses and redirect them to the original table.

local index = {} -- create private index local mt = { -- create metatable __index = function (t, k) print("*access to element " .. tostring(k)) return t[index][k] -- access the original table end, __newindex = function (t, k, v) print("*update of element " .. tostring(k) .." to " .. tostring(v)) t[index][k] = v -- update original table end } function track (t) local proxy = {} proxy[index] = t setmetatable(proxy, mt) return proxy end t = {} t=track(t) t[2] = "hello" -- *update of element 2 to hello print(t[2]) -- *access to element 2

It is easy to adapt the concept of proxies to implement read-only tables. All we have to do is to raise an error whenever we track any attempt to update the table. For the __index metamethod, we can use a table — the original table itself — instead of a function, as we do not need to track queries; it is simpler and rather more efficient to redirect all queries to the original table. This use, however, demands a new metatable for each read-only proxy, with __index pointing to the original table:

function readOnly (t) local proxy = {} local mt = { -- create metatable __index = t, __newindex = function (t, k, v) error("attempt to update a read-only table", 2) end } setmetatable(proxy, mt) return proxy end days = readOnly{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"} print(days[1]) --> Sunday days[2] = "Noday" --> attempt to update a read-only table

“call”: called when Lua calls a value.

function function_event (func, ...) if type(func) == "function" then return func(...) -- primitive call else local h = metatable(func).__call if h then return h(func, ...) else error(···) end end end 2.9 – Environments

Besides metatables, objects of types thread, function, and userdata have another table associated with them, called their environment. Like metatables, environments are regular tables and multiple objects can share the same environment.

Threads are created sharing the environment of the creating thread.

Userdata and C functions are created sharing the environment of the creating C function.

Non-nested Lua functions (created by loadfile, loadstring or load) are created sharing the environment of the creating thread. Nested Lua functions are created sharing the environment of the creating Lua function.

例子:

-- Non-nested function, created using loadstring (this is an example with loadstring) local f1 = loadstring("return x + 10") -- 假设 x 是全局变量 x = 10 print(f1()) -- 这里会使用全局变量 x,输出 20 -- Nested function function outer() local x = 5 local function inner() return x + 10 -- 这里会访问到 outer 函数的局部变量 x end return inner() end print(outer()) -- 输出 15,内层函数访问的是外层函数的局部变量 x -- 去掉 local x = 5,内层函数访问的是全局变量 x,则输出 20

Environments associated with userdata have no meaning for Lua. It is only a convenience feature for programmers to associate a table to a userdata.

Environments associated with threads are called global environments. They are used as the default environment for threads and non-nested Lua functions created by the thread and can be directly accessed by C code (see §3.3).

补充:在 C 代码中使用 lua_getglobal 可访问全局环境获取全局变量的值

void lua_getglobal (lua_State *L, const char *name); Pushes onto the stack the value of the global name. It is defined as a macro: #define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, s)

The environment associated with a C function can be directly accessed by C code (see §3.3). It is used as the default environment for other C functions and userdata created by the function.

Environments associated with Lua functions are used to resolve all accesses to global variables within the function (see §2.3). They are used as the default environment for nested Lua functions created by the function.

You can change the environment of a Lua function or the running thread by calling setfenv. You can get the environment of a Lua function or the running thread by calling getfenv. To manipulate the environment of other objects (userdata, C functions, other threads) you must use the C API.

标签:

跟着Lua5.1官方参考文档学习Lua(4)由讯客互联软件开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“跟着Lua5.1官方参考文档学习Lua(4)