语法分析

####
写在前面:
词法分析的工作是扫描文本,提取一个个独立的单词
语法分析的工作是分析一个个独立的单词之间的组合规则从而获得文本的含义
例:
词法分析:定义词法规则(正则表达式) Number为[0-9]+ ,Letters为[a-zA-Z]+ ,不在规则中的抛弃
则,扫描文本"This is 233"时得出LettersLettersNumber
语法分析:处理词法分析的结果,定义sentence=LettersLettersNumber,
则,分析结果得这个文本是一个句子
####
语法分析的方法有很多,下面先从基本概念开始做简要介绍
终结符与非终结符:
语法分析 - 逐梦 - 随梦飞翔
 描述文法规则的语言(形式化符号)
BNF和EBNF(extended BNF)
此两种文法描述语言大体相同,只是EBNF比BNF多了重复描述符{...}和可选描述符[...],而BNF中只能通过递归来表示重复。BNF与EBNF的具体定义这里不展开。只简单举个例子:
1、一个b后跟任意个a,如b, ba, baa, baaa
BNF表示:A Aa|b
EBNF表示:A b{a}
2、if语句(忽略空格)
BNF表示:
statement if_statement | other
if_statement if (exp) statement | if (exp) statement else statement
exp 0|1
EBNF表示:
statement if_statement | other
if_statement if (exp) statement[else statement]
exp 0|1
3、Tiny语言的文法描述(摘自教材)
BNF:
语法分析 - 逐梦 - 随梦飞翔
 EBNF:
语法分析 - 逐梦 - 随梦飞翔

语义动作
分析到某个语法时所要执行的动作(代码)
如算式计算,遇到A b+c时,计算b+c的值并反馈给A做下一步计算。

First集合与Follow集合
First(A)表示非终结符号A包含的第一个终结符号,如A acB | b,则First(A)={a,b}
Follow(A)表示非终结符号A后面紧跟着的终结符号,如A B[c],B a 则:Follow(A)={$},Follow(B)={c,Follow(A)=$},$表示结束符

 好了,入正题,文法分析的方法有哪些呢?
两种思路: 自顶向下分析  和  自底而上分析
自顶向下分析 又分为两种思路: 回溯法 预测分析
回溯法通用性较强,但性能较低,故一般不用于编译器,这里不做介绍
自顶向下是从非终结符到终结符方向分析,即文法规则从箭头左边到箭头右边
自底而上是从终结符到非终结符方向分析,即文法规则从箭头右边到箭头左边, 箭头右边的成分替换成左边的过程叫归约
例:
T aS
S ab
自顶向下的思路是:
1、T
2、aS(T换成aS)
3、S(匹配a)
4、ab(S换成ab)
5、b(匹配a)
6、空(匹配b)
自底而上的思路是:
1、a(匹配a)
2、aa(匹配a)
3、aab(匹配b,有合适的文法规则可用于归约 S ab
4、aS(归约,仍有合适的文法规则可用于归约 T aS)
5、T(归约)
6、完成
具体算法:
1、自顶向下:递归下降分析法,LL(1)分析,LL(K)分析
注:第一个L(left)表示从左到右分析字符串,第二个L(left)表示最左推导,即文法规则中从左到右的推导(如上自顶向下的思路例子),K为一个整数,表示算法中需要向前(右)探测多少个字符才能决定使用哪一条文法规则
2、自底而上:LR(0)分析,LR(1)分析,SLR(1)分析,LALR(1)分析,LR(K)等
注:第一个L(left)表示从左到右分析字符串,第一个R表示从右到左的推导,K为整数,同上。其中SLR(1)和LALR(1)是LR(1)的不同改进方法。

上面提到的算法中只有递归下降分析法是用递归函数调用实现的,其余皆为栈的方式,固定算法,数据驱动,文法规则以数据表(table)的方式呈现,故不同的文法规则规则只是换了不同的数据设置。函数调用的方法代码易懂,贴合人的思维,故一般用于比较简单的手工编写,而栈的方式一般是用文法规则自动生成分析代码。

笔者认为,实际应用比较多的是递归下降分析法和LALR(1),所以这里只介绍这两种分析方法。
(原因:
自顶向下发分析方法中文法不能有左递归(AAb)和左公因子(Aab|B, Babc),左递归会造成无穷展开,左公因子会造成先行预测的位数难以确定。故自顶向下分析发对文法描述有一定的限制,一般不用于自动生成代码。
自底而上分析方法中,LR(0)因没有先行预测,不能处理具有 归约-归约冲突 和 归约-移进冲突 的文法(这里不展开),SLR(1)是用LR(0)的DFA作分析的,比LR(0)更强大但仍有其无法适用的文法规则,LR(1)则是因为DFA状态数太多,导致数据表占用太多内存。LALR(1)是在LR(1)基础上的简化,但会导致出错延迟。综合之下,使用LALR(1)的较为普遍。)

递归下降分析法
以四则运算为例:

BNF文法:
exp →  exp addop term | term
addop →  + | -
term  term mulop factor | factor
mulop  * | /
factor  (exp) | number

消除左递归和左公因子,写出EBNF:
exp  term{addop term}
addop →  + | -
term  factor{mulop factor}
mulop  * | /
factor  (exp) | number

语法分析代码:

var token; void main() { getToken();//获取下一个(第一个)语法成分赋予变量token,由词法分析提供下一个语法成分

exp(); void exp() { term(); while((token=='+')||(token=='-')) { match(token);//匹配当前语法成分(token),并获取下一个语法成分赋予token term(); } } void term() { factor(); while((token=='*')||(token=='//')) { match(token); factor(); } } void factor() { switch(token) { case'(': match('('); exp(); match('('); break; case number: match(number); break; default: error(...); } } void match(expectedToken) { if(token==expectedToken) getToken();//获取下一个符号 else error(...); }

至于语义动作插入的位置,要根据实际情况分析进行填入(所以这种分析方法不能由计算机自动生成)
继续此例,填入计算的语义动作后,代码如下:

var token; var result:double; void main() { getToken();//获取下一个(第一个)语法成分赋予变量token,由词法分析提供下一个语法成分 result=exp(); } double exp() { var addResult:double; addResult=term(); while((token=='+')||(token=='-')) { match(token);//匹配当前语法成分(token),并获取下一个语法成分赋予token if(token=='+') addResult+=term(); else addResult-=term(); } return result; } double term() { var mulResult:double; mulResult:double=factor(); while((token=='*')||(token=='//')) { match(token); if(token=='*') mulResult*=factor(); else mulResult/=factor(); } return mulResult; } double factor() { var factorResult:double; switch(token) { case'(': match('('); factorResult=exp(); match('('); break; case number: factorResult=match(number); break; default: error(...); } return factorResult; } void match(expectedToken) { if(token==expectedToken) getToken();//获取下一个符号 else error(...); }


LALR(1):
以A  (A) | a 为例:
1、首先需要扩充文法,以便于判断分析的结束
扩充文法:
A'  A   
A  (A) | a

2、构建LR(1)的DFA
语法分析 - 逐梦 - 随梦飞翔
解释: 图中每条语法规则的”.“号表示当前状态分析到哪个符号,
比如2状态:A  (.A) 表示当前状态已接受”(“,下一个期待的语法成分是”A“
2状态收到A后进入4状态,此时A  (A.  表示已收到“A”,下一个期待的语法成分是“)”

若期待的下一个语法成分是非终结符,则需要在状态中展开列出
如0状态,A'  .A 下一个期待的语法成分是A,A是非终结符,无法直观判断下一个期待接收的应该是什么符号,所以展开A,已知 A  (A) | a ,故下一个成分可能是”(“也可能是”a“,展开成A  .(A) 和 A  .a  故0号状态中列出有3条规则。如此类推,其他状态亦如此。

每条语法规则",”后面的是先行符,值为当前语法规则箭头左侧符号的follow集。$表示字符串的结束符
先行符的作用请看此例:
S  a | Bc
B  a
构造DFA时就有状态如下:
S  .a
S  .Bc
B  .a
没有先行符,无法判断遇到a时是用 S  .a还是 B  .a
有了先行符,就可以通过预判字符串中下一个符号从而进行选择:
S  .a,$
S  .Bc,$
B  .a,c

注:没有先行符的DFA就是LR(0)DFA。

3、合并状态
 核心一样的状态就合并,先行符做并集
语法分析 - 逐梦 - 随梦飞翔
 状态2和5合并,状态3和6合并,状态4和8合并,状态7和9合并
结果如下:
语法分析 - 逐梦 - 随梦飞翔
 
4、建表
语法分析 - 逐梦 - 随梦飞翔
注释:状态0的(字段表示状态0遇到(字符会接收并跳转到状态2
          Goto字段表示遇到A直接跳转至相应状态,不做任何操作
          归约:当一条文法规则完成匹配之后就把箭头右侧的内容换成左侧的内容,并按原路返回之前的状态。如状态5遇到$或/时就会归约成A,返回到状态0,接着进入状态1

5、使用栈的分析过程
依照上面的表进行操作
语法分析 - 逐梦 - 随梦飞翔
 
 6、语义动作
 自底而上的分析方法语义动作的填入比自顶向下相对简单,语义动作的执行点就是归约的时候,只有在归约的时候才进行语义动作。具体的代码实现这里不作详述。如何使用LALR(1)来进行分析代码的自动产生,可以参考yacc程序的思路。  下一篇将简要介绍如何使用lex+yacc自动产生“编译器”代码
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值