【编译原理】学习笔记1 词法分析
进行词法分析,打印分析结果。
编译器是一个程序:输入字符串,输出目标代码。
词法分析: 读入源码字节,将其组成有意义的TOKEN流。
语法分析: 根据TOKEN流构建树形的中间表示。
语义分析: 检查是否和语言的定义一致,并且会收集信息放入语法树中以便在随后的代码生成过程中使用。
中间代码生成: 根据语法树生成低级的中间表示。
代码优化: 优化中间代码。
在词法分析中涉及到的概念:
DFA:有穷、确定状态机。
NFA:有穷、不确定状态机。
词法单元:表示某种词法单位的抽象符号(比如关键字、字符串),由一个词法单元名称和一个可选的属性值组成。
模式:词法单元的词素可能具有的形式。
词素:源代码中的一个字符串序列。
字母表:有限的符号集合。
串:由字符表中的字符组成的有穷序列。
语言:字符表上一个任意可数的串集合,其上最基本的操作有并、连接、闭包。
接受/终结状态:找到一个词素。
初始状态:在读入任何输入符号之前,状态转换图总是位于他的开始状态。
可行前缀:
有效项集:
LR(0)状态机:
SLR:
词法分析器会将所有的字符识别成一个个的 token
GNU:类UNIX操作系统
Flex:词法分析器工具,利用正则表达式来匹配字符串的C语言代码。
自动化工具,可以按照定义好的规则自动生成一个C函数yylex(),也称为扫描器(Scanner)。这个C函数yylex()把文本串作为输入,按照定义好的规则分析文本串中的字符,找到符合规则的一些字符序列后,就执行在规则中定义好的动作。
引用自词法分析flex & 语法分析bison
词法单元:
1)关键字。一个关键字的类型就是该关键字本身。
2)运算符。它可以表示单个运算符,也可以像表 1-1 中的 comparison 那样,表示一类运算符。
3)标识符。一个表示所有标识符的词法单元。
4)常量。一个或多个表示常量的词法单元,比如数字和字面值字符串。
5)界符。每一个标点符号有一个词法单元,比如左右括号、逗号和分号。
而词法分析程序生成工具所基于的理论基础,是计算理论中最入门的内容:正则表达式(Regular Expression)和有限状态自动机(Finite State Automata)
一个正则表达式由特定字符串构成,或者由其它正则表达式通过以下三种运算得到:
- 并运算(Union):两个正则表达式 r 和 s 的并记作 r | s,意为 r 或 s 都可以被接受。
- 连接运算(Concatenation):两个正则表达式 r 和 s 的连接记作 rs,意为 r 之后紧 跟 s 才可以被接受。
- Kleene 闭包(Kleene Closure):一个正则表达式 r 的 Kleene 闭包记作 r*,它表示: | r | rr | rrr | …。
而我们所需要做的,仅仅是为工具提供作为词法规范的正则表达式
使用 Flex 创建词法分析程序
GNU Flex 在 Linux 下的安装:
在Ubuntu系统直接安装flex的命令为:sudo apt-get install flex
在安装的时候遇到锁正由进程 3447(unattended-upgr)持有的提示,查了一下是linux锁互斥,尝试终端输入命令:
sudo rm /var/cache/apt/archives/lock
sudo rm /var/lib/dpkg/lock-frontend
然后重新输入安装命令就好了。
桌面进入终端,新建compile文件夹以及lexical.l的描述文件:
建完如图:
完成flex源代码文件的编写,主要包含三个部分:
{definitions}
%%
{rules}
%%
{user subroutines}
%{
C语言声明,一般声明全局变量和函数,以及头文件包含,会复制进lex.yy.c中
%}
定义正则表达式的名字,可以在规则段中使用
%%
规则段,每一行都是一条规则,每一条规则由匹配模式和动作组成。每当一个模式被匹配到,后面的动作被执行!模式一般使用正则表达式书写,动作部分为C代码
%%
用户自定义过程,直接复制到lex.yy.c末尾
flex提供的2个全局变量:
yytext:刚刚匹配到的字符串
yyleng:刚刚匹配到的字符串的长度
完成flex代码并编译:
输入gcc lex.yy.c -lfl -o scanner:报错了呜呜呜
修改了一下,要注意占位符和空格:
对test.cmm进行词法分析(事先写好test.cmm的内容如下)
出结果了,跟预期的不一样,应该不会报错啊啊啊啊啊
应该是词法分析的规则有点问题,修改了一下,把识别空格的规则放到最前面,这样遇到空格不会报错。识别int整型也有问题,修改修改。
修改之后词法分析输出如下:
下面要进行报错处理,如果有词法错误就不输出语法树,只输出每一行的报错。每一行最多有一个错误。
思路如下:用c语言写需要用动态数组来存储输出结果,如果有词法错误就把之前存的词法分析结果清空,只存入报错信息,最后输出所有的错误信息。
int b;//输入字符串的长度 scanf(“%d\n”, &b); char* a = NULL; a =
(char*)malloc(sizeof(char)*(b+1));//在堆区创建字符串的存储空间
fgets(a,b+1,
stdin);//输入字符串 fputs(a, stdout);//输出字符串 free(a);//释放在堆区的内存
不好用 下一个方法
用一维指针数组
声明:
char *name[4] = { “Justinian”, “Momo”, “Becky”, “Bush” };
又报错,这是啥错误没见过
查阅资料
什么是段错误
core dump又叫核心转储, 当程序运行过程中发生异常, 程序异常退出时, 由操作系统把程序当前的内存状况存储在一个core文件中, 叫core dump.
(linux中如果内存越界会收到SIGSEGV信号,然后就会core
dump)。产生段错误的原因大致上有三类:访问不存在的内存地址、访问系统保护的内存地址和访问只读的内存地址。
什么是段错误
使用ulimit -a命令查看core文件大小限制,这个兄弟怎么是0
????修改完后还是段错误
经查阅发现段错误有可能有两个原因:栈溢出和指针问题。
感觉我的指针数组是乱写的。
linux终端命令:
新建文件夹:mkdir 创建文件夹
新建文件:touch filename
新建文件并进入编辑模式:vi filenmae
切换到插入模式(Insert mode):输入字母i
插入模式下只能一直输入文字,如果输错了字,将该字删除,要先按一下ESC键转到命令行模式(command mode)再删除文字。
在命令行模式(command mode)下,按一下**:**冒号键进入「Last line mode」
保存文件 : w filename
(输入 「w filename」将文章以指定的文件名filename保存)
存盘并退出:wq
不存盘强制退出:q!
需要自行完成包括词法规则等在内的 Flex 代码。假设这部分写好的代码名为 lexical.l 。随后,使用 Flex 对该代码进行编译:
flex lexical.l
编译好的结果会保存在当前目录下的 lex.yy.c 文件中(本质上是一份C语言的源代码 yylex()就在里面,yylex()读取输入文件中的一个词法单元 )。
再为它编写一个 main 函数:
1 int main(int argc, char** argv) {
2 if (argc > 1) {
3 if (!(yyin = fopen(argv[1], "r"))) {
4 perror(argv[1]);
5 return 1;
6 }
7 }
8 while (yylex() != 0);
9 return 0;
10 }
可以直接将main函数放入lexical.l 中的用户自定义代码部分,甚至可以不写 main 函数,因为Flex 会自动给你配一个。
将输出程序命名为 scanner:
gcc main.c lex.yy.c -lfl -o scanner
例如,想要对一个测试文件 test.cmm 进行词法分析,只需要在命令行输入: ./scanner test.cmm
编写 Flex 源代码
Flex 源代码文件包括三个部分,由“%%”隔开。
第一部分为定义部分,格式一般为:
name definition
其中 name 是名字,definition 是任意的正则表达式。
digit [0-9]
letter [_a-zA-Z]
第二部分为规则部分,由正则表达式和相应的响应函数组成,格式一般为:
pattern {action}
其中 pattern 为正则表达式,其书写规则与前面的定义部分的正则表达式相同。而 action则为将要进行的具体操作,这些操作可以用一段 C 代码表示。
Flex 将按照这部分给出的内容依次尝试每一个规则,尽可能匹配最长的输入串。如果有些内容不匹配任何规则,Flex 默认只将其拷贝到标准输出。
第三部分为用户自定义代码部分,这部分代码会被原封不动地拷贝到lex.yy.c 中。可以在前面的定义部分(即 Flex 源代码文件的第一部分)之前使用“%{”和“%}”符号将 要声明的内容添加进去。被“%{”和“%}”所包围的内容也会一并拷贝到 lex.yy.c 的最前面。
在 Flex 源代码中书写正则表达式
- 符号 “.” 匹配除换行符 “\n” 之外的任何一个字符。
- 符号 “[” 和 “]” 共同匹配一个字符类,即方括号之内只要有一个字符被匹配上了, 那么方括号括起来的整个表达式都被匹配上了。如果方括号中的第一个字符是“^”,则表示对这个字符类取补。
- 符号“^”用在方括号之外则会匹配一行的开头,符号“$”用于匹配一行的结尾,符号 “<>”用于匹配文件的结尾。
- 符号“{”和“}”含义:如果花括号之内包含了一个或者两个数字,则代表花括号之前的那个表达式需要出现的次数。例如,A{5}会匹配 AAAAA,A{1,3}则会匹配 A、 AA或者AAA。如果花括号之内是一个在 Flex 源代码的定义部分定义过的名字,则表示那个名字对应的正则表达式。例如,在定义部分如果定义了 letter 为[a-zA-Z],则{letter}{1,3} 表示连续的一至三个英文字母。
- 符号 “*” 为 Kleene 闭包操作,匹配零个或者多个表达式。例如{letter}*表示零个或 者多个英文字母。
- 符号 “+” 为正闭包操作,匹配一个或者多个表达式。例如{letter}+表示一个或者多 个英文字母。
- 符号“?”匹配零个或者一个表达式。例如表达式-?[0-9]+表示前面带一个可选的负号的数字串。
无论是*、+还是?,它们都只对其最邻近的那个字符生效。例如 abc+表示 ab 后面跟一个或多个 c,而不表示一个或者多个
abc。如果你要匹配后者,则需要使用小括号“(” 和“)” 将这几个字符括起来:(abc)+。
- 符号“|”为选择操作,匹配其之前或之后的任一表达式。例如,faith | hope | charity 表 示这三个串中的任何一个。
- 符号“\”用于表示各种转义字符,与 C 语言字符串里“\”的用法类似。例如,“\n”表示 换行,“\t”表示制表符,“*” 表示星号,“\” 代表字符 “\” 等。
- 符号 ““”(英文引号)将逐字匹配被引起来的内容(即无视各种特殊符号及转义字符。例如,表达式”…"就表示三个点而不表示三个除换行符以外的任意字符。
- 符号 “/” 会查看输入字符的上下文,例如,x/y 识别 x 仅当在输入文件中 x 之后紧跟着 y,0/1 可以匹配输入串 01 中的 0 但不匹配输入串 02 中的 0。
- 任何不属于上面介绍过的有特殊含义的字符在正则表达式中都仅匹配该字符本身。
例子练习Flex源代码里正则表达式的书写:
- 带一个可选的正号或者负号的数字串:
可以这样写:[+-]?[0-9]+
- 带一个可选的正号或者负号以及一个可选的小数点的数字串,表示起来要困难一些, 可以考虑下面几种写法:
a)[+-]?[0-9.]+
会匹配太多额外的模式,像 1.2.3.4;
b)[+-]?[0-9]+\.?[0-9]+
会漏掉某些模式,像 12.或者.12;
c)[+-]?[0-9]*\.?[0-9]+
会漏掉 12.;
d)[+-]?[0-9]+\.?[0-9]*
会漏掉.12;
e)[+-]?[0-9]*\.?[0-9]*
会多匹配空串或者只有一个小数点的串;
f) 正确的写法是:[+-]?([0-9]*\.?[0-9]+|[0-9]+\.)
(还没搞懂为什么)
由词法解析器生成的符号,也叫 token,是终结符。终结符是最小表义单位,无法继续进行拆解和解析。
规则左侧定义的符号,是非终结符。非终结符需要进行语法解析,最终由终结符构成其表示形式。
NFA 和 DFA,FA 表示 Finite Automata(有穷状态机),即根据不同的输入来转换内部状态,其内部状态是有限个数的。而 NFA 和 DFA 分别代表 有穷不确定状态机 和 有穷确定状态机。运用子集构造法可以将 NFA 转换为 DFA,让构造得到的 DFA 的每个状态对应于 NFA 的一个状态的集合。
词法分析器(lexer)生成终结符,而语法分析器(parser)则利用自顶向下或自底向上的方法,利用文法中定义的终结符和非终结符,将输入信息转换为 AST(抽象语法树)。
引用自知乎回答
词法分析程序的输入是源程序,输出是一个个单词符号的二元组,它的任务是从左至右逐个字符地对源程序进行扫描,产生一个个的单词符号,把作为字符串的源程序改造成为单词符号串的中间程序。
【编译原理】学习笔记1 词法分析相关推荐
- [编译原理学习笔记2-2] 程序语言的语法描述
[编译原理学习笔记2-2] 程序语言的语法描述 文章目录 [编译原理学习笔记2-2] 程序语言的语法描述 [2.3.1] 上下文无关文法 [2.3.2] 语法分析树与二义性 [2.3.3] 形式语言鸟 ...
- 编译原理学习笔记20——符号表
编译原理学习笔记20--符号表 20.1 符号表的组织与操作 20.2 符号表的内容 20.3 利用符号表分析名字的作用域 20.1 符号表的组织与操作 符号表 符号表的作用与组织 符号表的整理和查找 ...
- 编译原理学习笔记2——高级程序设计语言概述
编译原理学习笔记2--高级程序设计语言概述 2.1常用的高级程序设计语言 2.2程序设计语言的定义 2.2.1语法 2.2.1语法 2.2.3程序语言的基本功能和层次机构 2.2.4程序语言成分的逻辑 ...
- 编译原理学习笔记 5.1 翻译文法和语法制导翻译
前言 参考课上PPT内容. 该学习笔记目前仅打算个人使用. 后续会进一步整理,包括添加笔记内容,标明参考资料. 更新中... 跳过目录 目录 导言 一.翻译文法和语法制导翻译 输入文法 翻译文法 活动 ...
- 编译原理学习笔记一(待续)
这几天忙着学英语,同时在学习编译原理,对这门课很感兴趣,已经制作了词法分析器,同时还在补充这个分析器的功能,也准备着手开始写语法分析器,看到最后能不能连在一起,我想如果能够将整套编译器的流程跑下来真的 ...
- 编译原理学习笔记 3.3 正则文法的状态图
前言 参考东南大学廖力老师的编译原理教程和课上PPT内容. 该学习笔记目前仅打算个人使用. 后续会进一步整理,包括添加笔记内容,标明参考资料. 更新中... 跳过目录 目录 状态图的画法(根据文法画出 ...
- 编译原理学习笔记 6.2 符号表的组织与内容
前言 参考课上PPT内容. 该学习笔记目前仅打算个人使用. 后续会进一步整理,包括添加笔记内容,标明参考资料. 更新中... 跳过目录 目录 一.符号表的结构与内容 "名字"域 & ...
- 程序设计语言编译原理_编译原理学习笔记(二):高级程序设计语言
高级程序设计语言 一.语言概述 1.1 语法 v.s. 语义 程序本质上是一定字符集上的字符串 语法:一组规则,用它可以形成和产生一个合式(well-formed)的程序 定义了程序的形式结构 定义语 ...
- 编译原理学习笔记(一)
题目 已知一个不确定的有限自动机(NFA) (1)描述该NFA所识别语言的特点,列举两个可识别的串; (2)写出与该NFA等价的正规式; (3)将其确定化(即转换为与NFA等价的DFA); (4)使用 ...
- 【编译原理学习笔记】1:符号和符号串,文法和语言,文法的类型
符号和符号串 字母表(符号集):元素的非空有穷集合. 符号:字母表中的元素. 符号串:由字母表中的符号组成的任何有穷序列. 头:如符号串z=xy中的x. 尾:如符号串z=xy中的y. 固有头:符号串的 ...
最新文章
- 修改openwrt 编译 image的大小
- Android使用ConstraintLayout 加载RecyclerView数据显示不全
- Docker多阶段镜像构建Dockerfile脚本示例:构建nodejs前端项目
- Python 面向对象 --- 文件
- 这些年,NLP常见的预训练模型剖析
- Python计算组合数生成杨辉三角形
- Shell排序(改良的插入排序)
- 拆机芯片DIY一个STM32F401CCU6核心板
- 计算机综合应用技能,系统测评计算机综合应用技能期末作业题稿.doc
- java就业感言_JAVA就业班感言
- 三菱PLC CCD控制ST程序 本PLC程序中CCD控制用的ST结构化语言,程序都有注释
- jpa 清空数据库的写法
- 无线投影服务器连接投影仪,投影仪如何实现无线连接投影?
- 30行python代码实现微信自动陪女盆友聊天(itchat-uos + 无限制调用)
- Python matplotpy颜色表
- 公众号如何运营?我的经验总结
- Linux——linux脚本命令集合
- arctanx麦克劳林公式推导过程_多元正态分布的推导、n维球体积面积的计算
- WebService为什么不如RESTful API流行
- 鼠标右键失灵及间歇性失灵,解决方法
热门文章
- 《王者荣耀》推出2022年第一个英雄,或将争夺峡谷第一美男
- vue--elementUI
- OLED demura技术学习part2
- B02 - 049、查看Kafka集群的方式①:ZooInspector
- linux配置jdk rmp
- python stdout stderr 一起输出_stdout和stderr标准输出的区别
- 计算机网络实验rip协议,软件学院《计算机网络实验报告》5-RIP路由协议实验.pdf...
- 2022杭电多校(十)
- 5G电源能源行业的变化
- 为 Linux 做一把 USB “钥匙”