Lua笔记(14)—— generic for

Generic for的语法:

for <var-list> in <exp-list> do
    <body>
end

通常情况,<exp-list>只包含一个元素:a call to an iterator factory。而<var-list>大多数也只含有一个变量,第一个变量称之为“控制变量”,当它变为nil时,循环退出。

Generic for首先计算in后的表达式,<exp-list>最终生成3个值:iterator functioninvariant stateinitial value for the control variable。同multiple assignment一样,只有最后一个(或唯一一个)表达式可以生成多个值。

初始化以后,generic for调用iterator function,传入invariant stateinitial value for the control variable作为参数。从generic for出发点来看,invariant state没什么意义,generic for只在初始化时候,才把invariant state传入iterator function。(请参考:How to understand “invariant state” in generic for?

实际上:

for var_1, ..., var_n in <explist> do <block> end

相当于:

do
    local _f, _s, _var = <explist>
    while true do
        local var_1, ... , var_n = _f(_s, _var)
        _var = var_1
        if _var == nil then break end
            <block>
    end
end

举个例子,iterator functionfinvariant statesinitial value for the control variablea0。则control variable的值则会依次为:a1 = f(s, a0)a2 = f(s, a1),直到为nil。

Lua笔记(13)——module简介

Lua用户的观点来看,module就是一段可以通过require加载的代码(CLua),并且生成和返回一个tableLua中所有的标准库都是module

通过require命令加载module的步骤:
(1)查看module是否已经在package.loaded table中了,如果已经在了,就返回table中的值;否则转向步骤(2);
(2)查找以module命名的Lua文件。找到就使用loadfile加载,会得到一个称之为“loader”的函数。否则转向步骤(3);
(3)查找以module命名的C库文件。找到就使用package.loadlib加载,寻找luaopen_modname函数,也就是“loader”的函数。

有了loader以后,require会调用loader函数,并传入模块名和得到loader的文件名作为参数(很多module会忽略参数)。loader的返回值会被存入package.loaded table中,如果没有返回值,require表现会像loader返回了true,这样可以防止多次加载同一module

有时,为了避免module名冲突,需要重命名module(比如,加载同一module的不同版本)。Lua module没有把名字固定在内部,所以简单地重命名Lua文件就好了。但是对binary文件,由于无法改luaopen_*函数,所以需要一个trick:当module名有“-”时,require期待的open函数是使用”-“以后的名字。举个例子,如果一个module名字是v1-mod,则require期待的open函数就是luaopen_mod。所以只要把module前面加上版本和”-“以进行区分就可以了。

require查找modulepath是一系列用“;”分开的template,例如:

 /usr/local/share/lua/5.3/?.lua;/usr/local/share/lua/5.3/?/init.lua;/usr/local/lib/lua/5.3/?.lua;/usr/local/lib/lua/5.3/?/init.lua;./?.lua;./?/init.lua

对每个templaterequiremodule名字代替?,看是否存在这个文件。找到就返回,没有就尝试下一个template

require查找Lua文件的pathpackage.path变量里的值。这个值是这样得到的:
1)首先看是否定义环境变量LUA_PATH_5_2(取决于具体的Lua版本,这里只是个例子),如果有,则把这个值赋给package.path,没有转向2);
2)如果定义了环境变量LUA_PATH,则把这个值赋给package.path,没有转向3);
3)使用预定义的default path的值赋给package.path)。
需要注意的是,当使用环境变量定义的值时,如果有“;;”,则要把它替换成default path。举例来说,如果LUA_PATH_5_2的值是“mydir/?.lua;;”,则最后得到package.path应该是mydir/?.lua后面跟上default path
查找C库是package.cpath变量里的值。类似地,也有LUA_CPATH_5_2(同样取决于具体的`Lua`版本)和LUA_CPATH

Lua笔记(12)——注释

Lua中单行注释是以“--”开头,多行注释以“--[[”开头,以“]]”结尾。举例如下:

-- your code goes here
--[[
print("Hello")
--]]

上面以“--[[”开头,“--]]”结尾注释代码是一个小技巧。一旦想使这段被注释的代码生效,只要把“--[[”改成“---[[”即可。这样就变成了两个单行注释:

-- your code goes here
---[[
print("Hello")
--]]

运行如下:

Hello

Lua笔记(11)——closure

Lua中,closure就是一个函数加上其它能够让这个函数正确访问non-local变量(也称之为upvalue)的一切(Simply put, a closure is a function plus all it needs to access non-local variables correctly.)。参看下面程序:

function newCounter()
    local i = 0
    return function()
        i = i + 1
        return i
        end
end

c1 = newCounter()
print(c1())
print(c1())

c2 = newCounter()
print(c2())

newCounter()被称作factory,它用来创建closure functionclosure function所访问的变量i。执行代码如下:

1
2
1

可以看到,每次调用newCounter(),都会返回一个新的closure,而这些closure都会访问自己的i变量。

Lua笔记(10)——变参函数

Lua中把...称为变参表达式(vararg expression)。参看下面程序:

function fwrite(fmt, ...)
    io.write(string.format(fmt, ...))
end

fwrite("%s\n", "Hello world!")

要注意,...作为函数参数时,要作为唯一一个或者最后一个。

{...}会返回一个包含所有参数的数组(一个table)。参看下面程序:

function add(...)
    local t = {...}
    local s = 0
    for i = 1, #t do
        s = s + t[i]
    end
    print(s)
end

add(1, 2, 3, 4)

执行结果:

10

Lua笔记(9)——函数传参和返回值

Lua中,调用函数要加上括号(即使没有参数也不例外)。只有以下情况可以不加:函数只有一个参数,且参数是字符串或是table constructor

> print "Hello world!"
Hello world!
> print {x=10}
table: 0x1d81810

传给函数多余的实参(argument)会被丢弃,多余的形参(parameter)会赋值nil

> function f(a, b) print(a, b) end
> f(1)
1       nil
> f(1, 2)
1       2
> f(1, 2, 3)
1       2

根据实际情况,Lua会自动调整函数返回值的数量:
(1)当调用函数作为一条statement时,会丢弃所有的返回值;
(2)当调用函数作为一个表达式时,只会保留第一个返回值;
(3)只有当调用函数作为一系列表达式(a list of expressions)中的最后一个(或是唯一一个)时,才会得到所有的返回值。所谓的一系列表达式(a list of expressions)有四种情况:多重赋值(multiple assignments),函数调用传参(arguments to function calls),构建tabletable constructor)和return语句。

针对(1),(2),请看下面程序(test.lua):

function f()
        return 1, 2
end

f()

a, b = f(), 3
print(a, b)

执行结果如下:

[root@localhost ~]# ./test.lua
1       3

针对(3),请看下面程序(test.lua):

function f()
        return 1, 2
end

function g(a, b)
        print(a, b)
end

function h()
        return f()
end

a, b = f()
print(a, b)

g(f())

t = {f()}
print(t[1], t[2])

print(h())

执行结果如下:

[root@localhost ~]# ./test.lua
1       2
1       2
1       2
1       2

注意:在调用的函数外面再加上一层括号,可以强制函数只返回一个结果:

function f()
        return 1, 2
end

print(f())
print((f()))

执行结果:

[root@localhost ~]# ./test.lua
1       2
1

Lua笔记(8)——C API对table的操作

先看下面的C程序(hello.c):

#include <stdio.h>
#include <stdlib.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

int main(void)
{   
    lua_State *L = luaL_newstate();

    luaL_openlibs(L);

    lua_getglobal(L, "io");
    lua_getfield(L, -1, "write");
    lua_pushstring(L, "Hello\n");

    lua_pcall(L, 1, 0, 0);
    return 0;
}

lua_getglobal函数定义如下:

void lua_getglobal (lua_State *L, const char *name);
Pushes onto the stack the value of the global name.

是把全局变量的值push到堆栈里。在这个程序中,即是把io这个table的值存到堆栈中。

lua_getfield函数定义如下:

void lua_getfield (lua_State *L, int index, const char *k);
Pushes onto the stack the value t[k], where t is the value at the given index. As in Lua, this function may trigger a metamethod for the "index" event

也即把堆栈中指定索引(indextable中的keyk的值push到堆栈。在程序中,即把io.write函数存到堆栈中。

接下来lua_pushstring是把io.write函数的参数push到堆栈,而lua_pcall即执行io.write函数。

执行结果:

[root@Fedora test]# ./hello
Hello

Lua笔记(7)——luaL_loadfile和luaL_dofile的区别

luaL_loadfile()会加载和编译Lua脚本,但不会运行。而luaL_dofile不仅运行编译后的脚本,运行结束后还会把脚本pop出栈。看下面这个例子:

一个简单的脚本(test.lua):

print "Hello World!"

首先看调用luaL_loadfile()的程序:

#include <stdio.h>
#include <stdlib.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

static void stackDump(lua_State *L)
{
    int i = 0;
    int type = 0;
    int top = lua_gettop(L);

    printf("There is %d elements in stack: ", top);
    for (i = 1; i <= top; i++)
    {
        type = lua_type(L, i);
        switch (type)
        {
            case LUA_TSTRING:
            {
                printf("'%s'", lua_tostring(L, i));
                break;
            }
            case LUA_TBOOLEAN:
            {
                printf(lua_toboolean(L, i) ? "true" : "false");
                break;
            }
            case LUA_TNUMBER:
            {
                printf("%g", lua_tonumber(L, i));
                break;
            }
            default:
            {
                printf("Element type is %s", lua_typename(L, type));
                break;
            }
        }
        printf(" ");
    }
    printf("\n");
    return;
}

static void bail(lua_State *L)
{
    fprintf(stderr, "\nFATAL ERROR:%s\n\n", lua_tostring(L, -1));
    exit(1);
}
int main(void)
{   
    lua_State *L = luaL_newstate();

    luaL_openlibs(L);

    if (luaL_loadfile(L, "test.lua"))
    {
        bail(L);
    }

    stackDump(L);

    lua_close(L);
    return 0;
}

执行结果:

[root@Fedora test]# ./a
There is 1 elements in stack: Element type is function

可以看到,并没有打印“Hello World!”,而且栈里还有一个类型为function的元素。

接下来看调用luaL_dofile()的程序:

#include <stdio.h>
#include <stdlib.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

static void stackDump(lua_State *L)
{
    int i = 0;
    int type = 0;
    int top = lua_gettop(L);

    printf("There is %d elements in stack: ", top);
    for (i = 1; i <= top; i++)
    {
        type = lua_type(L, i);
        switch (type)
        {
            case LUA_TSTRING:
            {
                printf("'%s'", lua_tostring(L, i));
                break;
            }
            case LUA_TBOOLEAN:
            {
                printf(lua_toboolean(L, i) ? "true" : "false");
                break;
            }
            case LUA_TNUMBER:
            {
                printf("%g", lua_tonumber(L, i));
                break;
            }
            default:
            {
                printf("Element type is %s", lua_typename(L, type));
                break;
            }
        }
        printf(" ");
    }
    printf("\n");
    return;
}

static void bail(lua_State *L)
{
        fprintf(stderr, "\nFATAL ERROR:%s\n\n", lua_tostring(L, -1));
        exit(1);
}
int main(void)
{
    lua_State *L = luaL_newstate();

    luaL_openlibs(L);

    if (luaL_dofile(L, "test.lua"))
    {
        bail(L);
    }

    stackDump(L);

    lua_close(L);
    return 0;
}

执行结果:

[root@Fedora test]# ./a
Hello World!
There is 0 elements in stack:

可以看到,不仅打印了“Hello World!”,而且栈也变成了空。

参考资料:
lual_dofile(); wont load script with C++ and Lua

Lua笔记(6)——栈(stack)

C程序和Lua库之间通过栈(stack)来进行数据交换,并且栈中的每个槽位(slot)都能存放任意的Lua数据类型值。栈如下图所示:

                            |________|    <--  (-1)
                            |________|    <--  (-2)
                            |________|
                            | ...... |
                            |________|    <--  (2)
                            |________|    <--  (1)

栈底以1为起始索引,而栈顶则以-1作为起始索引。lua_gettop()函数可以返回当前栈中的元素个数,同时也是栈顶元素的索引值。如果是空栈,则 lua_gettop()返回0

Lua C API的核心就集中在对栈的操作上:当想要运行某个Lua脚本时,需要调用luaL_dofile()函数。而想执行Lua脚本的某个函数时,则首先要把函数push进栈(lua_getglobal()),如果函数需要参数,则参数也要相应地进栈(lua_pushXX())。接下来执行函数(比如lua_pcall()函数)。当函数退出时,返回值同样push进栈,C程序就可以使用lua_toXX()函数获得结果。请看下面这个例子:

一个简单的Lua脚本(test.Lua):

function add (a, b)
    return (a+b)
end

C程序如下:

#include <stdio.h>
#include <stdlib.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

static void bail(lua_State *L)
{
    fprintf(stderr, "\nFATAL ERROR:%s\n\n", lua_tostring(L, -1));
    exit(1);
}
int main(void)
{   
    lua_State *L = luaL_newstate();

    luaL_openlibs(L);

    if (luaL_dofile(L, "test.lua"))
    {
        bail(L);
    }

    lua_getglobal(L, "add");


    lua_pushnumber(L, 1);
    lua_pushnumber(L, 2);

    if (lua_pcall(L, 2, 1, 0))
    {
        bail(L);
    }

    printf("%g\n", lua_tonumber(L, -1));

    lua_close(L);
    return 0;
}

执行结果如下:

3

Lua笔记(5)——C API头文件简介

搭建Lua环境时,安装程序会把以下5个头文件拷贝到指定目录(默认为/usr/local/include/):

-rw-r--r--.  1 root root 14825 Jun  3 09:03 lua.h
-rw-r--r--.  1 root root 20581 May 20 13:39 luaconf.h
-rw-r--r--.  1 root root  8433 Oct 29  2014 lauxlib.h
-rw-r--r--.  1 root root  1173 Feb  6  2014 lualib.h
-rw-r--r--.  1 root root   191 Dec 22  2004 lua.hpp

先从最简单的lua.hpp来看:

// lua.hpp
// Lua header files for C++
// <<extern "C">> not supplied automatically because Lua also compiles as C++

extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

可以看到lua.hpp只是包含了其它的几个头文件,供C++程序使用。

lua.h定义了Lua提供的最基本的功能:创建Lua环境,调用Lua函数,在Lua环境中读写全局变量等。lua.h定义的所有成员(包括函数,宏等)都是用lua_LUA_作为前缀。

lauxlib.h定义了Lua辅助库(auxiliary library)提供的的函数,这个文件里定义的成员都是以luaL_作为前缀。Lua辅助库在lua.h定义的基础API之上,又封装了一层API,以提供更好的通用性。Lua辅助库并不会访问Lua的内部,所有操作都是通过lua.h的基础API去操作。

luaconf.h会定义Lua环境的配置信息。

lualib.h定义了Lua语言标准库的名字,以及打开这些库的函数。