构建文法(下)| 从零实现一门语言

    本节继续表达式文法的构建,也是整个文法中最复杂的部分。

    接上回,按照最开始的文法无法得出正确结果,因为真实的表达式计算是存在先后顺序的。所以,需要指定某个表达式中操作符的优先级,比如:* 和 \ 的优先级高于 + 和 -,在递归表达式时,应该优先计算高优先级的表达式。本次暂时只考虑部分操作符,=、||、&&、、>=、<=、==、!=、+、-、*、/、!、&、++、--、()等。

    我们来重新设计表达式的文法,与初版不同的是,要体现操作符的优先级。

    当程序读入一个个词法标记时,就进入到表达式的判定部分。比如给定一个表达式:“a+b*c”,如何知道 “*” 要优先于 "+"呢?

    假设文法判定程序读取到第一个操作符标记 “+”,首先得确认它的优先级,那么如何确认呢?

    我们可以通过一段子程序将所有操作符的文法按照优先级枚举出来,比如 “=”的优先级最低,最高是“()”。当文法判定程序每读入一个操作符,比如为  “+” 时,就按照对应的文法规则匹配,第一次先判定是否为 “=”,不匹配继续往后,直到操作符  "+" 时,我们就知道当前操作符的优先级了。然后继续读入下一个操作符是 "*",于是得到一个比上一次读取的优先级更高的操作符。

此时需要将 “*” 操作符的左数 "b" 和右数 "c" 相乘,得到临时值 t ,再将上一个操作符 "+"的左右数相加,即a+t。

    这里有一个问题,如果原表达式后面还有表达式呢,比如:"a+b*c\d",或者为:"a+b*c\d +e"。

    所以在读取到操作符 "*" 后,不能直接将左右数相乘,应该继续向后读取,直到表达式结束,因为不知道下一个的操作符的优先级是否比当前高。于是,直到表达式结束,将整个操作符读取完毕,同时也确认了每个操作符的优先级。

    为了方便后续的计算过程,我们需要将整个递归过程的操作符和操作数记录下来,然后从高优先级的计算,保留到临时变量中,然后再计算下一个优先级较低的,依次重复操作,直到最终递归算出最终结果。

    流程图如下:

    优先级最低的是赋值操作符,我们选择从低到高的顺序去递归判断所有操作符,当然也可以从高到低,因为低级别的操作符相对简单,我们从易到难分析。

1. “=”操作符

    观察赋值表达式:a=b,a=b+c*d,a=b=c=d*e,可以发现,

    1,赋值操作符的左值为变量;

    2,赋值操作符右值可以是一个变量或值;

    3,赋值操作符右值也可以是另一个表达式。

    那么如果右值是表达式时,我们需要继续判新的断表达式里的操作符的优先级,于是指定下一个优先级是 “||”。

    观察表达式:a*b||c+d/e,操作符"+"的左值和右值也为表达式,所以在构造文法时,操作符的两边都要递归。值得注意的是,赋值操作符的左值也可以是另一个表达式,但左值的表达式只能是赋值表达式。

assiexpr -> assiexpr ASSIGN assiexpr orexpr|~

    消除左递归。

assiexpr -> orepxr assipart   assipart -> ASSIGN orexpr assipart|~

2. " ||"操作符

orexpr -> andexpr orpart      orpart -> OR andexpr orpart|~ // a||b&&v

3. "&&"操作符

andexpr -> gteexpr andpartandpart -> AND gteexpr andpart|~

4. ">="操作符

gteexpr -> lteexpr gteexprgteexpr -> GTE lteexpr gtepart|~

5. "<="操作符

lteexpr -> eqexpr ltepartltepart -> LTE eqexpr ltepart|~

6. "=="操作符

eqexpr -> addexpr eqparteqpart -> EQU addexpr eqpart|~

    依次类推,为了简化流程和降低复杂度,我们选取几个代表的操作符来构建文法。

7. "+-"操作符

    参考《编译原理》表达式的文法结构

addexpr -> iterm addpartaddpart -> addopr iterm addpart|~addopr -> ADD SUBiterm -> factor itermpartitermpart -> mulopr factor itermpart|~mulopr -> MUL DIV MODfactor -> leftopr factor |valpartleftopr -> ! & ++ --valpart -> val rightporrightopr -> ++ --val -> ID |LPA COMMA expr RLA| NUM|CHAR|STRING|BOL

8. 函数调用。

a = test(b,c);

    函数调用也算表达式的一部分,可以算作是赋值表达式的右值。当表达式判定是ID类型且下一个标记是左括号时,进入函数调用的逻辑。

funcallexpr -> ID LPA COMMA expr RLA SEM

    至此已基本完成文法的设计,后续将进入代码的编写。

欢迎关注公众号:零点码起。


     1.一个hello world的诞生

     2.词法解析器

     3.从自然语言认识文法

     4.构造文法

     5.语义分析

     6.生成中间代码

     7.函数的帧栈调用过程

     8.汇编

     9.编译和链接

     10.终于跑起来了

     11.多文件编译

     12.丰富数据类型

     13.流程控制语句

     14.编译优化算法

     15.文件读取

     16.一个线程的实现

     17.什么是锁

     18.网络编程

     19.面向对象

     20.其他规划


欢迎关注公众号:零点码起。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值