PL真有意思(二):程序设计语言语法
前言
虽然标题是程序语言的语法,但是讲的是对词法和语法的解析,其实关于这个前面那个写编译器系列的描述会更清楚,有关语言语法的部分应该是穿插在整个设计当中的,也看语言设计者的心情了
和英语汉语这些自然语言不一样,计算机语言必须是精确的,它们的语法和语义都必须保证没有歧义,这当然也让语法分析更加简单
所以对于编译器一项很重要的任务就是时别程序设计语言的结构规则,要完成这个目标就需要两个要求:
- 完成对语法规则的描述
- 确定给定程序是否按照这些规则构造起来,也就是符合语法规则
第一个要求主要由正则表达式和上下文无关文法来描述完成,而第二个要求就是由编译器来完成,也就是语法分析了
描述语法:正则表达式和上下文无关语法
对于词法,都可以用三种规则描述出来:
- 拼接
- 选择
- Kleene(也就是重复任意多次)
比如一个整数常量就可以是多个数字重复任意多次,也叫做正则语言。如果对于一个字符串,我们再加入递归定义即可以描述整个语法,就可以称作上下文无关语法
单词正则表达式
对于程序语言,单词的类型不外乎关键字、标识符、符合和各种类型的常量
对于整数常量就可以用这样的正则表达式来表示
integer -> digit digit* digit -> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
上下文无关文法
一般正则表达式只适用于描述单词,因为正则表达式无法描述嵌套结构,一般正则表达式的实现都是用有限状态自动机,之前用Python实现了一个简单的正则表达式引擎也是这样,但是对于匹配任意深度的嵌套结构就需要有一个任意大的状态机,显然不符合。而定义嵌套结构对于描述语法非常有用,所以就有了上下文无关文法
expr := id | number | - expr | ( expr ) | expr or expr
op := + | - | * | /
对于上下文无关文法,每条规则叫做一个产生式,产生式左部的符合称为非终结符,而右部则是多个终结符或者非终结符,最后所有规则都会推到至终结符上,而终结符就是正则表达式定义的单词
推导和语法树
一个正确的上下文无关文法,就可以指导我们如何生成一个合乎语法的终结符串
最简单的就是从开始符号开始,用这个产生式的右部取代开始符合,再从得到的串选择一个非终结符继续进行推导,直到没有剩下的非终结符,这个过程就像递归构造一个树的过程
expr := expr op expr
:= expr op id
:= expr + id
:= expr op expr + id
:= expr op id + id
:= expr * id + id
:= id * id + id
但是对于给定的上下文语法有可能会推导出不止一颗语法分析树,我们就说这个上下文语法是存在歧义性的。所以对于上面的上下文无关语法还有更好的文法
扫描
扫描也就是词法分析,词法分析完全可以不需要什么正则表达式、自动机什么的,徒手撸出来,现在业界为了更好的生成错误信息,应该很多也是手工的词法分析器
手工的词法分析器,无非就是一直读入字符,到能判断出它的token在送入语法分析器
有限状态自动机
使用有限状态机的词法分析一般都是这样的几个步骤
给出词法的正则表达式
将正则表达式转换为非确定有限自动机(NFA)
其实对于任意的正则表达式都可以用拼接、选择和Kleene闭包来表示
而同样的,有限自动机也可以通过这三种方式来表示,图就不画了,这个在之前写Python正则表达式引擎的文章里都画过了(溜了
将NFA转换为确定性有限状态自动机(DFA)
将NFA转换到DFA可以采用的是子集构造法,主要思想就是,在读入给定输入之后所到达的DFA状态,表示的是原来NFA读入同样输入之后可能澳大的所有状态
最小化DFA
对于最小化DFA的主要思想是,我们把DFA所有状态分为两个等价类,终止态状态和非终止状态。然后我们就反复搜索等价类X和输入符合c,使得当给定C作为输入时,X的状态能转换到位于k>1个不同等价类中的状态。之后我们就把X划分为k个类,使得类中所有转台对于C都会转移到同一个老类的成员。直到无法再按这种方式找到划分的类时,我们就完成了
这四个步骤在之前的写的正则表达式引擎中都完成了,在那三篇文章里会更详细一点
语法分析
一般语法分析器的输入是token流,而输出是一颗语法分析树。其中分析方法一般可以分为自上而下和自下而上两类,这些类中最重要的两个分别称为LL和LR
LL表示从左向右,最左推导,LR表示从左向右,最右推导。这两类文法都是从左到右的顺序读取输入,然后语法分析器试图找出输入的推导结果
自上而下的方式
一般自上而下的语法分析器比较符合之前的推导方法,从根节点开始像叶节点反复的递归推导,直到当前的叶节点都是终结符
递归下降
递归下降很符合上面说的从根节点出发进行推导,一般用于一些相对简单一些的语言
read A
read B
sum := A + B
write sum
write sum / 2
比如对于这个程序的递归下降,语法分析器一开始调用program函数,在读入第一个单词read后,program将调用stmt_list,再接着调用stmt才真正开始匹配read A。以这种方式继续下去,语法分析器执行路径将追溯出语法分析树的从左向右、自上而下的遍历
表格驱动的LL自上而下
表格驱动的LL是基于一个语法分析表格和一个栈
分析流程是
- 初始化一个栈
- 将开始符号压入栈
- 弹出栈顶,然后根据栈顶的符号和当前的输入符号查表
- 如果弹出的是非终结符,将会继续查表来确定下一个压入栈中的产生式
- 如果是终结符将进行匹配
预测集合
从上面可以看出来最重要的就是那个语法分析表格了,语法分析表格其实就是根据当前输入字符对下一个产生式的预测,这里就要用到一个概念:预测集合,也就是First和Follow集合。这个在之前写编译器系列讲的比较详细,在这里就不写了
当然LL语法也会有很多处理不了的文法,所以也才会有其它的语法分析方法
自下而上的方式
在实践中,自下而上的语法分析都是表格驱动的,这种分析器在一个栈中保存所有部分完成的子树的根。当它从扫描器中得到一个新的单词时,就会将这个单词移入栈。当它发现位于栈顶的若干符号组成一个右部时,它就会将这些符号归约到对应的左部。
一个自底向上的语法分析过程对应为一个输入串构造语法分析书的过程,它从叶子节点开始,通过shift和reduce操作逐渐向上到达根节点
自底向上的语法分析需要一个堆栈来存放解析的符号,例如对于如下语法:
0. statement -> expr
1. expr -> expr + factor
2. | factor
3. factor -> ( expr )
4. | NUM
来解析1+2
| stack | input | |
|---|---|---|
| null | 1 + 2 | |
| NUM | + 2 | 开始读入一个字符,并把对应的token放入解析堆栈,称为shift操作 |
| factor | + 2 | 根据语法推导式,factor -> NUM,将NUM出栈,factor入栈,这个操作称为reduce |
| expr | + 2 | 这里继续做reduce操作,但是由于语法推导式有两个产生式,所以需要向前看一个符合才能判断是进行shift还是reduce,也就是语法解析的LA |
| expr + | 2 | shift操作 |
| expr + NUM | null | shift操作 |
| expr + factor | null | 根据fator的产生式进行reduce |
| expr | null | reduce操作 |
| statement | null | reduce操作 |
此时规约到开始符号,并且输入串也为空,代表语法解析成功
有限状态自动机的构建
0. s -> e
1. e -> e + t
2. e -> t
3. t -> t * f
4. t -> f
5. f -> ( e )
6. f -> NUM
- 对起始推导式做闭包操作
先在起始产生式->右边加上一个.
s -> .e
对.右边的符号做闭包操作,也就是说如果 . 右边的符号是一个非终结符,那么肯定有某个表达式,->左边是该非终结符,把这些表达式添加进来
s -> . e
e -> . e + t
e -> . t
对新添加进来的推导式反复重复这个操作,直到所有推导式->右边是非终结符的那个所在推导式都引入
- 对引入的产生式进行分区
把 . 右边拥有相同非终结符的表达式划入一个分区,比如
e -> t .
t -> t . * f
就作为同一个分区。最后把每个分区中的表达式中的 . 右移动一位,形成新的状态节点
- 对所有分区节点构建跳转关系
根据每个节点 . 左边的符号来判断输入什么字符来跳入该节点
比如, . 左边的符号是 t, 所以当状态机处于状态0时,输入时 t 时, 跳转到状态1。
- 对所有新生成的节点重复构建
最后对每个新生成的节点进行重复的构建,直到完成所有所有的状态节点的构建和跳转
小结
这一篇主要是提了对词法和语法的分析过程,因为想要结合语言设计和实践,更详细的应该去看前面的写一个编译器系列
PL真有意思(二):程序设计语言语法的更多相关文章
- PL真有意思(四):控制流
前言 对大多数计算模型而言,顺序都是基本的东西,它确定了为完成所期望的某种工作,什么事情应该最先做,什么事应该随后做,我们可以将语言规定顺序的机制分为几个类别: 顺序执行 选择 迭代 过程抽象 递归 ...
- PL真有意思(一):引言
前言 断断续续学编译原理到之前发过写一个编译器和正则表达式引擎系列文章也有一段时间了,然后最近看完PLP这本书,这本书应该算是入门书,但是对我这种半吊子收获很大.所以为了弥补最近学操作系统和接外包摸的 ...
- PL真有意思(五):数据类型
前言 现在大多数程序设计语言中都有表达式和/或对象的类型概念.类型起着两种主要作用: 为许多操作提供了隐含的上下文信息,使程序员可以在许多情况下不必显示的描述这种上下文.比如int类型的两个对象相加就 ...
- PL真有意思(八):其它程序设计模型
前言 在之前几篇我们讨论的语法.语义.命名.类型和抽象适用于所有语言.然而我们的注意力都主要集中在命令式语言上,现在这篇来看看其它范式的语言.函数式和逻辑式语言是最主要的非命令式语言. 函数式语言 命 ...
- PL真有意思(七):数据抽象和面向对象
前言 在之前的名字.作用域那篇提到模块类型,它使程序员可以从一个给定抽象出发,通过实例化产生多个实例:再后面是类,它使程序员可以定义一族相关的抽象. 在这一篇里,我们会来看一下面向对象程序设计及其三个 ...
- PL真有意思(三):名字、作用域和约束
前言 这两篇写了词法分析和语法分析,比较偏向实践.这一篇来看一下语言设计里一个比较重要的部分:名字.在大部分语言里,名字就是标识符,如果从抽象层面来看名字就是对更低一级的内存之类的概念的一层抽象.但是 ...
- PL真有意思(六):子程序和控制抽象
前言 在之前我们把抽象定义为一种过程,程序员可以通过它将一个名字与一段可能很复杂的程序片段关联起来.抽象最大的意义就在于,我们可以从功能和用途的角度来考虑它,而不是实现. 在大多数程序设计语言中,子程 ...
- PL/SQL语言语法
一.前言 SQL全称是"结构化查询语言(Structured Query Language)",而PL/SQL是过程语言(Procedure Language),是对SQL的扩展. ...
- C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | IT宅.com
原文:C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | IT宅.com C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | I ...
随机推荐
- 第三方软件 pcanywhere提权
pcanywhere 是一个远程管理软件 1.访问pcanywhere默认安装目录 访问 下载打开 利用破解工具直接 选择刚刚下载的软件 点破解 拿到用户密码后去百度下载客户端让后连接
- 22.Linux定时任务
1.计划任务时间管理 参数 含义 -e 编辑定时任务 -l 查看定时任务 -r 删除定时任务 -u 指定其他用户 \* 表示任意的(分.时.日.月.周)时间都执行 \- 表示一个时间范围段, 如5-7 ...
- mac外接显示器 字体发虚解决方案
描述 今天买的LG性价比之王21:9的2k显示器到了,但是连接mbp后发现,字体发虚,模糊 Macbook 外接显示器默认为 TV 模式,TV 渲染模式下,文字效果非常非常非常差 解决 下载 patc ...
- FreeSql 已支持 .NetFramework 4.0、ODBC 访问
FreeSql 开源发布快一年了,目前主仓库代码量 64118 行,用 git 命令统计的命令如下: find . "(" -name "*.cs" " ...
- ESP8266 打造一款物联网产品---搭建环境编译及烧录
一 前记 作为一个在wifi领域耕耘了多年的人,以前一直在外企和大公司做芯片,没有怎么使用过国内的芯片公司做出来的芯片.最近正好有一个项目需要用到一款低成本的wifi芯片,找来找去,发现乐鑫的最适合. ...
- SQLite AUTOINCREMENT
只有integer字段可以设置autoincrement.int不行.integer是无符号整型.
- (Java) RedisUtils
package com.vcgeek.hephaestus.utils; import org.springframework.beans.factory.annotation.Autowired; ...
- [考试反思]1007csp-s模拟测试63:朦胧
别找了原来没有写过叫<朦胧>的我check过了.(慌的一匹) 总算是比较早的改完了一套题. 但是考的是个啥啊... 前两道题都很卡常导致我想到了正解但是都放弃了. 2e8的复杂度怎么可能能 ...
- [考试反思]0819NOIP模拟测试26:荒芜
这么正式的考试,明天应该就是最后一次了吧 然而..今天,我仍然没能抓住机会 RNBrank1:.skyh还是稳.外校gmk拿走第三. 四五六名都是63-64.第七50.第八39.我和三个并列的是第九. ...
- CSPS模拟 72
状态..找不回来了.. T2 简单的期望 考试的时候忘考虑一个事,就是连续多位进位的情况 考试的时候打出$n^2$复杂度dp还没引起怀疑真是不应该. T3 简单的操作 最后一刻才想到图不联通,已经想不 ...