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

news2024/11/26 13:40:34

上一节我们运行了 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 := false

	for l.scanner.Scan() {
		l.ActualLineNo += 1
		l.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.coloradmin.cn/o/1259974.html

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

相关文章

基于单片机病房呼叫程序和仿真

如果学弟学妹们在毕设方面有任何问题&#xff0c;随时可以私信我咨询哦&#xff0c;有问必答&#xff01;学长专注于单片机相关的知识&#xff0c;可以解决单片机设计、嵌入式系统、编程和硬件等方面的难题。 愿毕业生有力&#xff0c;陪迷茫着前行&#xff01; 一、系统方案 1…

程序员必读之软件架构书摘

程序员必读之软件架构书摘 什么是架构 "架构"作为名词的一种理解&#xff1a; 从产品整体考虑&#xff0c;采用一定的结构&#xff0c;将产品分解为一系列组件、模块和交互。 比如考虑处理软件的安全、配置、错误处理等横切关注点的基础设施服务。 "架构&q…

广联达linkworks 文件上传漏洞复现

0x01 产品简介 广联达 LinkWorks&#xff08;也称为 GlinkLink 或 GTP-LinkWorks&#xff09;是广联达公司&#xff08;Glodon&#xff09;开发的一种BIM&#xff08;建筑信息模型&#xff09;协同平台。广联达是中国领先的数字建造技术提供商之一&#xff0c;专注于为建筑、工…

新手用什么工具制作电子画册?新分享

随着数字化时代的到来&#xff0c;电子画册已成为企业宣传、展示产品的重要手段。对于新手来说&#xff0c;选择一款合适的工具是关键。今天&#xff0c;为大家推荐一款适合新手制作的电子画册工具&#xff0c;让你轻松制作出精美画册。 工具推荐&#xff1a;FLBOOK在线制作电子…

关于mybatis插入返回主键id和SpringBoot事务注解自调用演示

文章目录 一. 插入返回任意规则主键ID二. SpringBoot自调用事务2.1 场景12.2 场景2 自调用结论总结 一. 插入返回任意规则主键ID 实体对象 TableName("bank") Data public class Entity {TableId("id")Integer id;TableField("money")Integer …

[原创][1]探究C#多线程开发细节-“Thread类的简单使用“

[简介] 常用网名: 猪头三 出生日期: 1981.XX.XXQQ: 643439947 个人网站: 80x86汇编小站 https://www.x86asm.org 编程生涯: 2001年~至今[共22年] 职业生涯: 20年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、Delphi…

代码随想录算法训练营第五十七天|739. 每日温度、496.下一个更大元素 I

LeetCode 739. 每日温度 题目链接&#xff1a;739. 每日温度 - 力扣&#xff08;LeetCode&#xff09; 单调栈开始&#xff0c;为什么要用栈&#xff0c;因为栈是先入后出&#xff0c;当我们遍历从前往后的时候&#xff0c;每次遍历的元素都是添加至栈尾&#xff0c;方便我们进…

勒索解密后oracle无法启动故障处理----惜分飞

客户linux平台被勒索病毒加密,其中有oracle数据库.客户联系黑客进行解密【勒索解密oracle失败】,但是数据库无法正常启动,dbv检查数据库文件报错 [oraclehisdb ~]$ dbv filesystem01.dbf DBVERIFY: Release 11.2.0.1.0 - Production on 星期一 11月 27 21:49:17 2023 Copyrig…

小型工厂MES选型指南

在制造业中&#xff0c;制造执行系统&#xff08;MES&#xff09;是一种重要的工具&#xff0c;可以帮助工厂实现更高效的生产和更精细的管理。对于小型工厂而言&#xff0c;选择适合的MES系统更是至关重要。如何从多方面评估MES系统的功能和性能&#xff0c;选择最适合的MES系…

Python continue的用法详解与转义字符及用法

Python continue的用法详解 continue 的功能和 break 有点类似&#xff0c;区别是 continue 只是忽略当次循环的剩下语句&#xff0c;接着开始下一次循环&#xff0c;并不会中止循环&#xff1b;而 break 则是完全中止循环本身。 如下程序示范了continue 的用法&#xff1a; # …

OpenCV快速入门:移动物体检测和目标跟踪

文章目录 前言一、移动物体检测和目标跟踪简介1.1 移动物体检测的基本概念1.2 移动物体检测算法的类型1.3 目标跟踪的基本概念1.4 目标跟踪算法的类型 二、差值法检测移动物体2.1 差值法原理2.2 差值法公式2.3 代码实现2.3.1 视频或摄像头检测移动物体2.3.2 随机动画生成的移动…

126. 单词接龙 II

126. 单词接龙 II 需要注意的是&#xff0c;由于要找最短路径&#xff0c;连接 dot 与 lot 之间的边就不可以被记录下来&#xff0c;同理连接 dog 与 log 之间的边也不可以被记录。这是因为经过它们的边一定不会是最短路径。因此在广度优先遍历的时候&#xff0c;需要记录的图…

【STM32】GPIO输入

1 GPIO输出 1.1 按键简介 按键&#xff1a;常见的输入设备&#xff0c;按下导通&#xff0c;松手断开 按键抖动&#xff1a;由于按键内部使用的是机械式弹簧片来进行通断的&#xff0c;所以在按下和松手的瞬间会伴随有一连串的抖动 1.2 传感器模块简介 传感器模块&#xff…

大数据Doris(三十):删除数据(Delete)

文章目录 删除数据(Delete) 一、​​​​​​​DELETE FROM Statement(条件删除)

nodejs+vue+elementui学生竞赛管理系统65o97

高校人才培养计划的重要组成部分&#xff0c;是实现人才培养目标、培养学生体育 能力与创新思维、学生竟赛管理系统检验学生综合素质与实践能力的重要手段与综合性实践教学环节。而我所在学院多采用半手工管理学生竟赛的方式&#xff0c;所以有必要开发学生竟赛管理系统来对学生…

livox 半固体激光雷达 gazebo 仿真 | 安装与验证

livox 半固体激光雷达 gazebo 仿真 | 安装与验证 livox 半固体激光雷达 gazebo 仿真 | 安装与验证livox 介绍安装验证 livox 半固体激光雷达 gazebo 仿真 | 安装与验证 livox 介绍 览沃科技有限公司&#xff08;Livox&#xff09;成立于2016年。为了革新激光雷达行业&#xf…

CANdelaStudio 中 Bese Variant 和 Variant区别

关于 Bese Variant &#xff0c;其在 CDDT 和 CDD 文件中都存在&#xff0c;有且只有一个 主要包含三部分&#xff0c;重点只关注 DIDs 和 Supported Diagnostic Classes 而在 CDD 文件中&#xff0c;除了 Bese Variant 外&#xff0c;还有一个 Variant “Variant” 这个概…

C# 使用PanGu分词

写在前面 这是官方介绍&#xff1a;盘古分词是一个中英文分词组件。作者eaglet 曾经开发过KTDictSeg 中文分词组件&#xff0c;拥有大量用户。作者基于之前分词组件的开发经验&#xff0c;结合最新的开发技术重新编写了盘古分词组件。 盘古分词组件需要配合其字典文件使用&am…

Cobalt Strike的各类反向上线操作

前言 Cobalt Strike 使用 GUI 框架 SWING&#xff08;一种java GUI的库&#xff09;开发&#xff0c;攻击者可通过CS木马在 beacon 元数据中注入恶意 HTML 标签&#xff0c;使得Cobalt Strike对其进行解析并且加载恶意代码&#xff08;类似XSS攻击&#xff09;&#xff0c;从而…

Nginx Openresty通过Lua+Redis 实现动态封禁IP

需求 为了封禁某些爬虫或者恶意用户对服务器的请求&#xff0c;我们需要建立一个动态的 IP 黑名单。对于黑名单中的 IP &#xff0c;我们将拒绝提供服务。并且可以设置封禁失效时间 环境准备 linux version: centos7 / ubuntu 等 redis version: 5.0.5 nginx version: nginx…