编译原理 lex yacc

三月 08, 2024 更新: 五月 07, 2024 [C/C++] #obsidian #编译原理 #编译链接

编译原理 lex yacc

lex yacc

只要在Unix环境中写程序,必定会邂逅神秘的Lex&YACC,就如GNU/Linux用户所熟知的Flex&Bison,这里的Flex就是由Vern Paxon实现的一个Lex,Bison则是GNU版本的YACC。在此我们将统一称呼这些程序为Lex和YACC。新版本的程序是向上兼容的(译注:即兼容老版本),所以你可以用Flex和Bison来尝试下我们的实例。

这些程序实用性极广,但如同你的C编译器一样,在其主页上并没有描述它们,也没有关于怎样使用的信息。当和Lex结合使用时,YACC实在是棒极了,但是Bison的主页上并没有描述Bison如何跟Lex结合使用以生成代码的相应说明。

Lex & YACC能为你做什么?

如果使用得当,这些程序(指LEX&YACC)可以让你轻易的解析复杂的语言,当你需要读取一个配置文件时,或者你需要编写一个你自己使用的语言的编译器时,这对于你来说是莫大的裨益。 本文档能提供给你一些帮助,你将发现你再也不用手工写解析器了,Lex & YACC就是为你量身打造的利器。

常用的全局变量和宏

**FILE yyin: FILE yyout: 这是Lex中本身已定义的输入和输出文件指针。这两个变量指明了lex生成的词法分析器从哪里获得输入和输出到哪里。默认:键盘输入,屏幕输出。

*char yytext: 指向当前识别的词法单元(词文)的指针

int yyleng: 当前词法单元的长度。

yylineno 提供当前的行数信息。(lexer不一定支持。)

ECHO: Lex中预定义的宏,可以出现在动作中,相当于fprintf(yyout, “%s”,yytext),即输出当前匹配的词法单元。

REJECT: 指示简析器对当前规则不做处理,而是采用第二匹配规则。 因为解析器在通常情况下,每个被匹配的对象只会对一个动作生效。REJECT指示解析器,会寻找下一个最配的规则来做处理。下面的规则会把输入的”abcd”处理后输出”abcd|abc|ab|a|abcd”。

> a |
> ab |
> abc |
> abcd {ECHO;printf("|");REJECT;}
> %%
>

BEGIN 开始一个条件处理块

int yylex(): 词法分析器驱动程序,用Lex翻译器生成的lex.yy.c内必然含有这个函数。它自动移动文件指针yyin 和 yyout 。 在定义模式动作时,可以用 return 语句来结束 yylex分析函数。return 需要返回一个整数。 由于 yylex() 函数的运行环境都是以全局变量的方式来保存,因此在下一次调用 yylex() 时,yylex()可以从上次扫描的断点处继续扫描。若用户未定义相应的return语句,则yylex()继续分析被扫描的文件,直到碰到文件结束标识符EOF。 在读取到EOF时,yylex() 函数调用 int yywrap() 函数,若yywrap()返回非0值,则yylex() 函数结束返回0;否则,yylex()继续对yyin指向的文件扫描。

int yywrap(): 词法分析器遇到文件结尾时会调用yywrap()来决定下一步怎么做: 若yywrap()返回0,则继续扫描 若返回1,则返回报告文件结尾的0标记。 由于词法分析器总会调用yywrap,因此辅助函数中最好提供yywrap,如果不提供,则在用C编译器编译lex.yy.c时,需要链接相应的库,库中会给出标准的yywrap函数(标准函数返回1)。

yymore() 这一函数告诉 Lexer 将下一个标记附加到当前标记后。 `yymore()’ 告诉解析器下一次匹配的规则,满足的部分将会添加到当前yytext值得后面而不是替换它。 例如,指定的输入”mega-kludge”经过下面的程序处理后将会输出”mega-mega-kludge”。

>%%
> mega- ECHO; yymore();
> kludge ECHO;/* 这时,yytext的值为mega-kludge */
>

yyless(int n) 返回当前匹配项除了开始的n个字符内的所有的内容到输入缓存区,解析器处理下一个匹配时,它们将会被重新解析。yyless将会导致yytext与yyleng的调整。(yyleng将会等于=n) 如输入”createtable”被下面的程序处理后,将会输出”createtableatetable”. 因为前n=3个字符foo外的字符atetable被重新返回到输入缓存区,再次解析。

>createtable ECHO; yyless(3);
> [a-zA-Z]+ ECHO;
>\n return 0;
> %%
>

unput(c) 将字符c放回到输入流中,该字符可以重新被解析。下面的动作将当前的匹配值附上括号后重新进行匹配。

>{
> int i;
>/* Copy yytext because unput() trashes yytext */
> char *yycopy = strdup( yytext );
>unput( ')' );
> for ( i = yyleng - 1; i >= 0; --i )
> unput( yycopy[i] );
> unput( '(' );
> free( yycopy );
> }
>

注意: 由于每次unput()将指定的字符添加到输入源的开头,所以将字符串添加到输入源开头必须从后道前处理。一个比较重要的潜在问题是使用unput()的时候,如果采用了%pointer指针模式保存yytext,unput会破坏yytext的内容,从最右边的字符开始将会破坏左边的一个字符。如果在unput()后要用到yytext,你首先必须复制一份yytext,或者用%array模式来保存yytext. 最后不能尝试放一个EOF标志输入流的结束。

input() 从输入源中读取下一个字符。 下面例子将会吃掉C语言注释

> %%
>"/*" {
>  register int c;
>  for ( ; ; ) {
>   while ( (c = input()) != '*' &&>c != EOF ); /* eat up text of comment */
>    if ( c == '*' ) {
>     while ( (c = input()) == '*' );
>      if ( c == '/' ) break; /* found the end */
>    }
>    if ( c == EOF ) {
>      error( "EOF in comment" );
>      break;
>   }
>  }
> }
>

yyterminate() 可以在动作内部返回描述区域中使用,它将终止解析器并返回0给解析器调用者,表示操作完成。缺省情况下,到达文件结束位置也会被调用,它是一个宏,并且可能重定义。