(接上篇)
--------------------------------------
7 调试接口
--------------------------------------
Lua 没有内置的调试功能。而是借助于函数和钩子(hook)提供了一个特殊接口,可以用来构建不同种类的调试器,分析器(profile)和一些其它的需要解释器内部信息的工具。这个接口在 luadebug.h 文件中声明。
-------------------
7.1 栈和函数信息
-------------------
获得解释器栈信息的主要函数是:
int lua_getstack (lua_State *L, int level, lua_Debug *ar);
它用给定层级上正在执行的一个活动记录标识填充部分 lua_Debug 结构。0 级是正在运行的函数,层级 n+1 是调用层级 n 的函数。通常,lua_getstack 返回 1 ;当以一个高于栈的深度的层次来调用时,它返回 0 。
结构 lua_Debug 用来携带一个活动函数的不同信息片段:
typedef struct lua_Debug {
const char *event; /* "call", "return" */
int currentline; /* (l) */
const char *name; /* (n) */
const char *namewhat; /* (n) global, tag method, local, field */
int nups; /* (u) number of upvalues */
int linedefined; /* (S) */
const char *what; /* (S) "Lua" function, "C" function, Lua "main" */
const char *source; /* (S) */
char short_src[LUA_IDSIZE]; /* (S) */
/* private part */
...
} lua_Debug;
lua_getstack 只填充这个结构的私有部分,为后来使用。用有用信息填充 lua_Debug 其它的字段,调用
int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar);
这个函数出错时返回 0 (例如,一个 what 的无效选项)。字符串 what 中的每个字符选择一些被填充的 ar 的字段,如上面 lua_Debug 的定义中被一个括号中的字母标识出来的: `S' 填充字段 source, linedefined, 和 what; `l' 填充字段 currentline, 等。而且, `f' 压栈在给定层级上正在执行的函数。
为获得一个不活动函数的信息(即,不在栈里),可以把函数压栈,并字符 > 放在字符串 what 的起头。例如,为了获取一个函数 f 在哪里定义,你可以
lua_Debug ar;
lua_getglobal(L, "f");
lua_getinfo(L, ">S", &ar);
printf("%d\n", ar.linedefined);
lua_Debug 的字段有下列意思
source
如果函数被定义在字符串中,source 就是那个字符串;如果函数定义在一个文件中,source 由 @ 开始并后跟文件名。
short_src
一个 ``printable'' 版本的 source,用在错误信息中。
linedefined
函数定义开始处的等号。
what
字符串 "Lua" 如果这是一个 Lua 函数, "C" 如果这是一个 C 函数, 或者 "main" 如果这是一个块的主要部分。
currentline
给定函数正在执行的当前行。如果没有行信息, currentline 被设置为 -1.
name
一个给定函数的合理的名字。因为函数在 Lua 中第一类值,它们没有一个固定的名字:一些函数可能是很多全局变量的值,而其它的可能只保存在一个表的字段中。lua_getinfo 函数检查给定的函数是标签方法或者是一个全局变量的名字。如果给定函数是一个标签方法,那么 name 指向相应的事件名。如果给定的函数是一个全局变量的值,那么 name 指向变量名字。如果给定函数既不是一个标签方法也不是一个全局变量,name 被设置为 NULL。
namewhat
解释之前的字段。如果函数是一个全局变量,namewhat 是 "global";如果函数是一个标签方法,namewhat 是 "tag-method";否则,namewhat 是 "" (空字符串)。
nups
一个函数的 upvalue 的个数。
-------------------
7.2 局部变量的操作
-------------------
luadebug.h 使用索引来操纵局部变量:第一个参数或者局部变量的索引为 1,以此类推,直到最后一个活动的局部变量。
下面的函数可以用来操纵给定活动记录的局部变量:
const char *lua_getlocal (lua_State *L, const lua_Debug *ar, int n);
const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n);
参数 ar 必须是一个有效的活动记录,被之前的 lua_getstack 调用填充或者作为一个 hook 参数给定(参见 7.3 节)。函数 lua_getlocal 获得一个局部变量(n)的索引,把它的值压栈,并返回它的名字。对于 lua_setlocal,你把新值压栈,这个函数把新值赋给那个变量并返回它的名字。两个函数失败时均返回 NULL;如果索引大于活动局部变量的时候这处情况就会发生。
作为一个例子,下面的函数列出一个在栈的给定层次上的所有的局部变量的名字:
int listvars (lua_State *L, int level) {
lua_Debug ar;
int i = 1;
const char *name;
if (lua_getstack(L, level, &ar) == 0)
return 0; /* failure: no such level in the stack */
while ((name = lua_getlocal(L, &ar, i++)) != NULL) {
printf("%s\n", name);
lua_pop(L, 1); /* remove variable value */
}
return 1;
}
-------------------
7.3 钩子 (Hooks)
-------------------
Lua 解释器为调试提供了两个钩子(Hook),一个 call hook, 一个 line hook。它两个有相同的类型:
typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);
你可以使用下面的函数来设置它们:
lua_Hook lua_setcallhook (lua_State *L, lua_Hook func);
lua_Hook lua_setlinehook (lua_State *L, lua_Hook func);
钩子是无效的如果它的值为 NULL ,NULL 也是两个钩子的默认值。函数 lua_setcallhook 和 lua_setlinehook 设置他们相应的钩子并返回它们之前的值。
当解释器进入或者离开一个函数时,call hook 会被调用。ar 的 event 字段的字符串是 "call" 或者 "return"。这个 ar 之后可以被用在调用 lua_getinfo, lua_getlocal, 和 lua_setlocal 去获得更多的关于函数和操纵局部变量的信息。
当解释器每次改变它执行的代码行号时会调用 line hook。ar 的字符 event 的字符串是 "line" , 并且 currentline 字段有行号。再次强调,你可以使用这个 ar 在其它的调用 API 的调用中。
当 Lua 在执行一个钩子时,它禁用了其它的钩子的调用。因此,如果一个钩子调用 Lua 去执行一个函数或者一个块,这个执行将不会再有其它的钩子调用。
-------------------
7.3 自反的调用接口
-------------------
库 ldblib 为 Lua 程序提供调试接口的功能。如果你想使用这个库,你的宿主应用程序必须打开它,通过调用 lua_dblibopen。
当使用这个库时你应该万分小心。这里提供的函数应该仅仅用于调试或者类似的任务(例如,性能分析)。请抵制把他们作为常用工具的诱惑。它们很慢并且违反了一些语言的安全性。(例如,局部变量的私密性)。作为一个一般性规则,如果你的程序不需要这个库,不要打开它。
getinfo (function, [what])
这个函数返回一个带有函数信息的表。你可以直接给出函数,或者你可以给出一个数字作为函数的值,它意味着函数正在栈上执行的函数的层次:Level 0 是当前的函数(getinfo 它自己);Level 1 是调用 getinfo 的函数;以此类推。如果函数的值比活动函数的个数多,那么 getinfo 返回 nil。
返回的表包含所有由 lua_getinfo 返回的字段,用一个字符串 what 来描述获取什么。what 的默认值是获得所有的可用信息。
例如,表达式 getinfo(1, "n").name 返回当前函数的名字,如果可以找到一个合理的名字,getinfo(print) 返回一个关于 print 函数的所有可用信息的表。
getlocal (level, local)
函数返回局部变量的名字和值,局部变量的 index 为 local ,它位于栈上的层级 level 的函数中。(第一个参数或者局部变量的 index 为 1,以此类推,直到最后一个活动的局部变量。)函数返回 nil 如果没有给定索引的局部变量,并发生一个错误当以一个范围之外的 level 调用时。(你可以调用 getinfo 去检查 level 是否有效。)
setlocal (level, local, value)
函数设置局部变量的值为 value,局部变量的 index 为 local ,它位于栈上的层级 level 的函数中。函数返回 nil 如果没有给定索引的局部变量,并发生一个错误当以一个范围之外的 level 调用时。
setcallhook (hook)
设置函数 hook 为调用钩子;这个钩子在每次解释器开始或者退出一个函数执行时将会被调用。调用钩子唯一的参数是事件名称("call" 或者 "return")。你可以以 level 2 调用 getinfo 去获得更多的正在被调用或正在返回的函数信息(level 0 是 getinfo 函数,level1 是钩子函数)。当无参调用时,这个函数关闭调用钩子。setcallhook 返回老的钩子。
setlinehook (hook)
设置函数 hook 为行钩子;这个钩子在每次解释器改变正在执行的代码的行时将会被调用。行钩子的唯一参数是解释器将要执行的行号。当无参调用时,这个函数关闭调用钩子。setlinehook 返回老的钩子。
--------------------------------------
8 独立执行的 Lua
--------------------------------------
尽管 Lua 被设计作为一个扩展语言,被嵌入到一个 C 宿主程序,它也经常被做为一个独立的语言使用。一个作为独立语言使用的 Lua 解释器,叫做简单的 lua,在标准发布版中被提供。这个程序可以被以下的参数以任意顺序调用:
-sNUM
设置栈的大小为 NUM(如果存在,这必须是第一个选项);
-
把标准输入作为文件执行。
-c
调用 lua_close 在执行完所有参数之后;
-e \rmstat
执行字符串 stat;
-f filename
执行文件 filename 所有剩余的参数在表 arg 中;
-i
进入交互模式,显示一个提示符;
-q
进入交互模式,不显示提示符。
-v
打印版本信息;
var=value
设置全局变量 var 为字符串 "value";
filename
执行文件 filename。
当无参调用时,lua 的行为和 lua -v -i 一样当标准输入是一个终端时,否则像 lua - 。
所有的参数按顺序处理,除了 -c 。例如,一个调用
$ lua -i a=test prog.lua
将首先和用户交互直到一个 EOF 出现在标准输入中,然后设置 a 为 "test",最后执行文件 prog.lua。(这里,$ 是一个 shell 提示符。你的提示符可能与此不同。)
当选项 -f filename 被使用时,所有命令行中剩余的参数放进一个叫做 arg 的表中被传给 Lua 程序 filename 。在这个表中,字段 n 获得最后的参数的索引,字段 0 获得 "filename"。例如,在调用
$ lua a.lua -f b.lua t1 t3
解释器首先执行文件 a.lua,然后新建一个表
arg = {"t1", "t3"; n = 2, [0] = "b.lua"}
最后,执行文件 b.lua。独立的解释器也提示了一个 getargs 函数可以用来存取所有的命令行参数。例如,如果你这样调用 Lua
$ lua -c a b
那么一个 getargs 的调用在 a 或者 b 中将返回表
{[0] = "lua", [1] = "-c", [2] = "a", [3] = "b", n = 3}
在交互模式中,一个多行的句子可以在行尾以反斜杠结束。如果全局变量 _PROMPT 被定义为一个字符串,那么它的值将被用做提示符。所以,提示符可以直接在命令行中修改:
$ lua _PROMPT='myprompt> ' -i
或者在任意的 Lua 程序中给 _PROMPT 赋值。
在 Unix 系统中,Lua 脚本可以被设置为可执行的程序,使用 chmod +x 和 #! 格式,像在 #!/usr/local/bin/lua, 或者 #!/usr/local/bin/lua -f 去获得其它的参数。
--------------------------------------
鸣谢
--------------------------------------
作者要感谢 CENPES/PETROBROBAS 和 TeCGraf 一起,使用该系统的早期版本,并提出宝贵意见。作者还要感谢 Carlos Henrique Levy,为这个语言起了个名字。Lua 在葡萄牙语里是月亮的意思。
--------------------------------------
其它(略)
--------------------------------------
和之前版本的不兼容及索引。
--------------------------------------
Lua 完整的语法
--------------------------------------
chunk ::= {stat [`;']}
block ::= chunk
stat ::= varlist1 `=' explist1
| functioncall
| do block end
| while exp1 do block end
| repeat block until exp1
| if exp1 then block {elseif exp1 then block} [else block] end
| return [explist1]
| break
| for `name' `=' exp1 `,' exp1 [`,' exp1] do block end
| for `name' `,' `name' in exp1 do block end
| function funcname `(' [parlist1] `)' block end
| local declist [init]
funcname ::= `name' | `name' `.' `name' | `name' `:' `name'
varlist1 ::= var {`,' var}
var ::= `name' | varorfunc `[' exp1 `]' | varorfunc `.' `name'
varorfunc ::= var | functioncall
declist ::= `name' {`,' `name'}
init ::= `=' explist1
explist1 ::= {exp1 `,'} exp
exp1 ::= exp
exp ::= nil | `number' | `literal' | var | function | upvalue
| functioncall | tableconstructor | `(' exp `)' | exp binop exp | unop exp
functioncall ::= varorfunc args | varorfunc `:' `name' args
args ::= `(' [explist1] `)' | tableconstructor | `literal'
function ::= function `(' [parlist1] `)' block end
parlist1 ::= `...' | `name' {`,' `name'} [`,' `...']
upvalue ::= `%' `name'
tableconstructor ::= `{' fieldlist `}' fieldlist ::= lfieldlist | ffieldlist | lfieldlist `;' ffieldlist | ffieldlist `;' lfieldlist lfieldlist ::= [lfieldlist1] ffieldlist ::= [ffieldlist1] lfieldlist1 ::= exp {`,' exp} [`,'] ffieldlist1 ::= ffield {`,' ffield} [`,'] ffield ::= `[' exp `]' `=' exp | `name' `=' exp
binop ::= `+' | `-' | `*' | `/' | `\^{ ' | `..'
| `<' | `<=' | `>' | `>=' | `==' | `\ { '=}
| and | or}
unop ::= `-' | not