C-lex yacc入门看这篇就够了

13 篇文章 0 订阅

前言

当我们面对一个具有一定语法规则的文本内容,如log文件,CMakeLists.txt,甚至某种编程语言的源文件,希望提取出其中的有用信息时,我们该如何做?最简单的方式就是使用逐字或者逐行读取的方式,根据文本的规则去编写if条件语句,判断是否已经碰到了我们希望读取的内容,如果是则提取信息,否则跳过。

对于具有复杂规则的文本,这么做的效率很低,并且不够优雅。我们可以使用lex yacc来帮助我们进行这项工作。

lex/yacc

lex是Lexical Analyzer Generator(词法分析生成器)的缩写,它能生成一个词法分析程序,该程序运行后可以进行词法分析。
yacc是Yet Another Compiler Compiler(一个编译器的编译器)的缩写,它能生成语法分析器,需要与lex一起使用。

网上有时也能看到flex和bison这两个工具,其地位分别对应于lex和yacc,使用方法也比较类似

lex

lex的输入是一个lex源文件,通常以.l或.lex结尾,输出是一个词法分析程序的C代码,经过编译器编译链接就可以得到可执行程序(scanner),结构如下:

定义块(definition section)
%%
翻译块(translation section)
%%
函数块(function section)

文件由%%分隔的三部分组成,定义(definition)块可将正则表达式命名,方便后面使用并且使lex源文件更具有可读性。规则块包含匹配到正则表达式规则时所对应的动作(action)。函数块部分会直接插入到输出源文件最后面。

定义块

lex允许将一个名字(name)和正则表达式关联起来,称之为定义(definition)

// 格式:name regular-expression

DIGIT [0-9]
ALPHA [a-zA-Z]

这样我们在其他definition或者规则需要使用该正则表达式的地方就可以使用改名字

VAR_NAME {ALPHA}*{DIGIT}*

定义块还可以包含所需的头文件,变量等:

%{
#include <stdio.h>
int global = 0;
%}

%{%}之间的内容会直接插入输出源文件中。

该部分还可以包含一些选项,用于控制lex的一些行为,从而影响输出的源程序内容

%option nounput noyywrap

翻译块

该部分定义了规则和对应的动作(action)

// 格式:token-pattern { actions }
"Hello" {printf("Hi there!\n");}
[0-9] {return YYDIGIT;}

这里我们简单复习下正则表达式:

"if"		//匹配任意位置出现的"if"字符串
“\n”

^"We"		//匹配行开头的"We"字符串
"end"$		//匹配行结尾的"end"字符串
^"name"$	//匹配单行的"name"

[0-9a-z]	//匹配数字和小写a-z单字符
"p.x"		//表示p+任意字符+x的字符串

[0-9]*		//匹配任意长度数字串(包括空)
[0-9]+		//匹配至少一个数字
[0-9]{3}	//匹配三个数字
[0-9]{2,3}	//匹配2-3个数字
A?			//匹配空串或A

"high" | "low" 	//匹配两个表达式中的一个

当在字符串流中匹配到规则时,将会执行相应的代码,如打印信息,返回token值等等。
如果需要使用到一些变量,则其定义应该写在规则块或定义块的最前面,同样使用如下格式插入代码:

%{
int wordcount
%}

在翻译块的代码块会插入生成的yylex函数(稍后介绍)中,是局部变量,在定义段的代码块在yylex函数外,是全局变量。

函数块

这里定义需要用到的函数,直接编写即可。

一些预定义的变量和函数

int yylex(),开始进行词法分析需要调用的函数。
int yywrap(),到达文件末尾时,将调用yywrap,如果不使用,可以定义其返回1,或者使用%option noyywrap
char* yytext,匹配的字符串内容
int yyleng,匹配的字符串长度
``

lex示例

lex源文件:

%{
int word = 0;
%}

ALPHA [a-zA-Z]
WORD {ALPHA}*

%option noyywrap
%%

{WORD} { word++; printf("match WORD: %s\n", yytext); }
[\n\t ]* { printf("match space\n"); }
. { printf("Undefined char: %s\n", yytext); }
%%

//int yywrap() {
//    return 1;
//}
int main() {
    FILE* f;
    f = fopen("test.txt", "r");
    if (!f)
        return -1;
    yyin = f;
    yylex();
    printf("word count: %d\n", word);
    fclose(f);
    return 0;
}

输入文件test.txt:

Hello world!
This is a file to test lex.
end.

编译指令:

lex test.l
gcc lex.yy.c -o scanner

执行scanner,得到输出:

match WORD: Hello
match space
match WORD: world
Undefined char: !
match space
match WORD: This
match space
match WORD: is
match space
match WORD: a
match space
match WORD: file
match space
match WORD: to
match space
match WORD: test
match space
match WORD: lex
Undefined char: .
match space
match WORD: end
Undefined char: .
word count: 10

至此通过编写test.l,我们已经完成了一个简单的词数统计的程序。这里强烈建议查看lex生成的源码,在这里我们可以看到lex帮助我们生成的函数、变量、宏,以及我们自己编写的代码在其中的位置和所起的作用,从而更好的理解scanner的实现原理。

yacc

yacc同样需要输入yacc源文件,以生成parser,其结构如下:

声明块(declarations section)
%%
语法规则块(grammar rules section)
%%
函数块(functions section)

声明块

声明块包含token声明和函数变量声明,以及规则优先级。token声明格式如下:

%token name1 name2 ...

例如

%token WORD INTEGER 

定义了两个token:WORD和INTEGER,yacc生成的程序中通过#define语句定义这两个token的值,从257开始,这是为了避免和已有字符冲突。token的值由scanner在执行yylex过程中从action中返回,表明特定的字符串已经被匹配识别。通常token由大写的字母表示,避免和C中的关键字和yacc生成的函数及变量冲突。

左结合和右结合通过%left%right定义,同一个结合性声明中符号的优先级相同,多个结合性声明中,先声明的符号集优先级更低,例如

%right '='
%left  '+' '-'
%left  '*' '/' '%'

=是右结合,其他是左结合,优先级逐行递增。没有结合性的符号使用%nonassoc声明。也可以声明标识符的结合性,这些标识符将自动声明为token。

变量及函数声明等代码部分同样通过%{%}括起来。

语法规则块块

格式:

identifier : definition ;

例如:

paren_expr : '(' expr ')' ;

我们定义了一个语法:paren_expr(开头)结尾,中间是exprexpr可以是一个token也可以是另一个语法规则。可以继续展开的符号是非终结符,如冒号左边的符号一定是非终结符,不可继续展开的符号是终结符,如字符常量和token。非终结符可以有多个定义,例如:

if_stat : IF '(' expr ')' stat
        │ IF '(' expr ')' stat ELSE stat
        ;

非终结符还可以是递归定义的:

list : item
     │ list ',' item
     ;

这里list的定义是左递归

(yacc部分未完待续)
参考:

  1. https://baike.baidu.com/item/lex/8558986
  2. https://baike.baidu.com/item/Yacc/9855057
  3. https://www.ibm.com/docs/en/zos/2.1.0?topic=tools-tutorial-using-lex-yacc
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mrbone11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值