热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

编译原理c语言词法分析器,用C语言实现一个真正的词法分析器

词法分析,是编译器的第一个模块,也是最简单的模块。最简单,指的是相对于编译器这种大型程序而言,与一般的代码相比还是有点复杂的

词法分析,是编译器的第一个模块,也是最简单的模块。

最简单,指的是相对于编译器这种大型程序而言,与一般的代码相比还是有点复杂的。

关于词法分析的简介,可以看之前的文章:

词法分析器的简单思路

按照通常的C代码惯例,前缀暂时设置为scf,Simple Compiler Framework,简单编译器框架。

它所需的基本数据结构,就是动态字符串和双向链表,前两篇文章已经做了简单的代码介绍。

用C语言写个动态字符串

用C语言实现Linux风格的双向链表

首先,需要定义一个枚举类型,说明词法分析要支持的单词类型,即各种运算符、常量、标识符。

因为是从C和C++中抽取了容易实现的那部分语法,单词类型还是很多的,见如下几张图片:

第一张图,是各种运算符的类型定义,与流行的编程语言基本一样。

7de26fbd6d07441dd27c666180caf37e.png各种运算符

第二张,大小括号、分号、逗号、冒号等语法标示符号,箭头、点号等运算符。

箭头->,一般表示指针。点号,表示取类的成员。

三个点号,表示函数的动态参数,例如printf(const char* fmt, ...)在词法分析时就会用到。

空格,space,在词法分析时作为分隔符之一。它是不需要传递到语法分析阶段的,用完之后需要忽略掉。

EOF,表示源代码文件的结尾,fgetc()之类的函数在文件结束时会返回这个值。把它也作为一个单词,用于提示词法分析过程的结束。

07686aefbb7cdb043ac5a74b000099f6.png括号之类的也算运算符

第三张,主要是用于代码流程控制的关键字。

ced591f3503b8a99b22baf26ea9c9878.png流程控制的关键字

第四张,是用于类型定义的关键字。

bd9adad9cf81c01876204a4c08c3b3d0.png数据类型的关键字

第五张,也是这个枚举的最后一部分,是常量和标识符。

de60ba4da11e805ae3496fc6c85b542c.png其他

定义完了这个枚举,就可以定义单词的数据结构,如下图:

30e0ec7cb415255485706ef54f1da59d.png

list,用于把它挂载到词法分析器的链表上,按照先进先出顺序(FIFO),以备后续的语法分析时读取。

type,填写为上面那个枚举的其中之一,用于表示这个单词的类型。

data,用于存储常量的值,可以是常量数字或者常量字符串。

text,用于存储单词的原始文本,即这个词的源代码。

file、line、pos,用于存储单词所在的源码文件名,行号,行内的位置,用于打印错误信息。

接下来定义几个与单词相关的函数,alloc()、clone()、free(),等等。

042b713c4fbc2cd910b031d6b02a2517.png

然后在C文件里实现这三个函数。

ac0ec68c32ac9d24a32735c350c3142f.png

86785894511c6aa831e83932bb290f8f.png

c4f65fbbcea3c2cb71171ac053695c4e.png

最后,定义词法分析器(lexer)的数据结构,scf_lex_t。

ea1f7c78be8bd77fc6a5d577931a81ed.png

scf_lex_key_word_t,用于定义关键字的源码字符串,和关键字对应的单词类型。

在一门编程语言中,关键字是固定的。

如果在词法分析时获得的字符串,能在关键字列表里查到,那么它就是个关键字。

关键字列表,就是scf_lex_key_word_t类型的数组。

scf_lex_escape_char_t,用于转义字符,一般是\r \n \t \0这4个,以\表示接下来的一个字符是转义字符。

如果能在转义字符列表里查到,要把这两个字符转化为对应的转义ascii码。例如'\n'要转化为一个字节的整数10。

转义字符列表,就是scf_lex_escape_char_t类型的数组。

scf_lex_error_t,用于存储错误信息。因为不一定发现一个错误就立即终止分析(打印错误信息),有可能积累到一定的数量之后再一起打印出来,所以它用于保存错误信息。

scf_lex_char_t,用于表示从源码文件里读取的一个字符。

词法分析,是一个字符一个字符的读取。

如果发现当前字符是下一个单词的,那么就要把它放回去,等分析下一个单词时再把它取出来,所以需要一个链表挂载暂时被放回的单词。

不能真把它写回源码文件再读出来,fgetc()之后文件指针已经变了,再写回去会覆盖掉下一个字符的。把它挂载一个链表上,优先从链表读,链表为空的时候再读源文件,是比较好的处理方法。

从当前单词的末尾字符多查看一个字符(即下一个单词的第一个字符),才能确定当前单词的终止位置,这叫LL1下降分析。这个多查看的字符,也叫终止符。

(这里我不是故意黑龙书,而是就这么点事,它居然真写的云山雾罩,让人一头雾水,读不下去。)

854de2265178cd365404a84de814db7a.png

scf_lex_t,词法分析器的数据结构,它有open()、close()、push_word()、pop_word()这4个函数。

其中push_word()和pop_word()是给语法分析器(parser)用的,语法分析时也存在LL1下降分析的场景,push_word()用于放回一个单词。

word_list_head,用于存储语法分析时这个临时被放回的单词,与词法分析的场景类似。

error_list_head,用于存储还没有打印的错误信息。

char_list_head,用于存储临时被放回的字符。

FILE* fp,源码文件的文件指针,从它这里读取字符。

nb_identities,标识符的个数。随着分析过程这个值是不断增加的,要根据它的值给获取的每一个标识符编不同的号码。

所有数据结构的定义说完了,接下来是代码实现。

scf_lex_open()的path是源码文件的路径,二级指针plex用于返回创建的词法分析器。

2204a0c706585bf41e152ab786f7dd3c.png

f932a55ea91208f6eba876bd32efb459.png

open、close、push_word,都比较简单,真正的词法分析在pop_word()函数里。

语法分析器(parser)在需要一个单词时,就调用词法分析器(lexer)的pop_word()函数,获取下一个单词。

之所以这么设计,而不是先完成整个源码文件的词法分析,是为了减少单词的库存,毕竟分析完的单词也是要占内存的。如果源码文件比较大,万一内存不够了就尴尬了,所以采取现使用现生产的JIT模式,暂时用不着的单词就让它在源码文件里存着。

在具体的词法分析之前,先准备关键字列表和转义字符列表。

b2d55bb4746c2df115dff8bc33fc7818.png

896308af5e619729a31dd173481ecf9c.png

更改这个关键字数组,就可以给词法分析器添加关键字。

转义字符暂时设置了4个,也可以更改这个数组添加。

下图是两个查找函数,用于查找关键字和转义字符。

93437fdcf5d3a4bddfb4f5599fe849ea.png

接下来,是词法分析的核心函数,scf_lex_pop_word(),它声明是:

int scf_lex_pop_word(scf_lext_t* lex, scf_lex_word_t** pword);

二级指针用于返回单词,返回值用于返回错误码,成功返回0,失败返回一个负数(一般是-1)。

5160c93e8cda46a62c82968062a22aad.pngpop_word1

它被语法分析器调用(也可以直接被main函数调用,只做词法分析),所以在单词链表不空的情况下(有被语法分析器放回的单词),优先返回链表头部的单词。

在单词链表空的情况下,调用_lex_pop_char()获取一个字符,开始新单词的分析。

如果是EOF,源码已经读完了,就返回一个EOF单词,获得这个词之后语法分析就会结束,同时词法分析也结束了。

如果是\n \r \t和空格,一律当作空格丢弃,但是\n时要更新行号,其他字符要更新列号。

_lex_space()函数会丢弃所有被当作空格的字符。它包含一个递归调用,用于处理连续的空格。如果当前字符不被当作空格,它就调用_lex_push_char(),把它放回到字符链表上。

0f69e2abc46fb210c3d5d13ab50740da.png

丢弃完空格之后,继续分析下一个单词。

接着图片pop_word1的代码926行,如下图的pop_word2。

cddaa00c6f9c6a5d701edf4e9233df17.pngpop_word2

它分析+ - * / %开头的运算符,例如+ += ++,- -= --,* *=,/ /=,% %=这些运算符。

图pop_word3主要是一些单个字符的特殊运算符,各种括号,逗号,冒号,分号。

185ce1fa42f21a1e0c584b7b69d09cc0.pngpop_word3

pop_word4,是以& | ! =开头的运算符,例如& && &=,| || |=,= ==。

c07085e34bfc632a9b6d57da945cd4d0.pngpop_word4

最后一张,图pop_word5。

a66536f85dae252bf4609b2e5d744b45.pngpop_word5

前几行是分析以>和 >> >= >>=,<<<<= <<=。

然后是点号,它可能是1个点或者3个点,分别表示类的成员和函数的可变参数。

以&#39;开始的是字符,以"开始的是字符串,它们都需要做转义字符分析。在词法分析时,它们都是起始字符和终止字符,用于标示字符和字符串的起始和终止。

转义字符在源代码里实际是2个字符,要转义成1个。

字符串,可以是包含或者不包含转义字符的多个字符。

0到9开头的是常量数字,即使是16进制以0x开头,8进制以0开头,开始也是0。

10进制的开头0到9都可能。

暂时不支持直接以点号开头的浮点数,所以0.5必须写成0.5,不能写成.5,否则报词法错误。

标识符,以下划线_或者a-z或者A-Z开头,中间可以包含数字。

总体结构说完了,里面的一些具体分析的函数,接下来几篇再说。

最后还是推荐下龙书,编译原理,因为编译器领域的著作比较少,另外两本虎书和鲸书更难读。

举报/反馈



推荐阅读
  • 好久没玩过C语言了,上一次还是在大二的时候。。。废话不多说,这里有一个C语言实现的学生选课系统代码,分享给大家,具体如下&# ... [详细]
  • 在ROS系统中,参数读写一般通过xml或者yaml格式的文件,其中yaml用得比较多。这是一种可读性高,轻量级的标记语言,简单好用。对于yaml文件,ros中用的较早版本的yaml- ... [详细]
  • 一、在androidStudio中实现tabs比较简单,新建项目就可以选择tabs模板进行创建,默认实现tabs功能:直接运行项目就可以看到效果:可以说非常简单,但是我们在实际开发 ... [详细]
  • 开发笔记:googletest安装与使用
    本文由编程笔记#小编为大家整理,主要介绍了googletest安装与使用相关的知识,希望对你有一定的参考价值。简介googletest是Google公司 ... [详细]
  • 为什么python是动态类型语言_Python 3.7.0 面向对象的动态类型语言
    代表Python开发社区和Python3.7发布团队,我们很高兴地宣布https:www.python.orgdownloadsreleasepython-370 ... [详细]
  • 文本生成图像简要回顾 text to image synthesis
    摘要       文本生成图像作为近几年的热门研究领域,其解决的问题是从一句描述性文本生成与之对应的图片。近一周来,我通过阅读了近几年发表于顶会的近10篇论文,做出本文中对该方向的 ... [详细]
  • ———Java培训、Android培训、iOS培训、.Net培训、期待与您交流!———一、引用计数器每个OC对象都有自己的引用计数器,表示“对象被引用 ... [详细]
  • 下载器,就是一种网络工具,从网络中接收自己想要的数据。下载器是一个网络客户端。它的下载流程无非就是客户端连接服务器端,然后发送资源下载请求 ... [详细]
  • 1、概念共享内存:共享内存是进程间通信中最简单的方式之一。共享内存允许两个或更多进程访问同一块内存,就如同malloc()函数向不同进程返回了指向同一个 ... [详细]
  • 1.trigraph三字符组据说是为了照顾旧式键盘,还是为了键盘坏了,或者是使用非ASCII字符编码的语言输入方便,设计了一些三元字符组& ... [详细]
  • SimpleDateFormat类所在java包位置:java.text.SimpleDateFormat。继承结构如下:复制代码java.lang. ... [详细]
  • 双向链表的基本操作C语言
    双向链表的基本操作C语言 ... [详细]
  • 操作系统基础知识(常用面试题)
    1.进程和线程有什么区别?进程(Process)是系统进行资源分配和调度的基本单位,线程(Thread)是CPU调度和分配 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
author-avatar
蓝天ab白云
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有