Lua4.0 词法分析

在说语法分析之前,先说一下词法分析。

因为语法分析时会调用词法分析。

词法分析相关的文件为 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

----------------------------------------

时间: 2024-11-09 09:52:02

Lua4.0 词法分析的相关文章

Lua4.0 语法分析

Lua 最初使用的是 Yacc 生成的语法分析器,后来改为手写的递归下降语法分析器(Recursive descent parser).因为一般在语言发展的早期,语言的语法还没有稳定下来,变动可能会比较多,用工具可以快速的验证自己的想法.当语法稳定下来之后,一般都会采用手写的语法分析器.因为这样程序员是调试可控的,并且一般也能有更好的性能表现.递归下降的语法分析对编程语言的语法有要求.因 Lua 的语法比较简单,是 LL(1) 文法.所以,手工编写语法分析也就是情理之中的事了. 关于递归下降的语

Lua4.0 开篇

标题说是 4.0,其实这里分析的是 4.0.1.不过按照 Lua 的版本号规则,小号只做 bug fix .所以,下面的所说的 4.0 指的就是 release 4.0.1(在不引起混淆的情况下). 4.0 发布于 2000 年 11 月,4.0.1 发布于 2002.7,我们看的上一个版本 2.4 则是发布于 1996 年 5 月,怎么说这个版本也是二十一世纪的了. 4.0 算是比较新的版本了,因为它有在线版的代码和文档.在线文档在 http://www.lua.org/manual/,其实从

Lua4.0 参考手册(一)1-3

说明:这个文档是 doc 目录里的 manual.html 文件.原文版权归原作者所有,这篇翻译只是作为学习之用.如果翻译有不当之处,请参考原文.-------------------以下是正文-------------------编程语言 Lua4.0 的参考手册--------------------------------------1 简介--------------------------------------Lua 是一个扩展编程语言,支持通用编程功能与数据描述功能.作为一个强大

Lua4.0 字符串相关

这节看一下字符串相关的: TString 数据结构如下所示,可以看到,TString 不单用于处理字符串,还可用于处理用户自定义数据. /* ** String headers for string table */ /* ** most `malloc' libraries allocate memory in blocks of 8 bytes. TSPACK ** tries to make sizeof(TString) a multiple of this granularity, t

Lua4.0 ZIO缓冲区

来看一下 ZIO 缓冲. 词法分析读一个一个的字符就是从它读的. 或者 umdump 时也是从它读字符(一个 char 字节)的. 缓冲区隔离了下层数据源的不同,对上层提供一致的读取接口. 相关的代码文件是 lzio.h 和 lzio.c . 先看一下数据结构: #ifndef ZBSIZE #define ZBSIZE  256             /* buffer size */ #endif struct zio {    size_t n;                     

Lua5.0 词法分析

语法分析器会调用词法分析器. 在语法分析之前,简单的看一下词法分析. 内存管理和 ZIO 输入在词法分析中会用到,因为它们相对比较孤立,不影响主流程的阅读. 上一个版本也看过它们了,这里就不再重复了. 词法分析最重要的函数就是 int luaX_lex (LexState *LS, SemInfo *seminfo); 如果你用其它的词法分析工具生成器,生成的词法分析器也会有个类似的函数. 这个函数主要就是从源代码中读出一个 token 返回给语法分析器. 在语法分析的 next 和 lookh

Lua4.0 代码

这个标题是 2014.11.13 号写的,今天总算是写入内容了. 离上次写代码分析时间有点长,都忘记自己之前是如何写的了. 不管这些历史包袱了,这次轻装上阵,想到哪就写到哪. 开始. 参照 4.0 的 INSTALL 文档内容,编译下.在 Linux 机器上比较简单,make 一下就好了. 在 Windows 上建工程的话,也算是比较容易.这部分之前有描述,这里就不再重复了. 简单的说一下这次的代码分析可能会写什么内容.(注意,这里用"可能"的原因的是,依目前的想法,最大程度上会这么写

Lua4.0 翻译小结

4.0 手册翻译完了,小结一下. 这一次从 2.4 跳到 4.0,手册绝大部分都得从头翻译.以前的 2.4 的只参考了一小部分,所以,翻译得进度比较慢.不过,好在现在已经翻译完了. 翻译过程中,有几节内容不太熟悉,翻译的可能不好.也有的是因为英语的意思没有太明白.不过,如果遇到不了解的,还是以英文为主. 另外,有一点需要解释一下.为什么一个完整的手册要分这么多篇?一个主要的原因是发博客时的字数限制.博客的字数限制是一万字.为了不破节,就自己这么分篇儿了.再者,觉得一篇太长的话, 每次看后面的内容

Lua4.0 参考手册(八)6.2-6.5

(接上篇)-------------------6.2 字符串处理-------------------这个库提供字符串处理的通用函数,如查找,提取子串和模式匹配.在 Lua 中索引一个字符串的时候,第一个字符的索引是 1(不像 C 中是 0).另外,索引可以为负数,负数被解释为逆向索引,从字符串的结尾开始.所以,最后一个字符位置是 -1,以此类推. strbyte (s [, i])返回 s 的第 i 个字符的内部数值码(例如:ASCII 码).如果没有 i,它被认为是 1 .i 可以为负.