LR 语法分析器

下载地址

 

LR语法分析器算是基本完成了,只需要一个文法定义文件(.syntax),就可以进行对应语言的语法分析,最后形成语法树。词法分析是固定的,采用C++的词法定义。以后将加入动态的词法分析。

压缩包中文件的描述:

LRTable.exe 是用文法定义文件(syntax文件)生成LR动作表文件(action文件),使用方法在 1_build_action.bat 文件和 tableop.txt 文件中有。直接运行 1_build_action.bat 会自动生成compile.script.syntax 文件对应的动作表,并保存为 script.action 。

LRParser.exe 是语法分析器,运行时必须传入syntax文件和对应的action文件,这两个文件必须是对应的,否则运行结果不可预料。运行停止后可以使用 “?” 命令来查询支持哪些 Command。例如用 result 命令来打印分析后的语法树。

*.scp 文件是例子代码,用来给 LRParser 分析的。

tt.txt 是一个错误的例子代码,测试 LRParser 的错误恢复。

tableop.txt 是 LRTable.exe 的命令序列,自动生成动作表,并保存退出。供 1_build_action.bat 使用。

compile.script.syntax 是我的脚本的文法定义。我的脚本解释器的语法分析是硬编码的。该文法定义是一个老版本的定义,还不支持object、new、以及匿名函数。因此 object.scp 用这个文法定义以及对应的动作表分析会出错。

compile.script2.syntax 是最新的文法定义,支持object、new、以及匿名函数,可以正确分析 object.scp 文件。

再要更改语言,就只用更改 syntax 文件即可,无需再更改 LRParser.exe,这就是我的语法分析器的强大之处。
PS:

1、除了两个exe文件之外,其余文件不管后缀是什么,都是文本文件,可以用文本编辑器打开。

2、syntax文件中,每个非终结符的定义最后都必须留至少一个空行,否则无法正确分析。

3、syntax文件中,nil、identifier 和 literal 是特殊终结符,本别指代【空】、标识符和常量。他们的定义应该由词法分析给出,但是目前词法分析是硬编码的,所以看不到他们的定义。

identifier 就是以字母或者下划线开头,字母数字下划线组成的记号
nil 是空,不匹配任何符号
literal 就是 数字开头的记号(数字常量)或者引号括起来的记号(字符串常量)
关键字由单引号括起来,文法中称之为终结符。

4、syntax文件中,圆括号()代表optional(可有可无)。

 


 

补充1:文法分析法的分类

语法分析分为自顶向下和自底向上两种。自顶向下分析法有 递归下降、LL 等。LL大致相当于递归下降的非递归版本。自顶向下分析的思路是分析器读入输入序列的前几个记号,就必须对后续序列的形制进行猜测,否则就会出错。例如:

stat -> 'if' exp 'then' stat 'else' stat 'endif'
stat -> 'while' exp 'do' stat 'endwhile'

如果读入记号为'if'分析器就可以大胆猜测后续序列采用第一个产生式进行分析,如果分析不下去,那只能说输入的代码错了,可以进行错误修复。

如果是:

stat -> 'if' exp 'then' stat 'else' stat 'endif'
stat -> 'if' exp 'then' stat 'endif'

光读入第一个'if'是没有办法确定的,分析器必须读如若干个记号,直到遇到'else'或者'endif'我才能确定。这就是自顶向下的麻烦所在:分析器很难知道读入多少个记号才能确定用哪一个产生式来处理。

自底向上分析法有移动归约、LR系列(SLR、LALR、LR)等。自底向上的思想是:分析器不对后续序列采用何种产生式进行猜测,只对已经读入的序列负责。已经读入的符号在堆栈里摆成个什么形制(堆栈状态),就按照对应产生式来归约。

移动归约适合手动编码,比如a+b/2,总是看符号,然后比较符号的优先级。/比+优先级高,就先归约/再归约+。移动规约和LR系列的关系就好比是递归下降和LL的关系一样。

SLR仅仅依据当前堆栈状态来归约,SLR动作表里面的条目数就是所有可能的堆栈状态数。这样归约有个问题,例如a+b/2会被分析为((a+b)/2),因为读完a+b时,刚好可以归约,于是就归约了,完全没有考虑后面的除号(/)优先级更高。

LALR和LR是依据当前读入的字符和当前堆栈状态来归约。那么a+b虽然可以归约,但是当前读入字符/是不允许a+b归约的,得继续往前读,直到看到b/2再开始归约,变为a+(b/2)=>(a+(b/2))。

LALR 是简化了的LR(教科书都是先介绍LR,再介绍LALR)。LR动作表的每一条包含一个记号和一个堆栈状态,这两条规定了一个动作,这样的动作表比SLR 动作表大许多。LALR把LR动作表按照堆栈状态分组,每一组里面有一个堆栈状态和若干个记号,意思是:我遇到这若干个记号,而当前堆栈状态恰好是那个状 态,我就采取对应动作。因此LALR的动作表和SLR动作表一样大,还可以像LR一样依据读入记号来归约。但是由于合并了动作,会导致有些LR可以分析的 LALR无法分析。比如某文件按照LR分析状态转移序列可以是1-4,4-6,6-3或者1-3,3-7,7-2。LALR将3、4合并,6、7合并,那 么就变成1-34,34-67,67-?,问号处是跳到34,还是2?

所以LR是所有分析法中最强大的。

 


 

补充2:关于 LRTable 生成的动作表

LRTable.exe生成的动作表是规范LR动作表,像一个数据库一样,包含四个字段:set(也叫state)、in、action、param,每一行后面还有对action和param的注释。

in字段里面的值,除了#表示文件结尾外,其他符号都是syntax文件中出现的。

action的意思是:

0=error,没有使用。

1=push,将[in]移入记号栈,将[param]移入状态栈

2=reduct,从记号栈中移出N个记号,进行归约,归约后作为当前读入记号,不立刻放入堆栈;并且从状态栈中弹出N个状态。param是一个数字,表示第几个产生式,N就是这个产生式左边的符号数。

3=custom,没有使用

4=accept,动作和reduct一样。

当前堆栈状态就是状态栈的栈顶元素。在教科书上,符号站和状态栈是一个栈,符号和状态交替存放。我为了避免异型数组,就分成两个栈,运行中保证两个堆栈数目一致即可。

其实可以简化一下,将reduct和accept的参数直接设置为N,也就是说param不是产生式,而直接是N,这样我可以不必读入syntax文件。我当初是想有利于报错和错误恢复。现在的错误恢复没有用到产生式。

LRParser最难的地方就是错误恢复。要对可能的错误进行搜索。找到一个较好的修复方案。

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 17
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值