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-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

Linux服务器Java启动脚本

Linux服务器Java启动脚本 1、初版2、优化版本3、常用脚本仓库 本文章介绍了如何在Linux服务器上执行Java并启动jar包, 通常我们会使用nohup直接启动,但是还是需要手动停止然后再次启动, 那如何更优雅的在服务器上启动jar包呢,让我们一起探讨一下吧。 1、初版 第一个版本是常用的做法,直接使用nohup后台启动jar包, 并将日志输出到当前文件夹n

[Linux]:进程(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:Linux学习 贝蒂的主页:Betty’s blog 1. 进程终止 1.1 进程退出的场景 进程退出只有以下三种情况: 代码运行完毕,结果正确。代码运行完毕,结果不正确。代码异常终止(进程崩溃)。 1.2 进程退出码 在编程中,我们通常认为main函数是代码的入口,但实际上它只是用户级

【Linux】应用层http协议

一、HTTP协议 1.1 简要介绍一下HTTP        我们在网络的应用层中可以自己定义协议,但是,已经有大佬定义了一些现成的,非常好用的应用层协议,供我们直接使用,HTTP(超文本传输协议)就是其中之一。        在互联网世界中,HTTP(超文本传输协议)是一个至关重要的协议,他定义了客户端(如浏览器)与服务器之间如何进行通信,以交换或者传输超文本(比如HTML文档)。

如何编写Linux PCIe设备驱动器 之二

如何编写Linux PCIe设备驱动器 之二 功能(capability)集功能(capability)APIs通过pci_bus_read_config完成功能存取功能APIs参数pos常量值PCI功能结构 PCI功能IDMSI功能电源功率管理功能 功能(capability)集 功能(capability)APIs int pcie_capability_read_wo