yacc/lex linux 下 使用指南

 

关于Parser Generator的使用方法可以到:/html/linuxshijie/20070917/84.html

以下文章属林木100版权所有,请勿转载。

Lex介绍

Lex 通过对.lex或.l文件定义的格式生成一个C语言源码文件,通过编译这个源码,就生成了.lex文件或.l文件定义的编译器。.lex或.l文件的格式分三段:
1.全局变量声明部分
2.词法规则部分
3.函数定义部分
以下是一个简单的例子:lex_example.l文件

%{            //全局声明部分
/*林木100 linux
www.linmu100.com
*/
#include <stdio.h> 

extern char *yytext;
extern FILE *yyin;
int sem_count = 0;

%}

%%
[a-zA-Z][a-zA-Z0-9]*    {printf("WORD[%s] ", yytext);}
[a-zA-Z0-9//.-]+        printf("FILENAME ");
/"                      printf("QUOTE ");
/{                      printf("OBRACE ");
/}                      printf("EBRACE ");
;                       {sem_count++; printf("SEMICOLON ");}
/n                      printf("/n");
[ /t]+                  /* ignore whitespace */;
%%
//上面为规则定义部分,以下为函数定义部分
int main(int avgs, char *avgr[])
{
    yyin = fopen(avgr[1], "r");
    if (!yyin)
    {
        return 0;
    }
    yylex();
    printf("sem_count : %d/n", sem_count);
    fclose(yyin);

    return 1;
}

Lex 常用格式如下表,常规表达式:

字符含义

A-Z, 0-9, a-z

构成了部分模式的字符和数字。

.

匹配任意字符,除了 /n。

-

用来指定范围。例如:A-Z 指从 A 到 Z 之间的所有字符。

[ ]

一个字符集合。匹配括号内的任意 字符。如果第一个字符是 ^ 那么它表示否定模式。例如: [abC] 匹配 a, b, 和 C中的任何一个。

*

匹配0个或者多个上述的模式。

+

匹配1个或者多个上述模式。

?

匹配0个或1个上述模式。

$

作为模式的最后一个字符匹配一行的结尾。

{ }

指出一个模式可能出现的次数。 例如: A{1,3} 表示 A 可能出现1次或3次。

/

用来转义元字符。同样用来覆盖字符在此表中定义的特殊意义,只取字符的本意。

^

否定。

|

表达式间的逻辑或。

"<一些符号>"

字符的字面含义。元字符具有。

/

向前匹配。如果在匹配的模版中的“/”后跟有后续表达式,只匹配模版中“/”前面的部分。如:如果输入 A01,那么在模版 A0/1 中的 A0 是匹配的。

( )

将一系列常规表达式分组。

常规表达式举例

常规表达式含义

joke[rs]

匹配 jokes 或 joker。

A{1,2}shis+

匹配 AAshis, Ashis, AAshi, Ashi。

(A[b-e])+

匹配在 A 出现位置后跟随的从 b 到 e 的所有字符中的 0 个或 1个。



使用lex扫描上述举例文件 lex_example.l:
lex lex_example.l
缺省会生成lex.yy.c文件,然后用gcc编译这个文件,注意要有-ll选项:
gcc lex.yy.c -o analyse -ll
这样就生成了一个简单的词法分析器analyse,假设有文件demo,其内容如下所示:
firstword;
secondword;
thirdword

fourthword{
fifthword
}
输入命令:
./analyse demo
会有如下显示:
WORD[firstword] SEMICOLON
WORD[secondword] SEMICOLON
WORD[thirdword]

WORD[fourthword] OBRACE
WORD[fifthword]
EBRACE
sem_count : 2

实际上,对于上述lex_example.l文件,函数定义部分可以完全省略,因为lex会自动为你生成main函数。这时仍然按上述方法生成analyse,输入命令:
./analse < demo
结果如下:
WORD[firstword] SEMICOLON
WORD[secondword] SEMICOLON
WORD[thirdword]

WORD[fourthword] OBRACE
WORD[fifthword]
EBRACE


在上述lex_example.l文件中我们还使用了两个变量:
extern char *yytext;
extern FILE *yyin;
这两个变量是lex提供的外部借口,用户可以根据自己需要自己更改,lex提供了以下接口:

Lex 变量

yyinFILE* 类型。 它指向 lexer 正在解析的当前文件。
yyoutFILE* 类型。 它指向记录 lexer 输出的位置。 缺省情况下,yyin 和 yyout 都指向标准输入和输出。
yytext匹配模式的文本存储在这一变量中(char*)。
yyleng给出匹配模式的长度。
yylineno提供当前的行数信息。(lexer不一定支持。)

Lex 函数

yylex()这一函数开始分析。 它由 Lex 自动生成。
yywrap()这 一函数在文件(或输入)的末尾调用。如果函数的返回值是1,就停止解析。 因此它可以用来解析多个文件。代码可以写在第三段,这就能够解析多个文件。 方法是使用 yyin 文件指针(见上表)指向不同的文件,直到所有的文件都被解析。最后,yywrap() 可以返回 1 来表示解析的结束。
yyless(int n)这一函数可以用来送回除了前憂? 个字符外的所有读出标记。
yymore()这一函数告诉 Lexer 将下一个标记附加到当前标记后。

以下是一个计算字符个数的.l文件内容,有兴趣的朋友可以编译试试
%{
/*
林木100 linux
www.linmu100.com
*/
int wc = 0;        /* word count */
%}
 
%%
[a-zA-Z]+    { wc++; }
/n|.        { /* gobble up */ }
%%
int main(void)
{
    int n = yylex();
    return n;
}
 
int yywrap(void)
{
    printf("word count: %d/n", wc);
    return 1;
}

yacc介绍

Yacc 是 Yet Another Compiler Compiler的缩写。 Yacc 的 GNU 版叫做 Bison。它是一种语法解析工具。它用巴科斯范式(BNF, Backus Naur Form)来书写。按照惯例,Yacc 文件有 .y 后缀。
实际上,yacc才是真正分析语法的核心,.y文件格式和.l文件一样分三段,但每一段的意义有所不同:
1.全局变量声明,终结符号(终端符号)声明
2.语法定义
3.函数定义

以下是一个简单的yacc_example.y文件,定义了一个简单的计算器:
%{
//全局变量声明
#include <ctype.h>
#include <stdio.h>
#define YYSTYPE double /*double type for YACC stack; for yylval*/
 
/*林木100 www.linmu100.com */
 
void yyerror(const char *str)
{
    fprintf(stderr, "error:%s/n", str );
}
%}
//终结符声明
%token NUMBER
 
%%
lines    : lines expr '/n'            { printf("%g/n", $2); }
        | lines '/n'
        | /* e */
        | error '/n'                { yyerror("reenter last line:"); /*yyerrok(); */}
        ;
 
expr    : expr '+' term                { $$ = $1 + $3; }
        | expr '-' term                { $$ = $1 - $3; }
        | term
        ;
 
term    : term '*' factor            { $$ = $1 * $3; }
        | term '/' factor            { $$ = $1 / $3; }
        | factor
        ;
 
factor    : '(' expr ')'                { $$ = $2; }
        | '(' expr error            { $$ = $2; yyerror("missing ')'"); /*yyerrok(); */}
        | '-' factor                { $$ = -$2; }
        | NUMBER
        ;
 
%%
//以上部分为语法定义,以下部分为函数定义
int main(void)
{
    return yyparse();
}
 
int yylex(void)
{
    int c;
    while ((c = getchar()) == ' ');
    if (c == '.' || isdigit(c)) {
        ungetc(c, stdin);
        scanf("%lf", &yylval);
        return NUMBER;
    }
    return c;
}

使用yacc扫描这个文件:
yacc yacc_example.y
缺省会生成一个y.tab.c文件,然后用gcc编译这个文件,注意要有选项 -ll-ly
gcc y.tab.c -o analyse -ll
运行./analyse:结果如下图所示:


现在对照yacc_example.y文件
讲解一下.y文件的规则:
1.在全局变量声明部分,声明了一个接口函数yyerror,这个函数是用来在出错时调用的。这一段主要是声明一些变量,数据结构,函数用。
2.%token NUMBER则声明了一个终端符(终结符),这个符号是由Lex返回的,会在yacc语法规则中用到。
3.语法规则部分则声明了语法:
    3.1语法规则对外只有一个接口,这一点要注意,初学者常常会犯语法对外有多个接口的错误。
    3.2无论是lex文件还是yacc文件都要注意最大可能性的词法和语法规则要放在冲突规则的前面,
这样保证了最大可能规则会被最先匹配,比如lex文件中:
       temperator      return T1;
       temp             return T2;
        在yacc文件中,例子如下
            command:
                NUMBER CHAR
                |   NUMBER
               ;

对于.y文件还要注意全局语法,以及递归的调用。

初学者对于yacc文件规则可能会较为生疏,关键还要多做一些练习。


Lex 和 Yacc 的结合

lex和yacc结合时需要注意的是
lex文件头要引用yacc生成的头文件:"y.tab.h"

以下是一个lex和yacc结合的实例:
lex_yacc_exp.l文件:
%{
/*林木100
www.linmu100.com
*/
#include <stdio.h>
#include <string.h>
#include "y.tab.h"
extern char *yytext;
%}
%%
[0-9]+                  yylval.number=atoi(yytext); return NUMBER;
heater            return TOKHEATER;
heat                    return TOKHEAT;
on|off                  yylval.number=!strcmp(yytext,"on"); return STATE;
target                  return TOKTARGET;
temperature             return TOKTEMPERATURE;
[a-z0-9]+        yylval.string=strdup(yytext);return WORD;
/n                      /* ignore end of line */;
[ /t]+                  /* ignore whitespace */;
%%

lex_yacc_exp.y文件:
%{
/*林木100
www.linmu100.com
*/
#include <stdio.h>
#include <string.h>

void yyerror(const char *str)
{
    fprintf(stderr,"error: %s/n",str);
}

int yywrap()
{
    return 1;
}

main()
{
    yyparse();
}

char *heater="xl's test";

%}

%token TOKHEATER TOKHEAT TOKTARGET TOKTEMPERATURE

%union
{
    int number;
    char *string;
}

%token <number> STATE
%token <number> NUMBER
%token <string> WORD

%%

commands:
    | commands command
    ;


command:
    heat_switch | target_set | heater_select

heat_switch:
    TOKHEAT STATE
    {
        if($2)
            printf("/tHeater '%s' turned on/n", heater);
        else
            printf("/tHeat '%s' turned off/n", heater);
    }
    ;

target_set:
    TOKTARGET TOKTEMPERATURE NUMBER
    {
        printf("/tHeater '%s' temperature set to %d/n",heater, $3);
    }
    ;

heater_select:
    TOKHEATER WORD
    {
        printf("/tSelected heater '%s'/n",$2);
        heater=$2;
    }
    ;

输入以下命令,分别生成lex.yy.c,y.tab.c,y.tab.h三个文件:
lex lex_yacc_exp.l
yacc -d lex_yacc_exp.y
gcc lex.yy.c y.tab.c -o analyse -ll

创建一个语法用例demo,内容如下:
heat on
target temperature 99
heater asdfsieiwef99adsf

输入./analyse <demo分析demo文件,会得到以下结果:



结语:
Lex 和 Yacc 是很强大的工具,这里只简单介绍了一些入门知识。
The Lex & Yacc Page 中有很多有趣的历史参考,以及 非常好的 lex 和 yacc 文档。

参考文档:
http://www.ibm.com/developerworks/cn/linux/l-lexyac.html
http://blog.csdn.net/ThinkinginLinux/archive/2005/03/19/323379.aspx

转载

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
作者: 胡彦 本框架是一个lex/yacc完整的示例,用于学习lex/yacc程序基本的搭建方法,在linux/cygwin下敲入make就可以编译和执行。 本例子虽小却演示了lex/yacc程序最常见和重要的特征: * lex/yacc文件格式、程序结构。 * 如何在lex/yacc中使用C++和STL库,用extern "C"声明那些lex/yacc生成的、要链接的C函数,如yylex(), yywrap(), yyerror()。 * 重定义YYSTYPE/yylval为复杂类型。 * 用%token方式声明yacc记号。 * 用%type方式声明非终结符的类型。 * lex里正则表达式的定义、识别方式。 * lex里用yylval向yacc返回属性值。 * 在yacc嵌入的C代码动作里,对记号属性($1, $2等)、和非终结符属性($$)的正确引用方法。 * 对yyin/yyout重赋值,以改变yacc默认的输入/输出目标。 * 如何开始解析(yyparse函数),结束或继续解析(yywrap函数)。 本例子功能是,对当前目录下的file.txt文件,解析出其中的标识符、数字、其它符号,显示在屏幕上。linux调试环境是Ubuntu 10.04。 总之,大部分框架已经搭好了,你只要稍加扩展就可以成为一个计算器之类的程序,用于《编译原理》的课程设计。 文件列表: lex.l: lex程序文件。 yacc.y: yacc程序文件。 main.hpp: 共同使用的头文件。 Makefile: makefile文件。 file.txt: 给程序解析的文本文件。 使用方法: 1-把lex_yacc_example.rar解压到linux/cygwin下。 2-命令行进入lex_yacc_example目录。 3-敲入make,这时会自动执行以下操作: (1) 自动调用flex编译.l文件,生成lex.yy.c文件。 (2) 自动调用bison编译.y文件,生成yacc.tab.c和yacc.tab.h文件。 (3) 自动调用g++编译、链接出可执行文件main。 (4) 自动执行main,得到如下结果:。 bison -d yacc.y g++ -c lex.yy.c g++ -c yacc.tab.c g++ lex.yy.o yacc.tab.o -o main id: abc id: defghi int: 123 int: 45678 op: ! op: @ op: # op: $ AllId: abc defghi 参考资料:《LexYacc从入门到精通(6)-解析C-C++包含文件》, http://blog.csdn.net/pandaxcl/article/details/1321552 其它文章和代码请留意我的blog: http://blog.csdn.net/huyansoft 2013-4-27
好的,我可以帮你解答这个问题。 首先,逆波兰计算器是一种基于后缀表达式的计算器,它能实现基本的算术运算。我们可以使用YACC/Bison来构建一个逆波兰计算器。 以下是一个简单的逆波兰计算器的代码: ```c %{ #include <stdio.h> #include <stdlib.h> %} %token NUMBER %left '+' '-' %left '*' '/' %right UMINUS %% input: /* empty */ | input line ; line: '\n' | exp '\n' { printf("%.2f\n", $1); } ; exp: NUMBER { push($1); } | exp exp '+' { push(pop() + pop()); } | exp exp '-' { push(-pop() + pop()); } | exp exp '*' { push(pop() * pop()); } | exp exp '/' { push(1.0 / pop() * pop()); } | exp UMINUS { push(-pop()); } ; %% void push(double); double pop(void); int main(void) { yyparse(); return 0; } void push(double f) { printf("push %.2f\n", f); } double pop(void) { printf("pop\n"); return 0.0; } int yyerror(char *s) { fprintf(stderr, "error: %s\n", s); return 0; } int yywrap(void) { return 1; } ``` 以上代码使用了YACC/Bison语法来定义输入的规则,以及各种运算操作的规则。在主函数中,我们调用yyparse()函数来开始解析输入的表达式。 在表达式解析过程中,我们使用push()函数将解析到的数字压入栈中,使用pop()函数从栈中弹出数字,并进行各种运算操作。 需要注意的是,以上代码并没有进行具体的数值计算,而是简单地输出了一些调试信息。如果需要进行具体的数值计算,需要在相应的运算操作中添加具体的计算代码。 希望以上内容能够帮助你理解如何使用YACC/Bison构建一个逆波兰计算器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值