Bison|4.3 词法分析器函数 yylex|V20240916

声明:本文梳理自 Bison 参考手册:https://www.gnu.org/software/bison/manual/bison.html;部分借鉴自通义千问 AI。

4.3 词法分析函数 yylex

词法分析器(lexical analyzer)函数 yylex 能够识别输入流中的 Token 并将它们返回给解析器。Bison 并不会自动创建这个函数;你需要编写它以便使 yyparse 可以调用它。这个函数有时也被称为词法扫描器(lexical scanner)。

在简单的程序中,yylex 通常定义在 Bison 语法文件的末尾。如果 yylex 定义在一个单独的源文件中,则需要确保 Token kind 的定义是可用的。要做到这一点,在运行 Bison 时使用 -d 选项,这样它会将这些定义写入独立的解析器头文件 name.tab.h 中,从而可以在需要时引用它。

4.3.1 yylex 函数的调用约定

yylex 函数的返回值必须是它刚刚找到的 Token 类型的正数数值代码;零或负数表示输入结束。

当语法规则中通过名称引用一个 Token kind 时,可以使用解析器实现文件中的 yytoken_kind_t 枚举类型,为 Token kind 定义对应的数值代码,此时 yylex 函数就可以通过这个名称来表明类型。详见 符号,终结符和非终结符。

当语法规则中通过字符字面量引用一个标记时,该字符的数值代码也是该标记类型的代码。因此yylex() 函数可以直接返回该字符代码,可能需要转换成无符号字符以避免符号扩展。不允许使用空字符(null character),因为它的代码是零,而零表示输入结束。

下面是一个展示这些事项的例子:

int
yylex (void)
{if (c == EOF)    /* 检测输入结束。*/
    return YYEOF;else if (c == '+' || c == '-')
    return c;      /* 假设'+'的标记类型就是'+'。*/else
    return INT;    /* 返回标记的类型。*/}

此接口设计的目的在于使得来自 lex 工具的输出可以不加修改地作为 yylex 的定义。

4.3.2 特殊的 Token

除了用户定义的 Token 外,Bison 还会生成一些特殊的标记,这些标记是 yylex 可能会用到的。

  • YYEOF:表示文件的结束,并告诉解析器之后没有更多的内容了
  • YYUNDEF:告诉解析器发现了一些词法错误,它将发出一条关于 “无效标记” 的错误信息,并进入错误恢复状态(请参阅错误恢复)。返回一个未知的标记类型会产生完全相同的行为。
  • YYerror:要求解析器在不发出错误信息的情况下进入错误恢复模式。这样,词法分析器可以生成关于无效输入的准确错误信息(这是解析器无法做到的),同时又可以从解析器的错误恢复特性中受益。

下面是一个示例代码:

int
yylex (void)
{switch (c)
    {case '0': case '1': case '2': case '3': case '4':
      case '5': case '6': case '7': case '8': case '9':return TOK_NUM;case EOF:
        return YYEOF;
      default:
        yyerror ("语法错误: 无效字符: %c", c);
        return YYerror;
    }
}

这段代码展示了如何处理字符输入,并且在遇到非预期字符时,使用 yyerror 函数报告错误,并返回 YYerror 来触发解析器的错误恢复机制。

4.3.3 通过字符串字面量查找 Token

如果语法使用字面字符串标记,yylex() 函数可以通过两种方式确定它们的标记类型代码:

  1. 如果定义了 Token 的名称作为字面字符串标记的别名,那么 yylex() 函数可以像使用其他标记一样使用这些符号名称。在这种情况下,语法文件中使用的字面字符串标记不会影响 yylex() 函数的行为。这是推荐的方法。
  2. yylex() 函数可以在 yytname 表中搜索多字符标记。这种方法是不推荐的。使用字符串别名的主要目的是为了生成好的错误信息,而不是描述关键字的拼写。此外,在运行时查找标记类型会带来一定的开销(虽然很小但是可以察觉)。yytname 表只有在声明了 %token-table 时才会生成。

综上所述,使用符号名称来代替实际的字符串字面量是一种更好的做法,因为它不仅简化了 yylex() 函数的设计,还避免了在运行时进行额外的查找操作所带来的性能损耗。

4.3.4 标记的语义值

在一个普通的(非重入的)解析器中,标记的语义值必须存储到全局变量 yylval 中。当你只使用单一的数据类型来表示语义值时,yylval具有那种类型。因此,如果类型是 int(默认类型),你可能会在 yylex 中这样写:

…
yylval = value;  /* 将值放到Bison栈中。 */
return INT;      /* 返回标记的类型。 */

当你使用多种数据类型时,yylval 的类型是一个由 %union 声明创建的联合体。在存储一个标记的值时,你必须使用联合体的正确成员。如果 %union 声明如下所示:

%union {
  int intval;
  double val;
  symrec *tptr;
}

那么在 yylex() 函数中的代码可能会是这样的:

…
yylval.intval = value; /* 将值放到Bison栈中。 */
return INT;            /* 返回标记的类型。 */

4.3.5 标记的文本位置

如果在语义组行为(action)中使用了 @n 特性(见跟踪位置)来跟踪标记和组合的文本位置,那么则必须在 yylex() 函数中提供这些信息。函数 yyparse() 预期在全局变量 yylloc 中找到刚刚解析的标记的文本位置。因此,yylex() 函数必须在这个变量中存储正确的数据。

默认情况下,yylloc 的值是一个结构体,你只需要初始化那些会被动作(action)用到的成员即可。这四个成员分别叫做 first_line(首行)、first_column(首列)、last_line(末行)和 last_column(末列)。请注意,使用这个特性会使解析器的速度明显变慢。

yylloc 的数据类型名为 YYLTYPE

4.3.6 纯解析器的调用约定

当你使用 Bison 声明 %define api.pure full 请求一个纯(可重入)解析器时,则全局通信变量 yylvalyylloc 不能被使用。在这样的解析器中,这两个全局变量会被作为参数传递给 yylex() 函数的指针所取代。你必须像下面这样声明它们,并通过这些指针将信息返回。

int
yylex (YYSTYPE *lvalp, YYLTYPE *llocp)
{
  …
  *lvalp = value;  /* 将值放入 Bison 栈中。*/
  return INT;      /* 返回标记的类型。*/
  …
}

如果语法文件没有使用 @ 构造来引用文本位置,则 YYLTYPE 类型将不会被定义。在这种情况下,省略第二个参数;yylex 将仅带有一个参数被调用。

如果你想向 yylex() 函数传递额外的参数,可以像使用 %parse-param 一样使用 %lex-param。为了同时向 yylexyyparse 传递额外的参数,可以使用 %param

指令 %lex-param {argument-declaration} ...:指定 {argument-declaration} 是额外的 yylex 参数声明。你可以传递一个或多个这样的声明,这相当于重复使用 %lex-param

指令 %param {argument-declaration} ...:指定 {argument-declaration} 是额外的 yylexyyparse 参数声明。这等同于 %lex-param {argument-declaration} ... %parse-param {argument-declaration} ...。你可以传递一个或多个声明,这相当于重复使用 %param

例如:

%lex-param   {scanner_mode *mode}
%parse-param {parser_mode *mode}
%param       {environment_type *env}

会导致以下的函数定义:

int yylex(scanner_mode *mode, environment_type *env);
int yyparse(parser_mode *mode, environment_type *env);

如果添加了 %define api.pure full

int yylex(YYSTYPE *lvalp, scanner_mode *mode, environment_type *env);
int yyparse(parser_mode *mode, environment_type *env);

最后,如果同时使用了 %define api.pure full%locations

int yylex(YYSTYPE *lvalp, YYLTYPE *llocp, scanner_mode *mode, environment_type *env);
int yyparse(parser_mode *mode, environment_type *env);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值