自己手写词法分析器和语法分析器是很麻烦的一件事,而且这里面的逻辑非常复杂很容易出错。flex和lemon就是用来帮助生成词法分析器和语法分析器的,只需要写少量规则代码,就可以生成解析的c代码。现在先不关注实现原理,主要看一下这东西是怎么用的,等以后用熟了要实现深度定制的时候再来看实现源码。

1.词法分析器

首先得安装一个flex,至于怎么安装就不讲了。词法分析器的功能就是把一串字符串按照给定的规则分割成一个个记号(Token),对于flex来说,需要写一个指明规则的.i文件,然后再生成.c文件。

还是举个例子吧,比如下面这个.i文件,是用来解析形如~A->B->C的字符串,把字符串分割成 ~ 、A、->、B、->、C

%{#include "token.h"
%}%option reentrant
%option noyywrap%%[a-zA-Z]+    { return TK_SYM; }
~    { return TK_NEG; }
->    { return TK_IMPL; }%%

在%{和%}的内容会原封不动的搬到c代码里,一般是头文件。%option是指定词法编译时的一些规则,%option reentrant这个定义十分重要,没有这行生成的yy_flex函数是不带参数的int yylex (void),有了这一行就会生成带参数的yy_flex函数:int yylex (yyscan_t yyscanner),可以看到没有带参数的只能返回Token值,而带参数的就可以从yyscanner中取得实际值还有行数等其他信息,例如解析到A时,不带参数的只返回TK_SYM,而带参数的还可以返回该Token对应的值A。

在%%包含的范围内左边是要解析的规则,以正则表达式的形式表示,如[a-zA-Z]+表示的是一个或多个字母,右边{}里是解析到对应的规则所要执行的c语言动作,这里是返回对应的Token,如果不返回的话yy_flex()函数将会一直执行下去。

int main(int argc, char** argv)
{int token;yyscan_t scanner;FILE *fd;setbuf(stdout, NULL);yylex_init(&scanner);if (argc > 1){if (!(fd = fopen(argv[1], "r"))){perror(argv[1]);return 1;}else{yyset_in(fd, scanner);}}token = yylex(scanner);while (token){printf("tocken %d %s\n", token, yyget_text(scanner));token = yylex(scanner);}printf("end %d %s\n", token, yyget_text(scanner));yylex_destroy(scanner);return 0;
}

输入参数scanner在使用前要初始化,使用完毕后要销毁,如果程序带参数,那么将以第一个参数对应的文件作为输入,通过 yyset_in(fd, scanner);设置。每一次调用yylex(scanner);后返回Token值,Token需要自己宏定义,在前面的.i文件中包含了token.h头文件,在头文件里定义

#define TK_SYM 1
#define TK_NEG 2
#define TK_IMPL 3

token值从1开始,不能设为0,因为0是默认返回值,语法分析器也是以0作为结束标志。解析到的Token信息存放在scanner结构体变量里,yyget_text(scanner)是对应的字符串值。

另外yylex()函数的逻辑是先扫描完整个文件也就是扫描到文件结束符后再开始解析,从stdin输入flex会根据输入流自动调整从而变为出现回车符后就解析,但这个在eclipse里调试没用,所以会出现输入字符串加回车键没反应的现象,如果想要扫描到回车键后就解析可以在yy_init_buffer函数里把b->yy_is_interactive手工设为1

另外还有一个状态切换的语法,感觉没什么用,但还是讲一下吧,看下面的代码

["]                     { BEGIN(DOUBLE_QUOTED); }
<DOUBLE_QUOTED>[^"]+    { }
<DOUBLE_QUOTED>["]      { BEGIN(INITIAL); return ARGUMENT; }
<DOUBLE_QUOTED><<EOF>>  { return -1; }

这个代码的意思是解析到第1个双引号后进入DOUBLE_QUOTED状态,<DOUBLE_QUOTED>的意思是只有在DOUBLE_QUOTED的状态下才对后面的规则解析,<DOUBLE_QUOTED>["]是解析到第2个双引号后返回初始状态INITIAL,状态需要先通过这一句%x DOUBLE_QUOTED定义好状态名,INITIAL已经默认定义好了不需要用户定义。

2.语法分析器

一般比较常用的语法分析器是bison,而我这里选择的是lemon,lemon本来是为sqlite的语法解析而开发的,当然也可以做其他的语法解析,功能和bison差不多,但是lemon更加小巧,语法更友好,调试信息很丰富,更重要的是有人给lemon的源码写了一本很详细的书,叫《LEMON语法分析生成器(LALR 1类型)源代码情景分析》

lemon源代码就一个lemon.c文件,直接编译就好了。然后用lemon生成语法分析的.c文件时需要用到2个文件,分别是.y后缀的语法文件和模板文件lempar.c。如果语法文件是test.y,经过命令./lemon test.y就生成了test.c的语法分析文件和用来说明状态转换的.out文件,先来看一下api的使用,如下是解析一个计算器语法1+2

int main()
{void* pParser = ParseAlloc(malloc);ParseTrace(stdout, "");Parse(pParser, NUM, 1);Parse(pParser, PLUS, 0);Parse(pParser, NUM, 2);Parse(pParser, 0, 0);ParseFree(pParser, free);
}

这里NUM、PLUS是宏定义好了的Token对象,开始语法分析前先创建一个语法分析对象,并设定要使用的内存分配函数,使用完毕后要记得销毁。 ParseTrace(stdout, “”);这一行输出的整个语法解析过程的调试信息,主要是关于LRLA(1)分析时移进和规约的一些细节。然后把然后每次输入一个Token和对应的关联信息给Parse函数进行解析,当然Token可以由词法分析器生成。

下面来看整个语法文件,就是带.y后缀的文件

%include {#include <iostream>
#include "example1.h"
using namespace std;}%token_type { int }
%left PLUS MINUS.
%left DIVIDE TIMES.program ::= expr(A). {cout << "Result = " << A << "\n" << endl;}expr(A) ::= expr(B) MINUS expr(C). { A = B - C; }
expr(A) ::= expr(B) PLUS expr(C). { A = B + C; }
expr(A) ::= expr(B) TIMES expr(C). { A = B * C; }
expr(A) ::= expr(B) DIVIDE expr(C). { A = B / C; }expr(A) ::= NUM(B). { A = B; }

%开头的参数指的是Lemon的特殊声明,%include指的是头文件,这部分内容会原封不动移到生成的.c文件开头,%token_type指定Parse第3个参数的类型,一般是Token对应信息参数的类型,解析时每个表达式后的小括号会跟一个对象,这个对象就是传入的第3个参数。当然还可以用%extra_argument指定第4个参数类型,从而可以在解析过程中传递和解析更多信息。%left指定运算是左结合的,同时下一行声明的%left运算符优先级比上一行高。

接下来就是语法声明部分了,如果输入的表达式满足::=的右边时,将被规约到左边,{}里的内容是规约到该语法规则时执行的代码,expr后面的括号是Token绑定的对象,之前说了由Parse()函数的第3个参数输入。表达式又分为终结符和非终结符,终结符用大写字母表示,由Parse()的第2个参数输入,使用前要先定义好

#define PLUS                            1
#define MINUS                           2
#define DIVIDE                          3
#define TIMES                           4
#define NUM                             5

0不能定义,这个是默认作为输入终止的符号,非终结符用小写字母表示,由语法产生式规约得到,只在解析器内部使用。

下面简单说一下整个解析的过程,首先输入NUM(1),这时候语法栈中没有任何符号,不用做规约,直接把NUM压栈即可,再次输入PLUS,这时候栈中有NUM符号,根据语法expr(A) ::= NUM(B)先将其规约为expr(A),因为当前输入的不是结束符,所以program ::= expr(A)不能规约,这个规则放在第一行,表示当前语法模块已经完全解析完毕,expr不能进一步规约后将PLUS入栈,此时栈中有expr、PLUS。接下来输入NUM(2),没有要规约的,将NUM入栈,此时栈中有expr、PLUS、NUM。最后输入结束符,先把NUM规约为expr,此时栈中有expr、PLUS、expr再由expr(A) ::= expr(B) PLUS expr( C).语法继续规约为expr,由于现在输入是结束符,所以expr最后还可以规约为program,这时候解析完全结束,会调用yy_accept函数。

当然想要更透彻的理解整个过程还要结合.out文件里状态转换图和 ParseTrace(stdout, “”);输出的移进和规约信息,以下是ParseTrace调试信息

Input NUM
Shift 8
Stack: NUM
Input PLUS
Reduce [expr ::= NUM].
Shift 5
Stack: expr
Shift 3
Stack: expr PLUS
Input NUM
Shift 8
Stack: expr PLUS NUM
Input $
Reduce [expr ::= NUM].
Shift 6
Stack: expr PLUS expr
Reduce [expr ::= expr PLUS expr].
Shift 5
Stack: expr
Reduce [program ::= expr].
Result = 3Accept!
Popping $

想要对解析过程做一些修改满足额外需要,主要关注下面这几个函数

yy_find_shift_action()//确认移进和规约的转移状态
yy_shift()//执行移进操作
yy_reduce()//执行规约操作
yy_find_reduce_action()//确认是否可以进一步规约

关于这几个函数《LEMON语法分析生成器(LALR 1类型)源代码情景分析》的359页有讲解,当然直接看是不好理解的,需要结合lemon源码和LALR(1)的分析过程整体理解。

词法分析器flex和语法分析器lemon的初步使用相关推荐

  1. 构造可配置词法语法分析器生成器(上)

    本文为笔者原创,转载请注明出处 http://blog.csdn.net/xinghongduo 前言 源程序在被编译为目标程序需要经过如下6个过程:词法分析,语法分析,语义分析,中间代码生成,代码优 ...

  2. 基于flex/bison工具生成sysY2022文法的词法/语法分析器

    基于flex/bison工具生成sysY2022文法的词法/语法分析器 本文内容学习借鉴了往届参赛的优秀校友学长学姐的开源作品代码,仅用于学习目的,现分享给大家,如有侵权请联系我删除.我使用的开发环境 ...

  3. mysql词法分析antlr4_词法分析器和语法分析器的界线 - ANTLR 4 简明教程

    因为词法规则可以使用递归,所以词法解析器在技术上和语法解析器一样强大.那意味着我们甚至可以在词法分析器中匹配语法结构.或者,在另一个极端,我们可以把字符当作记号,使用语法分析器去把语法结构应用到字符流 ...

  4. C-语言词法分析器与语法分析器(一)

    说明: 为实践<编译原理>中的相关知识,认真完成了课程设计,实现了C-语言的词法分析器与语法分析器 C-语言是C语言的一个子集,语法包括: 整型变量与函数的声明 if else 分支语句 ...

  5. 【编译原理】词法分析器语法分析器

    简单编译器设计 采用Java语言对C++语言进行编译,具体的简单编译器设计 词法分析器-扫描器的设计与实现 基本符号表 状态转换图 代码实现 import java.io.*; import java ...

  6. Erlang词法分析器、语法分析器(lexer-leex,yac-yecc)

    一.简介 一门编程语言的编译器或者解释器通常功能分解为两步: 1.读取源码文件然后分析它的结构 2.处理这些结构,例如生成目标程序 lexer和yacc就是能完成第一步以便生成程序段的工具.而第一步的 ...

  7. 南华大学编译原理----词法分析器的设计与实现、语法分析器的设计与实现

    下载链接:(各位同学不需要充钱哈,这种我也没有收益,去淘宝上面找个代下,大概0.5元就能下载实验报告,用来给同学们参考,下载积分不是我设置的,是网站自己默认的) ------------------- ...

  8. 【编译原理】LR语法分析器的设计与实现

    LR语法分析器的设计与实现 本文为当时编译原理实验作业,要求用设计的思想完成,小题大做,仅供参考 文章目录 LR语法分析器的设计与实现 实验要求 实现功能 输入输出 样例 一.LR语法分析器问题定义 ...

  9. 简易编译器实现(二)使用Bison创建语法分析器

    你也可以通过我的独立博客 -- www.huliujia.com 获取本篇文章 简易编译器实现(一)使用Flex创建词法分析器一文介绍了编译器的概念和七个阶段,并说明了如何使用Flex创建词法分析器. ...

最新文章

  1. uboot给内核传参的方式——tag
  2. keras入门之手写字识别python代码
  3. c++排序算法ppt_C/C++学习教程:C语言排序算法—插入排序算法
  4. 剑桥offer(41~50)
  5. java映射的概念_Java之路:映射(Map)
  6. 计算机相关各机构简称
  7. 苹果恢复出厂设置系统也会还原吗_手机经常恢复出厂设置会怎么样?对手机有害处吗?这下终于清楚了...
  8. 突然间电脑的复制粘贴不能用了???
  9. android马甲包代理,安卓渠道马甲包配置
  10. 算法之红黑树/JAVA
  11. java 生成交易快照_Java生成订单号/交易流水号
  12. python解二元一次方程_Python数据处理篇之Sympy系列(五)---解方程
  13. Genome Biology + 微生物组学研究,2022『热门研究与论文发表系列研讨会』 第六期即将开讲!...
  14. 酒店预定系统开发方案
  15. Debug Hacks (1): 理解用GOT/PLT调用函数的原理
  16. Java乘船_pokemmo神奥哪里坐船
  17. crm商机管理是什么意思,你知道吗?
  18. Linux分区大师,linux分区管理
  19. 数字化转型导师坚鹏:BLM农商行数字化转型实战解决方案及案例
  20. WordPressJustNews资讯博客类模板源码V5.2.2版

热门文章

  1. mysql getgeneratedkeys,JDBC获得数据库生成的主键(JDBC、Primary Key)
  2. 使用HTML5的Canvas和raycasting创建一个伪3D游戏(part1)
  3. 图片预加载和懒加载的实现方法
  4. python数据动态可视化进阶版,Matplotlib Animations 数据可视化进阶
  5. 适合小白的人事面试回答模板
  6. DM8DSC iscsi+Mutipath环境下的共享集群部署
  7. Revit 2011 可否用API来链接 Rvt 文件?
  8. AcWing 1304. 佳佳的斐波那契
  9. 用C#对Illustrator矢量图形软件进行编程之2
  10. 小试牛刀--我的快速离散傅里叶变化matlab函数(FFT)