若要深入理解本节内容,请参看视频讲解及相关代码演示:
用java开发编译器
任何编程语言,最常用的就是循环语句了,无论何种编程语言,都必须包含循环控制语句,在C语言中,for, while, do..while 这三种循环语句的使用,占据了源程序代码的绝大部分,因此,要开发C编译器,对这三种循环语句的语法解析是必不可少的,我们先看看他们的语法定义:
STATEMENT -> WHILE LP TEST RP STATEMENT
STATEMENT -> FOR LP OPT_EXPR TEST SEMI END_OPT_EXPR RP STATEMENT
OPT_EXPR -> EXPR SEMI
OPT_EXPR -> SEMI
END_OPT_EXPR -> EXPR
STATEMENT -> DO STATEMENT WHILE LP TEST RP SEMI
第一句语法对应的是while 循环, 例如:
while (i < 10) {
...
}
其中i < 10 对应语法中的TEST, {…}这部分对应语法中的statement
第二句语法:
STATEMENT -> FOR LP OPT_EXPR TEST SEMI END_OPT_EXPR RP STATEMENT
对应的是for 循环,注意OPT_EXPR 可以是一个表达式,也可以是一个分号,这样该语法就能对应下面的循环代码:
for (i = 0; i < 10; i++) {...}
或
for(; i< 10; i++) {...}
最后一句语法对应的是do…while 循环,适用于下面情况:
do {
...
} while (i < 10);
下面我们分别依据几个实例来讲解解析器对循环语句的解析过程,大家一定要通过观看视频才好深入理解本节的算法内涵。先看对for循环的解析:
void f() {
int a = 0;
int i = 0;
for (i = 0; i < 10; i++) {
a = a + 1;
}
}
1: 解析器通过以下若干个语法表达式来解析 void f() , 这些内容在前面章节已经讲解过。
读入 void ,得到标签TYPE
通过表达式:
TYPE_SPECIFIER -> .TYPE
TYPE_OR_CLASS -> .TYPE_SPECIFIER
SPECIFIERS -> .TYPE_OR_CLASS
OPT_SPECIFIERS -> .SPECIFIERS
然后读入函数名f, 返回标签NAME,然后通过表达式:
FUNCT_DECL -> .NEW_NAME LP RP
把开头的函数的定义进行解析。
2:读入左括号,获得对应标签LC,进入STATEMENT部分的解析。
3: 解析器将通过下面步骤对变量声明语句: int a = 0; int i = 0; 进行解析,解析步骤在前面章节已经讲解过:
读入关键字int, 得到标签type.
根据以下表达式进行推导:
TYPE_SPECIFIER -> .TYPE
TYPE_OR_CLASS -> .TYPE_SPECIFIER
SPECIFIERS -> .TYPE_OR_CLASS
读入变量名a, 返回标签NAME,通过以下表达式进行推导:
NEW_NAME -> .NAME
VAR_DECL -> .NEW_NAME
读入符号 =, 返回标签 EQUAL,读入数字0,返回标签NUMBER,继续根据相
关表达式进行语法推导:
UNARY -> .NUMBER
BINARY -> .UNARY
NO_COMMA_EXPR -> .BINARY
EXPR -> .NO_COMMA_EXPR
INITIALIZER -> .EXPR
DECL -> .VAR_DECL EQUAL INITIALIZER
DECL_LIST -> .DECL
读入分号,得到标签SEMI,继续根据表达式进行推导:
DEF -> .SPECIFIERS DECL_LIST SEMI
DEF_LIST -> .DEF
LOCAL_DEFS -> .DEF_LIST
STATEMENT -> .LOCAL_DEFS
STMT_LIST -> .STATEMENT
上面步骤重复2次,从而对int a= 0; int i = 0; 进行解析
4: 读入关键字for, 读入左括号(, 读入变量名i, 得到标签NAME, 根据以下语法表达式进行递归:
UNARY -> .NAME
BINARY -> .UNARY
NO_COMMA_EXPR -> .BINARY
读入i 后面的等号和数字0,根据下面的表达式进行递归:
UNARY -> .NUMBER
NO_COMMA_EXPR -> .BINARY
NO_COMMA_EXPR -> .NO_COMMA_EXPR EQUAL NO_COMMA_EXPR
EXPR -> .NO_COMMA_EXPR
读入分号,得到标签SEMI, 根据表达式递归:
OPT_EXPR -> .EXPR SEMI
这样,i = 0 就被解释为 OPT_EXPR.
5: 继续读入变量名i ,得到标签NAME, 根据下面表达式进行递归:
UNARY -> .NAME
BINARY -> .UNARY
读入符号< 得到标签 RELOP 读入小于号后面的数字10,返回标签NUMBER,
继续通过下面表达式进行递归:
UNARY -> .NUMBER
BINARY -> .UNARY
BINARY -> .BINARY RELOP BINARY
NO_COMMA_EXPR -> .BINARY
EXPR -> .NO_COMMA_EXPR
TEST -> .EXPR
这意味着,解析器将表达式 i < 10 解释为 TEST.
6: 继续读入后面的分号,得到标签SEMI, 读入变量i, 得到标签NAME,根据下面表达式进行递归:
UNARY -> .NAME
读取后面的符号++, 返回标签INCOP,继续根据下面表达式递归:
BINARY -> .UNARY
NO_COMMA_EXPR -> .BINARY
EXPR -> .NO_COMMA_EXPR
END_OPT_EXPR -> .EXPR
也就是说 i++ 这个表达式被解析器解读为END_OPT_EXPR.
7: 读入右括号),和左大括号,解析器开始 进入for 下面的STATEMENT部分的解析。由于在for循环体中,只有一句语句 a = a + 1, 因此接下来将对这句赋值语句进行解析。
8: 读入变量名 a, 得到对应标签NAME, 然后根据下面表达式进行递归:
UNARY -> .NAME
BINARY -> .UNARY
NO_COMMA_EXPR -> .BINARY
读入符号=,返回标签EQUAL, 读入等号后面的变量a,得到标签NAME,继续根据
以下表达式进行解析:
UNARY -> .NAME
BINARY -> .UNARY
读入符号+, 得到标签PLUS,读入数字1,得到标签NUMBER,继续根据表达式
进行推导:
UNARY -> .NUMBER
BINARY -> .UNARY
BINARY -> .BINARY PLUS BINARY
NO_COMMA_EXPR -> .BINARY
EXPR -> .NO_COMMA_EXPR
读入分号,得到标签SEMI,继续根据表达式进行推导:
STATEMENT -> .EXPR SEMI
STMT_LIST -> .STATEMENT
到这里,解析器把语句a = a + 1; 先解读成EXPR, 然后继续把该表达式推导成STMT_LIST
9: 读入右大括号 }, 然后根据表达式进行递归:
COMPOUND_STMT -> .LC STMT_LIST RC
STATEMENT -> .COMPOUND_STMT
也就是说,解析器把for 语句后面的{…} 这部分解析成STATEMENT.
10: 根据表达式进行递归:
STATEMENT -> .FOR LP OPT_EXPR TEST SEMI END_OPT_EXPR RP STATEMEN
这时,整个for(…){…} 的代码部分被解析器解析成STATEMENT. 从而整个for循环语句能被解析器全部吸收
11: 接下来的推导跟以前一样,最后会推导到全局非终结符,进而解析器能够解析当前输入的代码。
对while, do…while 语句的解析请参看视频: