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

文章目录

    • [2.7 -- Error Handling](#2.7 – Error Handling)
    • [2.8 -- Metatables](#2.8 – Metatables)
    • [2.9 -- Environments](#2.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 函数

c 复制代码
#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 函数

c 复制代码
#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,自定义错误处理函数

c 复制代码
#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

lua 复制代码
     metatable(obj)[event]

This should be read as

lua 复制代码
     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.

    lua 复制代码
         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

    lua 复制代码
         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.

    lua 复制代码
         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.

    lua 复制代码
         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.

    lua 复制代码
         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.

    lua 复制代码
         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:

    lua 复制代码
         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.

    lua 复制代码
         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.

    lua 复制代码
         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:

lua 复制代码
print(getmetatable("hi")) --> table: 0x80772e0
print(getmetatable(10)) --> nil  

例子:元表的使用

lua 复制代码
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:

lua 复制代码
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].

    lua 复制代码
         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:

    lua 复制代码
    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.

    lua 复制代码
         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:

    lua 复制代码
    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.

    lua 复制代码
    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:

    lua 复制代码
    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.

    lua 复制代码
         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.

例子:

lua 复制代码
-- 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.

相关推荐
阿湯哥11 小时前
Lua脚本核心语法介绍
开发语言·junit·lua
王小义笔记21 小时前
Postman如何流畅使用DeepSeek
开发语言·测试工具·lua·postman·deepseek
程序猿多布2 天前
数学函数(C#、Lua 、Unity)
unity·c#·lua
程序猿多布2 天前
字符串操作总结(C# and Lua)
c#·lua
浅陌sss2 天前
Xlua中C#引用Lua变量,导致Lua侧的GC无法回收的原因及解决方法
c#·lua
张胤尘2 天前
Lua | 面试题每日一练 (1)
开发语言·后端·lua
a小胡哦3 天前
从入门到精通:Postman 实用指南
测试工具·lua·postman
张胤尘3 天前
Lua | 每日一练 (2)
开发语言·面试·lua
alenliu06214 天前
跟着 Lua 5.1 官方参考文档学习 Lua (1)
lua