在说语法分析之前,先说一下词法分析。
因为语法分析时会调用词法分析。
词法分析相关的文件为 llex.h,llex.c。
先来看一下词法分析的头文件。
RESERVED 保留字枚举。
Token 结构体,词法分析就是把输入源分析成一个个的 token。
这个词比较常见,不再翻译成中文,因为我也不知道它的准确的中文叫什么。
LexState 结构体,词法分析状态机,记录当前词法分析的状态以及一些相关的环境。
下面的是几个词分析的方法,注意 lua 中的命名约定。
lua*_ 开头的都是 lua 内部使用的 API。其中 * 表示不同的模块,比如这里的 luaX_ 表示的就是词法分析相关的接口。
而以 lua_ 开头的 API 则是对外的,比如 lua.h 中的那些。
下面来看下 llex.c 源代码文件。
next(LS) 宏从输入缓冲区中取得下一个字符, zgec 在之前的 ZIO 缓冲区中有介绍。
token2string 数组和 RESERVED 枚举一一对应。
注意它的注释,如果你要调整 RESERVED 枚举中的顺序,需要相应的也修改这里数组中元素的顺序。
这就是 RESERVED 上的注释的含义。
这个字符串数组 "while" 之前的元素都是保留字。
void luaX_init (lua_State *L) { int i; for (i=0; i<NUM_RESERVED; i++) { TString *ts = luaS_new(L, token2string[i]); ts->marked = (unsigned char)(RESERVEDMARK+i); /* reserved word */ } }
词法分析的初始化,驻留保留的字符串。
设置 marked 标记以避免垃圾回收。
TString 数据结构和 luaS_new 后面再进行分析。
luaX_checklimit 通过名字可以看出,判断是否超限。
luaX_syntaxerror 语法错误。
luaX_error 出错了。
luaX_token2str 根据 token 取得相应的保留字符串。
luaX_invalidchar 非法字符。
inclinenumber 到下一行,如果行数过多,则出错。
void luaX_setinput (lua_State *L, LexState *LS, ZIO *z, TString *source) { LS->L = L; LS->lookahead.token = TK_EOS; /* no look-ahead token */ LS->z = z; LS->fs = NULL; LS->linenumber = 1; LS->lastline = 1; LS->source = source; next(LS); /* read first char */ if (LS->current == ‘#‘) { do { /* skip first line */ next(LS); } while (LS->current != ‘\n‘ && LS->current != EOZ); } }
设置输入,包括 Lua 状态机,输入缓冲区,输入源名字,及其它的一些初始化。
程序最后判断首个字符是否为 ‘#‘,如果是,则跳过首行。
这是为了 linux 环境中 lua 程序做为可执行脚本时,跳过 #!/usr/bin/lua 这一行。
int luaX_lex (LexState *LS, SemInfo *seminfo);
这个方法才是词法分析程序里的主角。
每次调用这个方法都会返回一个 token。由语法分析 parser 来调用。
程序的主体是一个死循环里的一个 switch case 分支,以当前的字符为分析判断条件。
下面简单的看下它的各分支:
case ‘ ‘: case ‘\t‘: case ‘\r‘: /* `\r‘ to avoid problems with DOS */ next(LS); continue;
// 跳过空格,制表符,回车符。
case ‘\n‘: inclinenumber(LS); continue;
// 跳过换行,增加行号。
case ‘$‘: luaX_error(LS, "unexpected `$‘ (pragmas are no longer supported)", ‘$‘); break;
// 非法字符,报错。
case ‘-‘: next(LS); if (LS->current != ‘-‘) return ‘-‘; do { next(LS); } while (LS->current != ‘\n‘ && LS->current != EOZ); continue;
// 中划线,如果后接其它的字符,则返回它自己,也就是减号。
// 否则,就是注释,跳过注释行。
case ‘[‘: next(LS); if (LS->current != ‘[‘) return ‘[‘; else { read_long_string(LS, seminfo); return TK_STRING; }
// 单中括号,如果后面不是另一个单中括号,返回它。
// 否则,返回一个长字符串。
case ‘=‘: next(LS); if (LS->current != ‘=‘) return ‘=‘; else { next(LS); return TK_EQ; }
// 等号,如果后面不是等号,返回。
// 否则,返回相等。
case ‘<‘: next(LS); if (LS->current != ‘=‘) return ‘<‘; else { next(LS); return TK_LE; }
// 小于号,根据后面的字符决定它是小于还是小于等于。
case ‘>‘: next(LS); if (LS->current != ‘=‘) return ‘>‘; else { next(LS); return TK_GE; }
// 大于号,根据后面的字符决定它是大于还是大于等于。
case ‘~‘: next(LS); if (LS->current != ‘=‘) return ‘~‘; else { next(LS); return TK_NE; }
// 不等号,根据后面的字符决定它是非,或者是不等。
case ‘"‘: case ‘\‘‘: read_string(LS, LS->current, seminfo); return TK_STRING;
// 单引号或者双引号,返回字符串。
case ‘.‘: next(LS); if (LS->current == ‘.‘) { next(LS); if (LS->current == ‘.‘) { next(LS); return TK_DOTS; /* ... */ } else return TK_CONCAT; /* .. */ } else if (!isdigit(LS->current)) return ‘.‘; else { read_number(LS, 1, seminfo); return TK_NUMBER; }
// 点,根据点的多少返回相应的操作,如果是单个点后面接数字,返回数字。
case ‘0‘: case ‘1‘: case ‘2‘: case ‘3‘: case ‘4‘: case ‘5‘: case ‘6‘: case ‘7‘: case ‘8‘: case ‘9‘: read_number(LS, 0, seminfo); return TK_NUMBER;
// 数字
case EOZ: return TK_EOS;
// 结束
case ‘_‘: goto tname;
// 下划线,跳到 tname 标号。
default: if (!isalpha(LS->current)) { int c = LS->current; if (iscntrl(c)) luaX_invalidchar(LS, c); next(LS); return c; }
// 如果不是字母,判断是否为控件字符,如果是,返回非法字符。
// 否则返回字。
tname: { /* identifier or reserved word */ TString *ts = luaS_new(LS->L, readname(LS)); if (ts->marked >= RESERVEDMARK) /* reserved word? */ return ts->marked-RESERVEDMARK+FIRST_RESERVED; seminfo->ts = ts; return TK_NAME; }
// 根据是否是保留字符串返回保留字符串,或者返回一个标志符。
static const char *readname (LexState *LS);
读取标志符。
static void read_number (LexState *LS, int comma, SemInfo *seminfo);
读取数字
static void read_long_string (LexState *LS, SemInfo *seminfo);
读取长字符串
static void read_string (LexState *LS, int del, SemInfo *seminfo);
读取字符串
后记
本来想写得很详细。
可是写着写着,觉得很没有必要。
或者,我应该尝试着换一种方式去写。
我得想想,接下来到底该如何?
----------------------------------------
到目前为止的问题:
> TString 数据结构和 luaS_new
> 函数原型优化 luaU_optchunk
> 打印函数原型 luaU_printchunk
> dump 函数原型 luaU_dumpchunk
> 语法分析 luaY_parser
----------------------------------------