PL/0-语法分析器
本文主要按照笔者逻辑思考的顺序进行语法分析部分的说明。如有不明白的部分请见谅。
在拿到词法分析部分的一个又一个的 Token 时,庞大的语法分析让我无法下手,我不知道如何利用这些 Token,也不知道将 PL/0 语言用上下文无关文法描述出来有什么用,但这并不妨碍我们先将上下文无关文法写出来(看懂)😎
上下文无关文法描述的 PL/0 语言:
-
Program → Block .
-
Block → [ConstDecl][VarDecl][ProcDecl] Stmt
程序块的基本结构是:常量定义、变量定义、过程定义、语句
-
ConstDecl → const ConstDef {, ConstDef} ;
常量定义 -
ConstDef → ident = number
-
VarDecl → var ident {, ident} ;
变量定义 -
ProcDecl → procedure ident ; Block ; {procedure ident ; Block ;}
过程定义 -
Stmt → ident := Exp | call ident | begin Stmt {; Stmt} end | if Cond then Stmt | while Cond do Stmt | ε
赋值语句、调用语句、begin-end 块语句、判断语句、循环语句
-
Cond → odd Exp | Exp RelOp Exp
条件表达式 -
RelOp → = | <> | < | > | <= | >=
关系运算符 -
Exp → [+ | − ] Term {+ Term | − Term}
表达式 -
Term → Factor {* Factor | / Factor}
-
Factor → ident | number | ( Exp )
-
ident:字母开头的字母 / 数字串
-
number:无符号整数
从上面的文法中我们可以得到以下的信息:
- 🤔:PL/0 语言有着严格的先后顺序,必须将常量的声明、定义以及变量的声明放在最前面,然后是过程的声明和定义,最后才是语句。
- 😎:过程里可以嵌套过程的定义,过程里也可以调用(call)其他的过程。
- 😴:分号的位置很重要,常量和变量声明和定义的最后有;过程名后有;过程体结束后有;语句(stmt)只有在 begin 中的语句中有。
- 😋:程序最后是以 . 结束的。
有了上述的理解,我们终于知道程序的大概流程了。因为这里我们不用预测分析表,所以对于流程的了解挺重要的。下面将结合流程图展开分析(这里先不着急写代码,一切都理解了写代码其实很快。
程序的入口:
我们进入程序的“大门”,遇到了一个程序体,这里的程序体就像一个黑盒子(函数),我们一头扎进去,最后又会走出来,然后遇到一个 . ,这样程序就结束了。
程序体:
下面我们走进黑盒子中,发现又有许多分支 😵,但我们有一个竖向的“主线”,不妨就沿着主线走一遭。第一个“路口”是 const,我们忍住了没进去;第二个“路口”是 var,我们再次忍住了,因为我们是为了走到最后,而不能误入“歧途”。就这样,我们到了第三个 procedure 以及第四个“语句”,终于没有“路”可以走了,我们走到了尽头。
这一路上,我们遇到了很多“分叉口” – const、var、prodedure 和语句,但我们都不知道里面究竟是什么,它们对我们来说就是一个又一个的黑盒子,没错就是黑盒子,而在之后我们都会定义一个函数来代表黑盒子。这时,你可能感觉到了什么 😳,我们继续往下进行。
语句这个就更容易看出来分了五路,但不同于上面的程序体,语句这五条路,你一次只能选一条!
这样基本的程序结构,我们已经分析完了,但毕竟 talk is cheap. Show me the code
现在写完整的代码还为时尚早,不如用伪代码(可能)代替。
我们从头开始,进入了一个程序之中,并在其中发现了程序体(黑盒)和一个点号。
prog(){
block();
match('.');
}
然后是程序体内部,有 const、var 和 procedure 和语句:
block(){
const();
var();
procedure();
statement();
}
最后是语句,有五个分支:
statement(){
assign();
call();
begin();
if();
while();
// 空就不写了
}
下面就是对于函数体内部的补充了。待续……