在上篇文章中实现了支持变量和赋值语句的计算器。这次增加了条件语句和循环语句。
语法简单介绍
以下是条件语句的一个样例,可以对条件语句的格式有一个感性认识:
if var1 > 5
then
var2 := 10;
end
条件语句以if開始,后跟一个条件表达式,假设其为真则运行then后面的语句块,条件语句以end结束。
条件语句也能够支持else分支语句。比方
if var1 > 5
then
var2 := 10;
else
var2 := -10;
end
接下来是一个循环语句的样例:
var0 := 0;
var1 :=0;
repeat
var0 := var0 + var1;
var1 := var1 + 1;
until var1 > 100
上面这个样例是从1累加到100,结果保存在变量var0中。循环语句以repeat開始,后面是循环体。until后面是循环结束条件。
语法规则
条件语句和循环语句在代码中的实现与前一篇文章中赋值语句的实现原理同样。首先要定义出它们的语法规则。有了语法规则。在之前框架上面增加条件语句和循环语句的功能就比較简单了。以下列出两种语句的语法规则:
stmt -> id := expr; |
if lexpr then stmts end |
if lexpr then stmts else stmts end |
repeat stmts until lexpr
lexpr -> expr lop expr | expr
lop -> '<' | '>' | '=' | '!=' | '>=' | '<='
可见主要改动的是stmt,在当中新增了if开头的两条条件表达式语法规则和repeat开头循环表达式规则。
lexpr是条件表达式的语法规则,lop是几个条件操作符。lexpr条件表达式的运算符是expr算术表达式。
以下是之前提到的的语法规则。一并列出以方便查看:
stmts -> stmts stmt | stmt
id -> (letter|_) (letter | num)*
expr -> term | term+term | term-term
term -> factor | factor * factor | factor/factor
factor -> number | (expr) | id
number -> (0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9)*
语法树
之前有提到,求值的过程是在语法树的基础上进行的。
对于条件表达式和循环表达式相同要建立相应的语法树。以下分别相应举两个样例:
对于例如以下条件语句:
if var0 > 5
then
var1 := 10 * 2;
else
var1 := -3 / 3;
end
会生成例如以下的语法树:
if根节点存在三个子结点child,child[0]相应条件表达式。child[1]相应then后面的语句块,child[2]相应else后面的语句块。对该表达式求值的过程是先求条件表达式child[0]的值,若返回值非0。则计算then语句块child[1]的值,否则计算else语句块child[2]的值。
以下是一个repeat循环语句的样例:
repeat
var0 := var0 + var1;
var1 := var1 + 1;
until var1 > 100
为该循环语句生成语法树例如以下:
repeat根节点有两个子结点,左子结点是repeat后语句块。右子节点是until后的条件表达式。repeat后语句块中的每个语句都是一个节点,语句之间互为兄弟节点,图中的横向箭头就指向其兄弟节点。对repeat语法树求值的过程是先对repeat语句块进行求值。然后计算条件表达式的值,假设该值为假,那么继续循环对语句块求值,直到语句块的值为真则退出循环。repeat是一个其循环体至少会运行一次的循环。
if的语法树生成
以下是stmt函数中新增对if语句进行求值的case分支:
case IF: node = newNode(); node->kind = Stmt; node->attr.s = IfK; node->val.tt = IF; /*匹配if*/ match(IF); lnode = lexp(); <span style="white-space:pre"> </span>node->child[0] = lnode; <span style="white-space:pre"> </span>/*匹配then*/ match(THEN); <span style="white-space:pre"> </span>lnode = stmts(); <span style="white-space:pre"> </span> <span style="white-space:pre"> </span>node->child[1] = lnode; <span style="white-space:pre"> </span>/*存在else*/ <span style="white-space:pre"> </span>if (ELSE == token) <span style="white-space:pre"> </span>{ <span style="white-space:pre"> </span>match(ELSE); <span style="white-space:pre"> </span>rnode = stmts(); <span style="white-space:pre"> </span>node->child[2] = rnode; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>/*匹配end*/ <span style="white-space:pre"> </span>match(END); break;
这段代码的逻辑非常清晰,先建立一个if的节点。然后匹配if标识符,接下来调用lexp来匹配一个条件表达式,然后匹配then,后面是调用stmts进行语句块的的匹配。
假设存在else。匹配else后再次调用stmts进行语句块的匹配。最后匹配end。
repeat的语法树生成
以下是对repeat语句进行求值的case分支:
case REPEAT: node = newNode(); node->kind = Stmt; node->attr.s = RepeatK; node->val.tt = REPEAT; /*匹配repeat*/ match(REPEAT); lnode = stmts(); /*匹配until*/ match(UNTIL); /*条件表达式*/ rnode = lexp(); node->child[0] = lnode; node->child[1] = rnode; break;这段代码与if语句的处理分支类似。了解repeate的语法格式就非常easy理解这段代码了。
逻辑表达式的语法树生成
条件表达式匹配函数lexp与算术表达式exp类似。只是其优先级要低于算术表达式,以下是条件表达式和算术表达式相应的语法规则,集中列出来更easy看清楚他们的优先级:
lexpr -> expr lop expr | expr
expr -> term | term+term | term-term
term -> factor | factor * factor | factor/factor
factor -> number | (expr) | id
从上到下依次代表条件表达式,加减表达式。乘除表达式,最后是最小的语法单元(数字。变量以及用括号括起来的表达式)。
以下是处理逻辑表达式的函数lexp,其逻辑与其语法规则是相应的:
/*逻辑表达式*/ TreeNode *lexp() { TreeNode *node; TreeNode *lnode, *rnode; node = exp(); while ((GT == token) || (LT == token) ||(GE == token) || (LE == token) || (NE == token) || (EQ == token)) { /*左节点*/ lnode = node; /*操作符节点,即根节点*/ node = newNode(); node->attr.e = OpK; node->val.tt = token; node->child[0] = lnode; match(token); /*右节点*/ rnode = exp(); node->child[1] = rnode; } return node; }因为逻辑操作符的左側是算术表达式,所以先调用exp来处理算术表达式。接下来推断算术表达式后面是否跟着逻辑操作符,假设是的话。就处理逻辑操作符右側的算术表达式。while循环用来处理逻辑操作符连续的情况,逻辑操作符的为左结合。
if的语法树求值
以下的代码是calc函数中相应if语句求值的分支:
<span style="white-space:pre"> </span>case IfK: val = calc(node->child[0]); /*计算条件表达式*/ if (val) { val = calc(node->child[1]); /*计算then*/ } else if (node->child[2] != NULL) { val = calc(node->child[2]); } break;能够看出代码非常easy,先调用calc对if语法树的第一个子结点求值。该子结点相应条件表达式,检查返回值假设为真,则计算then后的语句块,否则检查是否存在else语句。假设存在就计算else后语句块的值。
repeat的语法树求值
以下代码是calc函数相应repeat语句的求值的分支:
case RepeatK: do { val = calc(node->child[0]); /*先运行语句块*/ val1 = calc(node->child[1]); /*计算条件表达式的值*/ }while (val1 == 0); break;能够看出求值的过程相当简单,先计算repeat后面语句块的值,然后计算until后条件表达式的值,假设条件表达式的值为假。则循环一直继续,当条件表达式的值为真时,退出循环。
保留字
if和repeat语句中出现的这些控制流程的字符串都是保留字,比方if, then, end,repeat,until,以下是在代码中用到的保留字
Reserve reservewords[] = { {"if", IF}, {"then", THEN}, {"else", ELSE}, {"end", END}, {"repeat", REPEAT}, {"until", UNTIL}, {NULL, NONUSE} };在getToken函数中解析到一个ID类型的token后,会推断是否是保留字,假设是保留字。就返回保留字对应的token类型。
位于getToken函数最后的这段代码运行了推断保留字的任务:
<span style="white-space:pre"> </span>tval[index] = '