自己动手写编译器:golex 和 flex 比较研究 2

上一节我们运行了 gcc 使用的词法解析器,使用它从.l 文件中生成对应的词法解析程序。同时我们用相同的词法规则对 golex 进行测试,发现 golex 同样能实现相同功能,当然这个过程我们也发现了 golex 代码中的不少 bug,本节我们继续对 golex 和 flex 进行比较研究,首先我们在上一节.l 文件的基础上增加更多的判断规则,其内容如下:

{
/*
this sample demostrates simple recognition: a verb/ not a verb
*/
%}%%
[\t ]+      /* ignore witespace */;
is |
am |
are |
were |
was |
be |
being |
been |
do |
does |
did |
will |
would |
should |
can |
could |
has |
have |
had |
go    {printf("%s: is a verb\n", yytext);}very |
simply |
gently |
quitely |
calmly |
angrily  {printf("%s: is an adverb\n", yytext);}to |
from |
behind |
above |
below |
between {printf("%s: is a preposition\n", yytext);}if |
then |
and |
but |
or  {printf("%s: is a conjunction\n", yytext);}
their |
my |
your |
his |
her |
its  {printf("%s: is a adjective\n", yytext);}I |
you |
he |
she |
we |
they  {printf("%s: is a pronoun\n", yytext);}[a-zA-z]+ {printf("%s: is not a verb\n", yytext);}%%main() {yylex();
}

将上面内容存储城 ch1-03.l然后运行如下命令:

lex ch1-03.l
gcc lex.yy.c -o ch1-03

于是在本地目录就会生成 ch1-03 的可执行文件,通过./ch1-03 运行该程序,然后输入文本如下:
请添加图片描述
我们将相同的词法规则内容放到 golex 试试,于是在 input.lex 中输入内容如下:

%{/*this sample demostrates simple recognition: a verb/ not a verb*/
%}
%%
is|
am|
are|
was|
be|
being|
been|
do|
does|
did|
will|
would|
should|
can|
could|
has|
have|
had|
go {printf("%s is a verb\n",yytext);}very|
simply|
gently|
quietly|
calmly|
angrily  {printf("%s is a  adverb\n", yytext);}to|
from|
behind|
above|
below|
between  {printf("%s is a preposition\n", yytext);}if|
then|
and|
but|
or  {printf("%s is a  conjunction\n", yytext);}their|
my|
your|
his|
her|
its   {printf("%s is a  adjective\n", yytext);}I|
you|
he|
she|
we|
they   {printf("%s is a  pronoun\n", yytext);}[a-zA-Z]+ {printf("%s is a not verb\n", yytext);}
(\s)+    {printf("ignoring space\n");}
%%
int main() {int fd = ii_newfile("/Users/my/Documents/CLex/num.txt");if (fd == -1) {printf("value of errno: %d\n", errno);}yylex();return 0;
}

然后执行 golex 程序生成 lex.yy.c,将其内容拷贝到 CLex 项目的 main.c,然后编译。在 num.txt 中添加内容如下:

did I have fun?
I should have had fun
he and she has fun from the park
they are enjoying the day very much

运行 CLex 项目,所得结果如下:

Ignoring bad input
did is a verb
ignoring space
I is a  pronoun
ignoring space
have is a verb
ignoring space
fun is a not verb
Ignoring bad input
Ignoring bad input
I is a  pronoun
ignoring space
should is a verb
ignoring space
have is a verb
ignoring space
had is a verb
ignoring space
fun is a not verb
Ignoring bad input
he is a  pronoun
ignoring space
and is a  conjunction
ignoring space
she is a  pronoun
ignoring space
has is a verb
ignoring space
fun is a not verb
ignoring space
from is a preposition
ignoring space
the is a not verb
ignoring space
park is a not verb
Ignoring bad input
they is a  pronoun
ignoring space
are is a verb
ignoring space
enjoying is a not verb
ignoring space
the is a not verb
ignoring space
day is a not verb
ignoring space
very is a  adverb
ignoring space
much is a not verb

可以看到 CLex的输出结果跟 flex一致,这意味着golex 和 flex 目前在功能上等价。可以看到当前我们的词法解析程序不够灵活,每次相应增加新的解析规则或是要判断新单词时,我们需要更改.lex 文件,然后重新编译,执行并生成新的 lex.yy.c 文件。

下面我们希望能做到不要重新编译执行 golex,我们也能动态识别新增加的单词。这里我们需要使用符号表的方法,同时我们需要在.l 或.lex 文件中设置更加复杂的规则和代码,首先我们定义模板文件的头部,内容如下:

%option noyywrap%{/*word recognizer with a symbol table*/enum {LOOKUP = 0,VERB,ADJ,ADV,NOUN,PREP,PRON,CONJ
};int state;int add_word(int type, char* word);
int lookup_word(char* word);
%}

在上面内容,我们先定义一系列命令关键字,也就是当我们在命令行输入 “verb"时,state 变量的值就是 verb,此时 add_word 将把用户在 verb 命令之后的单词作为类型 verb 加入到符号表中。此时用户输入的单词"verb”, “ADJ"等都会作为命令来使用,这些词就相当于编程语言中的关键字。函数 add_word 将把用户输入的单词加入到符号表对应类别,例如"verb has”,这条命令就会将单词has加入到符号表,并且设置其类型为 verb。lookup_word 用于在符号表中查询给定单词是否已经存在。

我们看模板文件的接下来部分:

\n  {state = LOOKUP;}
^verb {state = VERB;}
^adj {state = ADJ;}
^noun {state = NOUN;}
^prep {state = PREP;}
^pron {state = PRON;}
^conj {state = CONJ;}[a-zA-Z]+  {if (state != LOOKUP) {add_word(state, yytext);}else {switch(lookup_word(yytext)) {case VERB:  printf("%s: verb\n", yytext); break;case ADJ:   printf("%s: adjective\n", yytext); break;case ADV:   printf("%s: adverb\n", yytext); break;case NOUN:  printf("%s: noun\n", yytext); break;case PREP:  printf("%s: propsition\n", yytext); break;case PRON:  printf("%s: pronoun\n", yytext); break;case CONJ:  printf("%s: conjunction\n", yytext); break;default:printf("%s: don't recognize\n", yytext); break;}}
}
%%

可以看到上面代码比较复杂,首先它规定如果用户输入的是换行,那么程序进入 LOOKUP 状态,后续输入的字符串就会在符号表中进行匹配。如果在一行的起始用户输入的是关键字例如 verb, adj 等,那么程序进入单词插入状态,如果命令是 verb,那么后面输入的字符串都会以 verb 类型加入到符号表,其他命令例如 adj, adv 等的逻辑也相同。

我们看模板文件第三部分的内容:

main() {yylex();
}struct word {char* word_name;int word_type;struct word* next;
};struct word* word_list;extern void *malloc();int add_word(int type, char* word) {struct word* wp;if (lookup_word(word) != LOOKUP) {printf("!!! warning: word %s already defined\n", word);return 0;}wp = (struct word*)malloc(sizeof(struct word));wp->next = word_list;wp->word_name = (char*) malloc(strlen(word)+1);strcpy(wp->word_name, word);wp->word_type = type;word_list = wp;return 1;
}int lookup_word(char* word) {struct word* wp = word_list;for(; wp; wp = wp->next) {if (strcmp(wp->word_name, word)==0) {return wp->word_type;}}return LOOKUP;
}

在上面代码中,我们用一个列表来存储插入的单词,每个插入单词对应一个 Word 结构,它包含了单词的字符串,类型,还有指向下一个 Word对象的指针。lookup_word 函数遍历整个列表,看看有没有与给定字符串匹配的单词,add_word新增加一个 Word 结构,将给定字符串写入 Word 结构的 word_name 对象,设置其类型,也就是 word_type 的值,然后插入队列的开头。

将上面内容存为文件 ch1-04.l,使用如下命令构建 lex.yy.c:

lex ch1-04.l
gcc lex.yy.c -o 1-04

我们看看生成程序 1-04 的执行效果:
请添加图片描述

为了实现对应功能,GoLex 需要做相应修改,它需要做到如果输入是从控制台进来,那么每次读完一行数据后,它下次还需要再次从控制台读取,因此我们需要在 CLex 程序中增加一个 ii_console 函数,它判断当前输入是否来自控制台,在 input.c中添加如下代码:


int ii_console() {//返回输入是否来自控制台return Inp_file == STDIN;
}

同时在 l.h 中增加该函数的声明:

extern int ii_console();

接下来我们需要修改 yywrap,它需要判断当前输入是否来自控制台,如果是,那么它要再次打开控制台获取输入,在 GoLex中的 lex.par 中修改 yywrap 如下:

int yywrap() {//默认不要打开新文件if (ii_console()) {//如果输入来自控制台,那么程序不要返回ii_newfile(NULL);return 0;}return 1;
}

在上面代码实现中,如果输入来自控制台,那么 ii_console 返回 1,ii_newfile 调用时传入 NULL,输入系统就会再次打开控制台,然后等待用户输入。同时在这次比较中我也发现 GoLex 有 bug,那就是在 LexReader 的Head 函数中,当我们从输入读入一行字符串时,我们没有检测读入的是否是空字符串,如果是空字符串,我们需要继续读入下一行,因此在 LexReader.go 中我们做如下修改:

func (l *LexReader) Head() {/*读取和解析宏定义部分*/transparent := falsefor l.scanner.Scan() {l.ActualLineNo += 1l.currentInput = l.scanner.Text()if l.Verbose {fmt.Printf("h%d: %s\n", l.ActualLineNo, l.currentInput)}//bug here//如果读入的行为空,那么重新读入下一行if len(l.currentInput) == 0 {continue}if l.currentInput[0] == '%' {。。。。

有了上面修改后,GoLex 基本上也能做到前面 flex 程序的功能,但还有一个问题,那就是如果我们把前面 ch01-4.l 中的如下所示的代码直接放到 input.lex 中,GoLex 就会崩溃:

[a-zA-Z]+  {if (state != LOOKUP) {add_word(state, yytext);}else {switch(lookup_word(yytext)) {case VERB:  printf("%s: verb\n", yytext); break;case ADJ:   printf("%s: adjective\n", yytext); break;case ADV:   printf("%s: adverb\n", yytext); break;case NOUN:  printf("%s: noun\n", yytext); break;case PREP:  printf("%s: propsition\n", yytext); break;case PRON:  printf("%s: pronoun\n", yytext); break;case CONJ:  printf("%s: conjunction\n", yytext); break;default:printf("%s: don't recognize\n", yytext); break;}}
}

这是因为 GoLex 的 RegParser 在解析正则表达式时,它一次只读入一行。上面代码中正则表达式在匹配后对应的处理代码跨越了多行,因此这种格式会导致我们 RegParser 解析出错。一种解决办法是修改 RegParser 的解析方法,让他能解析跨越多行的匹配处理代码,这种修改比较麻烦,我们暂时放弃。一种做法是将上面多行代码全部放入一行,但这样会导致一行内容长度过长,使得模板文件很难看,目前我们的解决办法是用一个函数将这些代码封装起来,例如使用一个 Handle_string()函数来封装上面代码,于是上面部分修改如下:

[a-zA-Z]+  {handle_string();}
%%
void handle_string() {f (state != LOOKUP) {add_word(state, yytext);}else {switch(lookup_word(yytext)) {case VERB:  printf("%s: verb\n", yytext); break;case ADJ:   printf("%s: adjective\n", yytext); break;case ADV:   printf("%s: adverb\n", yytext); break;case NOUN:  printf("%s: noun\n", yytext); break;case PREP:  printf("%s: propsition\n", yytext); break;case PRON:  printf("%s: pronoun\n", yytext); break;case CONJ:  printf("%s: conjunction\n", yytext); break;default:printf("%s: don't recognize\n", yytext); break;}}
}

综上所述,GoLex 中 input.lex 的文本内容如下:

%{
#include<string.h>enum {LOOKUP = 0,VERB,ADJ,ADV,NOUN,PREP,PRON,CONJ
};int state;int add_word(int type, char* word);
int lookup_word(char* word);
void handle_string();
%}
%%
^verb {state = VERB;}
^adj {state = ADJ;}
^noun {state = NOUN;}
^prep {state = PREP;}
^pron {state = PRON;}
^conj {state = CONJ;}
(\n) {state = LOOKUP;}[a-zA-Z]+  {handle_string();}
%%
int main() {yylex();return 0;
}struct word {char* word_name;int word_type;struct word* next;
};struct word* word_list;extern void *malloc();int add_word(int type, char* word) {struct word* wp;if (lookup_word(word) != LOOKUP) {printf("!!! warning: word %s already defined\n", word);return 0;}wp = (struct word*)malloc(sizeof(struct word));wp->next = word_list;wp->word_name = (char*) malloc(strlen(word)+1);strcpy(wp->word_name, word);wp->word_type = type;word_list = wp;return 1;
}int lookup_word(char* word) {struct word* wp = word_list;for(; wp; wp = wp->next) {if (strcmp(wp->word_name, word)==0) {return wp->word_type;}}return LOOKUP;
}void handle_string() {if (state != LOOKUP) {add_word(state, yytext);}else {switch(lookup_word(yytext)) {case VERB:  printf("%s: verb\n", yytext); break;case ADJ:   printf("%s: adjective\n", yytext); break;case ADV:   printf("%s: adverb\n", yytext); break;case NOUN:  printf("%s: noun\n", yytext); break;case PREP:  printf("%s: propsition\n", yytext); break;case PRON:  printf("%s: pronoun\n", yytext); break;case CONJ:  printf("%s: conjunction\n", yytext); break;default:printf("%s: don't recognize\n", yytext); break;}}
}

注意上面代码增加了一句#include<string.h>,这是因为我们在代码中使用了 malloc 函数,这个函数声明在 string.h 头文件中。完成上面修改后运行 GoLex,将生成的 lex.yy.c 里面的内容拷贝到 CLex 中的 main.c中,编译运行后结果如下:
请添加图片描述
从上图执行效果可以看到,这次我们用 flex 实现的比较复杂功能,在 GoLex 上稍微修改也能实现同等功能。更多调试演示请在 B 站搜索 coding 迪斯尼。代码下载:
链接: https://pan.baidu.com/s/1Yg_PXPhWD4RlK16Fk7O0ig 提取码: auhs
github:
https://github.com/wycl16514/golang-implement-compiler-flex.git

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/175457.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】23、内存超详细介绍

文章目录 零、资料一、内存映射1.1 TLB1.2 多级页表1.3 大页 二、虚拟内存空间分布2.1 用户空间的段2.2 内存分配和回收2.2.1 小对象2.2.2 释放 三、查看内存使用情况3.1 Buffer 和 Cache3.1.1 proc 文件系统3.1.2 案例3.1.2.1 场景 1&#xff1a;磁盘和文件写案例3.1.2.2 场景…

【数据结构】顺序表---C语言版

【数据结构】顺序表 前言&#xff1a;一、线性表二、顺序表1.顺序表的概念及结构&#xff1a;2.顺序表的分类&#xff1a;3.顺序表缺陷&#xff1a; 三、顺序表的代码实现&#xff1a;1.头文件&#xff1a;2.函数文件&#xff1a;3.测试文件&#xff1a; 四、顺序表的相关OJ题&…

怎么给数据库某个字段建立一个前缀索引

说明&#xff1a;SQL调优中重要的一个环节是建立索引&#xff0c;其中有一条是字段值过长字段应该建立前缀索引&#xff0c;即根据字段值的前几位建立索引&#xff0c;像数据库中的密码字段、UUID字段。 因为其随机性&#xff0c;其实根据前几位就可以锁定某一条记录了。前缀索…

(附源码)SSM+成都大学体育场馆预约系统 计算机毕设37087

摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&#xff0c;使信息存…

Vatee万腾的数字探险之旅:vatee科技创新的新纪元

在数字时代的潮流中&#xff0c;Vatee万腾以其独特的数字探险之旅引领着科技创新的新纪元。这不仅是一次技术的进步&#xff0c;更是一场数字领域的探险&#xff0c;让我们一同探索Vatee在科技创新中的前沿地带。 Vatee万腾的数字探险起源于对未知的渴望和对创新的不懈追求。在…

【PUSDN】WebStorm中报错Switch language version to React JSX

简述 WebStorm中报错Switch language version to React JSX 可能本页面的写法是其他语法。所以可以不用管。 测试项目&#xff1a;ant design vue pro 前情提示 系统&#xff1a; 一说 同步更新最新版、完整版请移步PUSDN Powered By PUSDN - 平行宇宙软件开发者网www.pusdn…

《opencv实用探索·三》opencv Mat与数组互转

1、利用Mat来存储数据&#xff0c;避免使用数组等操作 //创建一个两行一列的矩阵cv::Mat mean (cv::Mat_<float>(2, 1) << 0.77, 0.33);std::cout() << mean << std::endl;float a mean.at<float>(0, 0); //0.77float b mean.at<float&…

使用vscode中编写c语言——无法打开 源 文件 “stdlib.h“C/C++(1696)问题

出现这个问题原因如下&#xff1a; 1、没有下载编辑器或者是没有配置好该编辑器的环境变量。 可以通过如下方法检查是否安装并配置好编辑器&#xff1a;打开终端&#xff1a;按winR cmd&#xff0c;然后输入gcc-v&#xff0c;查看是否有mingw64编辑器&#xff0c;如下图是已经…

TUP通信——与多个客户端同时通信

一&#xff0c;概括&#xff1a;可以通过多线程思想每加一个客户端由线程池中的主线程交给一个子线程管理 二&#xff0c;案例 &#xff08;1&#xff09;&#xff0c;线程池 &#xff08;2&#xff09;&#xff0c;服务端 &#xff08;3&#xff09;&#xff0c;客户端

【Qt】QStackedWidget、QRadioButton、QPushButton及布局实现程序首页自动展示功能

效果 在程序启动后&#xff0c;有时不会进入到工作页面&#xff0c;会进入到产品展示页面。 动画如下&#xff1a; 首页展示 页面操作 当不点击时&#xff0c;一秒自动刷新一次&#xff1b;当点击时&#xff0c;会自动跳转到对应页面&#xff1b;点击上一页、下一页、及跳转页…

03、K-means聚类实现步骤与基于K-means聚类的图像压缩

03、K-means聚类实现步骤与基于K-means聚类的图像压缩&#xff08;1&#xff09; K-means聚类实现步骤 开始学习机器学习啦&#xff0c;已经把吴恩达的课全部刷完了&#xff0c;现在开始熟悉一下复现代码。对这个手写数字实部比较感兴趣&#xff0c;作为入门的素材非常合适。…

理解Android无埋点技术

首先什么是无埋点呢&#xff0c;其实所谓无埋点就是开发者无需再对追踪点进行埋码&#xff0c;而是脱离代码&#xff0c;只需面对应用界面圈圈点点即可追加随时生效的事件数据点。 无埋点的好处 其实无埋点并不是完全不用写代码&#xff0c;而是尽可能的少写代码。开发者将SDK集…

零基础学编程轻松学编程,分享一款中文编程工具,编程构件简介

零基础学编程轻松学编程&#xff0c;分享一款中文编程工具&#xff0c;编程构件简介 中文编程开发语言工具编辑区界面截图如上图。 给大家分享一款中文编程工具 零基础轻松学编程&#xff0c;不需英语基础&#xff0c;编程工具可下载。 这款工具不但可以连接部分硬件&#…

数据库应用:Ubuntu 20.04 安装MongoDB

目录 一、理论 1.MongoDB 二、实验 1.Ubuntu 20.04 安装MongoDB 三、问题 1.Ubuntu Linux的apt 包管理器更新安装软件报错 2.Ubuntu20.04安装vim报错 3.Ubuntu20.04如何更换阿里源 4.Ubuntu22.04如何更换阿里源 一、理论 1.MongoDB &#xff08;1&#xff09;概念 …

6、Qt使用Log4Qt日志

一、知识点 1、Log4Qt有三部分 logger&#xff1a;负责捕获日志信息 layout&#xff1a;负责使用不同的样式输出日志 appender&#xff1a;负责输出信息到不同的目的地&#xff0c;比如数据库、文件、控制台等等 2、 日志级别如下&#xff0c;从上往下依次递增 ALL&#xff1a;…

css之svg 制作圆及旋转

1.代码 <template><div class"loading-box"><div class"circle-container"><svg width"75" height"75" class"move-left-to-right"><circle cx"37.5" cy"37.5" r"26&…

高端影像仪:打破微小产品测量局限

在现代工业生产中&#xff0c;影像仪以CCD数位影像为基石&#xff0c;将计算机屏幕测量技术与空间几何运算的能力融为一体&#xff0c;可以用于测量微小产品的各种尺寸和形状&#xff0c;为生产过程中的质量控制提供重要的参考依据。 影像仪产品内置高精度光学电动双倍镜头&am…

什么是动态住宅IP?它有什么用途?

随着网络的迅速发展&#xff0c;许多人对代理IP已经有了比较深刻的认识&#xff0c;并且广泛地运用到了各自的业务中&#xff0c;尤其在跨境的相关业务中表现尤其卓越。对于代理IP的类别&#xff0c;也需要根据自己的业务类型具体选择最合适的&#xff0c;那么今天IPFoxy就给大…

网页设计--第5次课后作业

1、快速学习JavaScript的基本知识第1-10章 JavaScript入门 - 绿叶学习网 2、使用所学的知识完成以下练习。需求如下3个&#xff1a; 1&#xff09;点亮灯泡 2&#xff09;将所有的div标签的标签体内容后面加上&#xff1a; very good 3&#xff09;使所有的复选框呈现被选…

【javaWeb】HTTP协议

HTTP (全称为 “超文本传输协议”) 是一种应用非常广泛的应用层协议 HTTP 是一个文本格式的协议. 可以通过 Chrome 开发者工具或者 Fiddler 抓包, 分析 HTTP 请求/响应的细节. 上图是通过Fiddler对访问百度搜索页时抓取的一个http协议的包。 观察抓包结果,可以看到,当前 http…