语法分析器概述
从词法分析的角度看,语言是一个单词的集合,称之为正规集,单词是由一个个字符组成的线性结构;从语法分析的角度看,语言是一个句子的集合,而句子是由词法分析器返回的记号组成的非线性结构。反映句子结构的最好方法是树,常用的有分析树和语法树。分析语法结构的基本方法有两种:自上而下分析方法和自下而上分析方法。自上而下分析从根到叶子建立分析树,而自下而上分析恰好相反。在这两种情况下,分析器都是从左到右地扫描输入,每次读进一个记号。
与词法分析类似,语法分析也具有双重含义:
①规定句子形成的规则,也被称为语法规则。程序设计语言的大部分语法规则可以用上下文无关文法(Context Free Grammar,简称CFG)来描述。
②根据语法规则识别记号流中的评议结构,也被称为语法分析。最有效的自上而下和自下而上的分析方法都只能处理上下文无关文法的子类,如LL文法和LR方法,但是它们已足以应付程序设计评议的绝大多数语法现象。
一、任务与目的
·上机任务:
1、使用C/C++程序设计语言和递归下降子程序的方法编写该函数绘图语言的词法分析器。并要求设计一个语法分析器的测试小程序来调用自己编写的语法分析器测试各种不同的输入。
2、语法分析的任务是在词法分析基础上,根据语言的语法规则,把词法符号分解成各类语法单位。语法分析所依据的是语言的语法规则,语法规则通常用上下文无关文法描述。
·上机目的:
通过自己动手编写语法分析器,掌握正规式与正规文法、上下文无关文法(CFG)、有推导的基本概念(推导、分析树与语法树、二义性及二义性的消除)、自上而下分析(递归下降子程序方法、预测分析表方法、LL(1)文法)、自下而上分析。理解如何理论联系实际以及明白理论与实际的差别。
二、分析与设计
语法分析程序一般具有如下功能: 对单词符号串进行语法分析(根据语义规则进行推导和规约),识别出程序中的各类语法单位,最终判断输入串是否构成语法上正确的“程序”。
这里我们采用递归下降分析方法:直接以程序的方式模拟产生式产生语言的过程。它的基本设计思想是:为每一个非终结符构造一个子程序,每一个子程序的过程体中按该产生式的候选项分情况展开,遇到终结符直接匹配,而遇到非终结符就调用相应非终结符的子程序。该分析从调用文法开始符号的子程序开始,直到所有非终结符都展开为终结符并得到匹配为止。若分析过程中达到这一步则表明分析成功,否则表明输入中有语法错误。递归下降分析对文法的限制是不能有公共左因子和左递归。由于文法是递归定义的,因此子程序也是递归的。
对于规模比较小的语言,递归下降子程序方法是很有效的方法,它简单灵活,容易构造,其缺点是程序与文法直接相关,对文法的任何改变均需对程序进行相应的修改。
这里给出词法分析程序大概的设计方法:
1、根据要求写出语法分析的上下文无关文法G;
2、消除上下文无关文法G的二义性;
3、消除上下文无关文法G的(直接)左递归,并提取左因子;
4、构造文法的状态转换图并且简化;
5、将转换图转化为EBNF表示;
6、从EBNF构造递归下降子程序;
以下是较为详细的设计:
①总体结构与模块划分
语法测试模块(parsermain.cpp) 语法分析器模块(parser.h & parser.cpp) 绘图语言解释器入口 递归子程序集 先序遍历并打印表达式的语法树 出错处理模块 词法分析器模块(scanner.h & scanner.cpp) 初使化词法分析器 识别出具有独立意义的最小语法单位 辅助性模块 |
|
|
②重要数据结构
·语法树节点类型
struct ExprNode { // 语法树节点类型 enum Token_Type OpCode; union { struct { ExprNode *Left, *Right; } CaseOperator; struct { ExprNode *Child; FuncPtr MathFuncPtr; } CaseFunc; double CaseConst; double *CaseParmPtr; } Content; }; |
③关键思想与算法
·改写二义文法为非二义文法的方法:通过引入新的非终结符,使原来分辨不清的结构受到约束,从而使得对任何一个句子,仅能构造一棵分析树。
·消除直接左递归算法
输入:文法G中所有的A产生成 输出:等价的不含直接左递归的文法G’ 方法:首先,整理A产生式为如下形式: AàAa1|Aa2|…|Aam|p1|p2|…|pn 其中,ai非空,pj均不以A开始,然后用下述产生式代替A产生式: Aàp1A’| p2A’|…|pnA’ A’à a1A’| a2A’|…|amA’|e |
·消除左递归算法
输入:无回路文法G 输出:无左递归的等价文法G’ 方法:将非终结符合理排序:A1,A2,…,An,然后运用下述过程: for i in 2..n loop for j in 1..i-1 loop 用AjàQ1|Q2|…|Qk的右部替换每个形如AiàAj产生式中的Aj,得到新产生式: AiàQ1r|Q2r|…|Qkr; 消除Ai产生式中的直接左递归; end loop; end loop; |
|
·提取文法左因子算法:
输入:文法G 输出:等价的无左因子文法G’ 方法:为每个产生式A,找出其候选项中最长公共前缀a,重排A产生式如下,其中r是不以a为前缀的其他候选项。 Aàap1|ap2|…|apn|r 并用下述产生式替代之。 AàaA’|r A’àp1|p2|…|pn 重复此过程,直到所有A产生式的候选项中均不再有公共前缀。 |
·构造递归下降子程序的方法:
①构造文法的状态转换图并且简化; ②将转换图转化为EBNF表示; ③从EBNF构造递归下降子程序; |
三、测试例程设计
·测试程序(parsermain.cpp)
#include <stdio.h> #include "parser.h" extern void Parser(char *SrcFilePtr); int main(){ Parser("test.txt"); return 0; } |
·测试数据(test.txt)
// test data for t from -100 to 100 step 1 draw (t, 0); |
四、测试结果及分析
·测试环境
·软件平台:
OS 名称 Microsoft Windows XP Professional
OS版本 5.1.2600 Service Pack 2 内部版本号 2600
OS 制造商 Microsoft Corporation
开发环境 Microsoft .NET Framework版本 3.5
Microsoft Visual Studio 2008版本 9.0.21022.8 RTM
Microsoft Visual C++ 2008版本91899-270-3541886-60490
·硬件平台:
系统类型 基于 X86 的 PC
处理器#1 x86 Family 6 Model 15 Stepping 13 GenuineIntel ~1994 Mhz
处理器#2 x86 Family 6 Model 15 Stepping 13 GenuineIntel ~1994 Mhz
总的物理内存 1,024.00 MB X 2, DDRII 667Mhz
BIOS 版本/日期 Phoenix Technologies LTD R1100Q0, 2007-10-18
·测试结果
·结果分析
这里需要说明的一点是:因为语法分析器只是是整个编译器的一部分,所以在测试语法分析器时一定要加上如下的宏:
//-------------------------parser.cpp----------------------------- #include "parser.h" #define PARSER_DEBUG …… |
五、总结与体会
语法分析是编译器的重要阶段之一,可以认为是语法制导翻译模式编译器的核心。语法分析也有双重含义:根据一定的规则构成语言的各种结构,即语法规则;根据语法规则识别输入序列(记号流)中的语言结构,即语法法分析。同词法分析比较,语法分析的不是记号,而是组成语言的句子,从结构上讲不是线性的而是层次的,表征这种结构的最好方法是树,从而使得语法的分析就有了从根到叶子和从叶子到根两种分析方法。由于语言结构的复杂性,语法规则的描述也相应困难。
在上机实践中我们也发现:对于规模比较小的语言,递归下降子程序方法是很有效的方法,它简单灵活,容易构造,其缺点是程序与文法直接相关,对文法的任何改变均需对程序进行相应的修改。
附:源代码清单