yacc / lex 在linux 下 使用指南

2023-10-19 01:32
文章标签 linux 使用指南 yacc lex

本文主要是介绍yacc / lex 在linux 下 使用指南,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

    Lex 和 Yacc 是 Unix 和Linux 下词法和语法的分析,解析工具,有了这两个工具,你可以自己制作想要的编译器,也可以重新制作已有程序语言的解析器。需要注意的是linux下的这两个工具生成的程序源码只能是C和C++语言,当然现在早已有类似可以生成Java源码的语法分析器,如较常用的JavaCC(Java Compiler Compiler),相关内容可以去网上搜索。Lex和Yacc已被移植到windows下,现在常用的工具有Parser Generator。本文只介绍Linux 下Lex和Yacc的使用方法。


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 变量

yyin

FILE* 类型。 它指向 lexer 正在解析的当前文件。

yyout

FILE* 类型。 它指向记录 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:结果如下图所示:
clip_image001

现在对照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文件,会得到以下结果:
clip_image002 


结语:
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

/
/*-----Lex & Yacc ----www.linmu100.com ----*/
/
/*-----linux工具,Lex & Yacc,基本操作----*/
/
/*-----linux配置,UNIX,开源软件,linux技术,makefile----*/
/
/*----------------------@xiaolin--------------------*/
/

这篇关于yacc / lex 在linux 下 使用指南的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/236414

相关文章

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

Linux云服务器手动配置DNS的方法步骤

《Linux云服务器手动配置DNS的方法步骤》在Linux云服务器上手动配置DNS(域名系统)是确保服务器能够正常解析域名的重要步骤,以下是详细的配置方法,包括系统文件的修改和常见问题的解决方案,需要... 目录1. 为什么需要手动配置 DNS?2. 手动配置 DNS 的方法方法 1:修改 /etc/res

Linux创建服务使用systemctl管理详解

《Linux创建服务使用systemctl管理详解》文章指导在Linux中创建systemd服务,设置文件权限为所有者读写、其他只读,重新加载配置,启动服务并检查状态,确保服务正常运行,关键步骤包括权... 目录创建服务 /usr/lib/systemd/system/设置服务文件权限:所有者读写js,其他

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

Linux挂载linux/Windows共享目录实现方式

《Linux挂载linux/Windows共享目录实现方式》:本文主要介绍Linux挂载linux/Windows共享目录实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录文件共享协议linux环境作为服务端(NFS)在服务器端安装 NFS创建要共享的目录修改 NFS 配

linux系统中java的cacerts的优先级详解

《linux系统中java的cacerts的优先级详解》文章讲解了Java信任库(cacerts)的优先级与管理方式,指出JDK自带的cacerts默认优先级更高,系统级cacerts需手动同步或显式... 目录Java 默认使用哪个?如何检查当前使用的信任库?简要了解Java的信任库总结了解 Java 信

Python pickle模块的使用指南

《Pythonpickle模块的使用指南》Pythonpickle模块用于对象序列化与反序列化,支持dump/load方法及自定义类,需注意安全风险,建议在受控环境中使用,适用于模型持久化、缓存及跨... 目录python pickle 模块详解基本序列化与反序列化直接序列化为字节流自定义对象的序列化安全注

Linux命令rm如何删除名字以“-”开头的文件

《Linux命令rm如何删除名字以“-”开头的文件》Linux中,命令的解析机制非常灵活,它会根据命令的开头字符来判断是否需要执行命令选项,对于文件操作命令(如rm、ls等),系统默认会将命令开头的某... 目录先搞懂:为啥“-”开头的文件删不掉?两种超简单的删除方法(小白也能学会)方法1:用“--”分隔命