怀念夏天 发表于 2025-2-18 09:22:41

跟着 Lua 5.1 官方参考文档学习 Lua (4)

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 (: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: :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: :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)
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) or metatable(op2)
   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)       local mm2 = metatable(op2)       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 = 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 = true
    end

    for k in pairs(b) do
      res = true
    end
    return res
end

function Set.intersection (a, b)
    local res = Set.new{}
    for k in pairs(a) do
      res = b
    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 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.
   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         -- 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 = 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 = 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 end}
function setDefault (t, d)
    t = 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 -- access the original table
    end,

    __newindex = function (t, k, v)
      print("*update of element " .. tostring(k) .." to " .. tostring(v))
      t = v -- update original table
    end
}

function track (t)
    local proxy = {}
    proxy = t
    setmetatable(proxy, mt)
    return proxy
end

t = {}
t=track(t)
t = "hello" -- *update of element 2 to hello
print(t) -- *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) --> Sunday
days = "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.

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 跟着 Lua 5.1 官方参考文档学习 Lua (4)