[原创]编译器实践 - 熟悉语法,分析特点
格式乱了,可以看BLOG格式很规则:http://blog.二 熟悉语法,分析特点
上一篇我们了解了目标汇编,并创建了运行环境——TMY虚拟机。接下来,我们看看要将什么样的小语言翻译成目标汇编。
程序结构很简单,它在语法上与Ada或Pascal的语法相似:仅是一个由分号分隔开的语句序列。另外,它既无过程也无声明。所有的变量都是整型变量,通过对其赋值可较轻易地声明变量(类似FORTRAN或BASIC)。它只有两个控制语句:if语句和repeat语句,这两个控制语句本身也可包含语句序列。If语句有一个可选的else部分且必须由关键字end结束。除此之外,read语句和write语句完成输入/输出。并且声明变量、赋值和输出输入语句必须以“;”结束。在花括号中可以有注释,但注释不能嵌套。
举例:
read x; { input an integer }
if 0 < x then { don't compute if x <= 0 }
fact := 1;
repeat
fact := (fact * x);
x := x - 1;
until x = 0;
write fact; { output factorial of x }
else
write x;
end
代码1
我们统计一下此种语言的关键字和特殊符号:
关键字:
1. read:输入语句,输入一个变量。eg:read x;
2. write:输出语句,打印一个变量。eg:write fact;
3. if then else end:if条件控制语句。
4. repeat until:循环控制语句。
特殊符号:
1. +, -, *, /:加减乘除算数符号。
2. :=:赋值符号。
3. =, >, <:等于,大于,小于比较符号。
4. ;:结束符。声明变量、赋值和输出输入语句必须以此结尾。
5. ():括弧,可改变优先级。
6. {}:注释花括弧,在花括号中可以有注释,但注释不能嵌套。
我们看到,此种语言其实很简单,严格说甚至不能称为完整的语言。他没有数据类型的区别,也没有函数的概念。但他足以带领我们进入编译器的大门。
下面,我们来看看任何编译器工作的一个核心概念:语法树。
大家都知道编译器实质就是个翻译程序,一般来说他将一种较高级语言翻译成一种较低级语言。而这个过程需要将较高级语言的代码构建成一个特殊的数据结构,这样才方便根据此数据结构产生目标较低级语言代码。这个特殊的数据结构就是语法树。
我们来看看上面介绍的语言在构成语法树时是怎样个情形:
1) if 语句(带有3个可能的孩子)如下所示
" border="0" />
图1
2) repeat 语句有两个孩子。第1个是表示循环体的语句序列,第2个是一个测试表达式:
" border="0" />
图2
3) assign 语句有一个表示其值是被赋予的表达式的孩子(被赋予的变量名保存在语句节点中):
" border="0" />
图3
4) write 语句也有一个孩子,它表示要写出值的表达式:
" border="0" />
图4
5)算符表达式有两个孩子,它们表示左操作数表达式和右操作数表达式:
" border="0" />
图5
6)其他所有的节点(read语句、标识符表达式和常量表达式)都是叶子节点。
一旦我们可以构建出这样的数据结构类型,在转换到汇编的时候就很简单了。我们将设
计如下数据结构来配合以上的6种树:
typedef struct treeNode
{
struct treeNode * child[3]; /* 最多3个叶子 */
struct treeNode * sibling; /* 下一个子结构 */
struct treeNode * father; /* 上一个父结构 */
int lineno; /* 此语句在文本中源代码中的行号 */
int bcliang; /* 若为变/常量,记录此变/常量在内存中的位置 */
struct treeNode2 /* 属性结构 */
{
TokenType op; /* 若为关键字,标记为何种关键字 */
int qx; /* 若为计算符,标记权限值,实现先后计算 */
char libian; /* if, repeat等语句需要返回历遍时的标记, 用在打印语法树 */
char csdm; /* 同上, 用在转化为汇编代码 */
char name[20]; /* 若为变量,记录变量名称 */
} attr;
}TreeNode;
结构表2
我们看一个例子:
比如上面的 read 语句,在此结构中,将使用到蓝色标记的两个属性。其中 op = READ 表示此节点为 read 语句;name = 变量名称,以便在变量表中查找此变量对应位置,读入一个数值到对应内存。
我看看这样一个结构在生成汇编代码时的过程:
switch (Head->attr.op)
{
case READ: /* 为read语句开始翻译 */
fprintf(fp2, "% 3d: % 6s 0,0,0\n", lineNo++, "IN");
for (i = 0; i < strCLJLCount; i++) /* 查找变量在内存中的位置 */
if (!strcmp(strCLJL[i], Head->attr.name)) break;
/* 生成读入数值到对应内存的汇编代码 */
fprintf(fp2, "% 3d: % 6s 0,%d(6)\n", lineNo++, "ST", i);
……
break;
}
代码3
我们来看看上面 代码1 如果建立好结构树将是如何的样子:
代码1.JPG" border="0" />
图6
通过 图6 我们可以很直观的感受到,语法树很形象合理的表述出了一段小语言代码。而后,我们只要历遍此语法树就可以生成对应的汇编代码。
有一定基础的朋友,看到这里应该大体明白了小语言在转化到汇编代码的中间秘诀了。对于此种小语言的确比较简单,所以我们的进度很快。通过很简明的讲解,我们就大致就明摆了翻译的最核心秘诀 语法树 的重要作用。
从下章起,我们将一起一步一步的结合代码来看如何将以上的概念和主意用代码来实现。
附件:小语言编译器[包含具体代码]
http://www.