Lex & Yacc 入门

Lex/Yacc 整体做的能把 source code -> tokens -> syntax tree

  • patterns: .l 文件,Lex 会读取并且生成 C 文件。
  • grammer: .y 文件,Yacc 会读取然后生成 C 语言的 parser.

这是一个 basic 语言的 解释器架构图。

  1. bas.l 定义 token 和处理
  2. bas.y 定义语法
  3. yacc 生成了 y.tab.h 和 y.tab.c (yyparse)
  4. lex 根据 bas.l 和 y.tab.h 生成 yylex (为什么混合了 yacc 的逻辑)
  5. compile 生成解释器
Yacc reads the grammar descriptions in bas.y and generates a syntax analyzer (parser), that includes function yyparse, in file y.tab.c. Included in file bas.y are token declarations. The –d option causes yacc to generate definitions for tokens and place them in file y.tab.h. Lex reads the pattern descriptions in bas.l, includes file y.tab.h, and generates a lexical analyzer, that includes function yylex, in file lex.yy.c.

注意这里重要的是他们 include 的 function.

Lex

lex 会根据你指定的词法生成一个状态机。其实可以看到对应词法的 regular expression:

Regular expressions in lex are composed of metacharacters (Table 1). Pattern-matching examples are shown in Table 2.

同时:

If two patterns match the same string, the longest match wins. In case both matches are the same length, then the first pattern listed is used.

可以看看一个基本的 lex file:

%%
/* match everything except newline */
. ECHO;
/* match newline */
\n ECHO;
%%

int yywrap(void) {
    return 1;
}

int main(void) {
    yylex();
    return 0;
}

然后可以设置 define:


digit [0-9] 
letter [A-Za-z] 
%{
	int count; 
%}
%%
    /* match identifier */
{letter}({letter}|{digit})*	 count++;
%%

int main(void) {
    count++;
    yylex();
    printf("number of identifiers = %d\n", count); return 0;
}

Yacc

Yacc 使用的是 BNF 的变体,定义 grammer 来 parse

The BNF grammar is placed in the rules section and user subroutines are added in the subroutines section.

实际上,yacc 代码起作用的地方很反直觉。如果你在 yacc 里定义:

%token INTEGER

你可能会得到 y.tab.h 里面的:

#ifndef YYSTYPE
#define YYSTYPE int
#endif
#define INTEGER 258
extern YYSTYPE yylval;

下面的情况是你可能会在 lex 里面 include <y.tab.h>, 这是我们刚刚在前文就提到了的。Yacc 会 call yy;ex

To obtain tokens yacc calls yylex. Function yylex has a return type of int that returns a token. Values associated with the token are returned by lex in variable yylval.
[0-9]+ {
						yylval = atoi(yytext);
						return INTEGER;
				}
  • 把对应的数值存储在了 yylval
  • 返回值是 YYSTYPE 类型的值 INTEGER

这个类型的 default type 是个 integer (但是我真的不懂为啥)。如果是别的类型直接返回对应 token:

[-+] return *yytext; /* return operator */
  • 如果是数字就是个 integer 类型,存储值到 yylval 中,然后返回 type: INTEGER
  • 如果是 - + 或者 \n 则直接返回对应的类型
  • 如果是空格或者制表符,则 ; <-- skip whitespace.
  • . 匹配其他所有,都是 invalid character

以上全部是 lex part

以下开始 Yacc Part

We may reference positions in the value stack in our C code by specifying “$1” for the first term on the right-hand side of the production, “$2” for the second, and so on. “$$” designates the top of the stack after reduction has taken place. The above action adds the value associated with two expressions, pops three terms off the value stack, and pushes back a single sum. As a consequence the parse and value stacks remain synchronized.

Yacc 的模型是 token 栈 + 数据栈。具体操作类似 BNF 文法,

1 + 2 的时候:

  1. 1 是个 INTEGER, expr: INTEGER 的规则让它成为 expr, 这时候值等于 INTEGER 的值,是 1
  2. + 的 token 和值都是 ‘+’
  3. 2 按 1 步骤处理,此时栈上是 expr + expr, 按规则规约为 expr.

如果你不满意,可以用 user-defined 的 yyerror

发布于 02-05

文章被以下专栏收录