BIGBALLON
1
从PL/0到Flex
——记录我的PL/0学习过程
在编译原理这门课程的学习中,我们曾经接触到过 PL/0 语言,当时只知道
有这么一门语言,并未深究,恰逢课程设计,我们需要对 C语言实现的 PL/0编
译器进行扩展,因此,我再次接触到了 PL/0语言。
得知 PL/0 语言是 Pascal 语言的一个子集,并且是 Pascal 语言的设计者
Niklaus Wirth在其专著《Algorithms + Data Structures = Programs》
一书中给出的。看到书名,顿生敬佩之情,遂拜读之。时间关系,仓促地阅读了
小部分,但是足矣感受到 Niklaus Wirth这位 Pascal之父的伟大,“算法+数
据结构=程序”这句话虽然是过去面向过程时候的经典概括,但是一直到今,它的
本质是毋庸置疑的。于此同时,书中的最后章节,给出了 PL/0语言的规范和定
义,这也是我们研究的 LP/0语言最原始的出处,希望以后有机会,能够仔细阅
读这本圣经。
初次接触 PL/0语言,希望记录下我的学习过程,于是,本文诞生。
本文主要对 LP/0 编译器的源代码进行分析,同时,也将介绍具体实现的扩
展功能,另外,本文的任何观点都属于本人的个人观点,并不完全正确,本文提
及的所有资料,均可以在 PL/0 下载。
如果你有任何问题、建议或者是建议,非常欢迎一起讨论!
联系方式:
Email:[email protected]
Blog:http://www.cnblogs.com/BigBallon/
BIGBALLON
BIGBALLON
2
目录
一、从原书说开去 ........................................................................................................................... 3
1.1 PL/0 语言简介 ............................................................................................................... 3
1.2 PL/0 语言编译器 ........................................................................................................... 4
1.3 一些说明 .......................................................................................................................... 5
二、PL/0 编译器源码分析 .............................................................................................................. 6
2.1 pl0.h ............................................................................................................................... 6
2.2 pl0.c ............................................................................................................................... 9
三、PL/0编译器功能的扩展 ....................................................................................................... 33
3.0 扩展任务说明 ................................................................................................................ 33
3.1 注释功能扩展 ................................................................................................................ 33
3.2 类型功能的扩充(Boolean real array) ................................................................ 34
3.3 else子句和 exit语句功能扩展 ............................................................................... 38
3.4 输入输出语句功能扩展 ................................................................................................ 38
3.5 带参数的过程功能 和 函数功能 扩充 ...................................................................... 43
3.6 数组类型功能的扩展 .................................................................................................... 47
四、关于 Flex ............................................................................................................................... 48
4.1 Flex简介 ...................................................................................................................... 48
4.2 Windows下配置和使用 Flex ...................................................................................... 49
BIGBALLON
3
一、从原书说开去
Niklaus Wirth 在他的著作《Algorithms + Data Structures = Programs》
的第五章中,介绍了语言结构和编译程序,其中,给出了 PL/0语言的详细说明,同时也给
出了 Pascal实现的 PL/0编译器,而我们这里研究的是 C语言实现的 L/0编译器,其本质
没有任何区别。
PL/0 是一个小巧的高级语言。虽然它只有整数类型,但它是相当完全的可嵌套的分
程序(block)的程序结构,分程序中可以有常量定义、变量声明和无参过程声明,过程
体又是分程序。PL/0 有赋值语句、条件语句、循环语句、过程调用语句、复合语句和空
语句。由于上面这些语言概念已为大家熟知,因此不再进行语义解释。下面给出了PL/0
语言的文法。
1.1 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 | ε
Cond → odd Exp | Exp RelOp Exp
RelOp → = | <> | < | > | <= | >=
Exp → [+ | − ] Term {+ Term | − Term}
Term → Factor {∗ Factor | / Factor}
Factor → ident | number | ( Exp )
ident 是字母开头的字母数字串
number 是无符号整数
begin、call、const、do、end、if、odd、procedure、then、var、while 是保留字
BIGBALLON
4
1.2 PL/0 语言编译器
在《算法+数据结构=程序》一书中,Niklaus Wirth 设计的PL/0 语言编译器分成
两部分,把源语言翻译成中间语言的编译器和中间语言解释器,编译器用的是递归下降的
预测分析方法,并采用了一种紧急方式的错误恢复方法。
中间语言是一种栈机器代码,其指令集是根据 PL/0 语言的需要来设计的。
它包括下列指令:
(1)lit:将常数装入栈顶的指令;
(2)lod:将变量的值装入栈顶的指令;
(3)sto:对应于赋值语句的存储指令;
(4)cal:对应于过程调用的指令;
(5)int:增加栈顶寄存器的值,完成对局部变量的存储分配的指令;
3
(6)jmp,jpc:对应条件语句和循环语句的无条件转移和条件转移的控制转移指令;
(7)opr:包括一组算术和关系运算的指令。
一条指令由三个域组成:
(1)操作码f:上面已经列出了所有8 种操作码。
(2)层次差l:这里的层次差就是5.3.2 节介绍嵌套深度时的np − na。该域仅用于存取
指令和调用指令。
(3)多用途a:在运算指令中,a 的值用来区分不同的运算;在其他情况,a 或是一个
数(lit,int),或是一个程序地址(jmp,jpc,cal),或是一个数据地址(lod,
sto)。
编译器对 PL/0 源程序进行一遍扫描,并逐行输出源程序。在源程序无错的情况下,编
译器每编译完一个分程序,就列出该分程序的代码,这由编译器的listcode 过程完成。
每个分程序的第一条指令是jmp 指令,其作用是绕过该分程序声明部分产生的代码(即绕
过内嵌过程的代码)。listcode 过程没有列出这条代码。
BIGBALLON
5
1.3 一些说明
对 PL/0编译器执行的流程,这里简要说明一下。
1.) 编译器启动
2.) 首先做一些必要的初始化工作,具体包括 对保留字表( word )、保留字表中每
一个保留字对应的 symbol类型( wsym )、部分符号对应的 symbol类型表( ssym )、类
PCODE 指令助记符表( mnemonic )、声明开始集合( declbegsys )、表达式开始集合
( statbegsys )、项开始符号集合( facbegsys )以及一些全局变量的初始化。
3.) 首次调用 getsym()进行词法分析。
4.) 调用 block()过程,block()中还包括了词法分析和语法分析等
5.) 最后调用 interpret()解释执行目标程序
BIGBALLON
6
二、PL/0 编译器源码分析
PL/0 编译器的源码最早是作者使用 Pascal 编写的,后被改写为 c 语言版本,在课程
设计时我拿到的是一份 c语言版本的代码,所以这里也将对这份代码进行简单的分析。
2.1 pl0.h
#include <stdio.h>
#define norw 11 /* 保留字的数量 */
#define txmax 100 /* 标识符表的长度 */
#define nmax 14 /* 数字允许的最长位数 */
#define al 10 /* 标识符最大长度 */
#define amax 2047 /* 寻址空间 */
#define levmax 3 /* 最大允许的嵌套层数 */
#define cxmax 2000 /* 目标代码数组的长度 */
#define nul 0x1 /* 空 */
#define ident 0x2 /* 标识符 */
#define number 0x4 /* 数值 */
#define plus 0x8 /* + */
#define minus 0x10 /* - */
#define times 0x20 /* * */
#define slash 0x40 /* / */
#define oddsym 0x80 /* 奇数判断 */
#define eql 0x100 /* = */
#define neq 0x200 /* <> */
#define lss 0x400 /* < */
#define leq 0x800 /* <= */
#define gtr 0x1000 /* > */
#define geq 0x2000 /* >= */
#define lparen 0x4000 /* ( */
#define rparen 0x8000 /* ) */
#define comma 0x10000 /* , */
#define semicolon 0x20000 /* ; */
#define period 0x40000
#define becomes 0x80000
BIGBALLON
7
#define beginsym 0x100000
#define endsym 0x200000
#define ifsym 0x400000
#define thensym 0x800000
#define whilesym 0x1000000
#define dosym 0x2000000
#define callsym 0x4000000
#define constsym 0x8000000
#define varsym 0x10000000
#define procsym 0x20000000
enum object /* object为三种标识符的类型 */
{
constant, variable, proc
};
enum fct /* fct类型分别标识类 PCODE的各条指令 */
{
lit, opr, lod, sto, cal, Int, jmp, jpc // functions
};
/*
lit 0, a : load constant a
opr 0, a : execute operation a
lod l, a : load variable l, a
sto l, a : store variable l, a
cal l, a : call procedure a at level l
Int 0, a : increment t-register by a
jmp 0, a : jump to a
jpc 0, a : jump conditional to a
*/
typedef struct /* 类 PCODE指令类型,包含三个字段:指令 f、层差 l和另一个操作数 a */
{
enum fct f; // function code
long l; // level
long a; // displacement address
} instruction;
char* err_msg[] =
{
/* 0 */ "",
/* 1 */ "Found ':=' when expecting '='.",
/* 2 */ "There must be a number to follow '='.",
BIGBALLON
8
/* 3 */ "There must be an '=' to follow the identifier.",
/* 4 */ "There must be an identifier to follow 'const', 'var', or 'procedure'.",
/* 5 */ "Missing ',' or ';'.",
/* 6 */ "Incorrect procedure name.",
/* 7 */ "Statement expected.",
/* 8 */ "Follow the statement is an incorrect symbol.",
/* 9 */ "'.' expected.",
/* 10 */ "';' expected.",
/* 11 */ "Undeclared identifier.",
/* 12 */ "Illegal assignment.",
/* 13 */ "':=' expected.",
/* 14 */ "There must be an identifier to follow the 'call'.",
/* 15 */ "A constant or variable can not be called.",
/* 16 */ "'then' expected.",
/* 17 */ "';' or 'end' expected.",
/* 18 */ "'do' expected.",
/* 19 */ "Incorrect symbol.",
/* 20 */ "Relative operators expected.",
/* 21 */ "Procedure identifier can not be in an expression.",
/* 22 */ "Missing ')'.",
/* 23 */ "The symbol can not be followed by a factor.",
/* 24 */ "The symbol can not be as the beginning of an expression.",
/* 25 */ "",
/* 26 */ "",
/* 27 */ "",
/* 28 */ "",
/* 29 */ "",
/* 30 */ "",
/* 31 */ "The number is too great.",
/* 32 */ "There are too many levels."
};
char ch; // 用于词法分析器,存放最近一次从文件中读出的字符
unsigned long sym; // 词法分析器输出结果之用,存放最近一次识别出来的 token 的类型
char id[al+1]; // 词法分析器输出结果之用,存放最近一次识别出来的标识符的名字
long num; // 词法分析器输出结果之用,存放最近一次识别出来的数字的值
long cc; // 行缓冲区的列指针
long ll; // 行缓冲区长度
long kk; /* 引入此变量是出于程序性能考虑,见 getsym过程注释 */
long err; /* 出错总次数 */
long cx; // 代码分配指针,代码生成模块总在 cx所指位置生成新的代码
char line[81]; /* 行缓冲区,用于从文件读出一行,供词法分析获取单词时之用 */
BIGBALLON
9
char a[al+1]; /* 词法分析器中用于临时存放正在分析的词 */
instruction code[cxmax+1];/* 生成的类 PCODE代码表,存放编译得到的类 PCODE代码 */
char word[norw][al+1];/* 保留字表 */
unsigned long wsym[norw];/* 保留字表中每一个保留字对应的 symbol类型 */
unsigned long ssym[256];/*一些符号对应的 symbol类型表*/
char mnemonic[8][3+1]; /* 类 PCODE指令助记符表 */
unsigned long declbegsys, statbegsys, facbegsys; /* 声明开始、表达式开始和项开始符号集
合 */
struct /* 符号表 */
{
char name[al+1]; /* 符号的名字 */
enum object kind;
long val; /* 如果是常量名 val中放常量的值 */
long level; /* 如果是变量名或过程名,存放层差、偏移地址和大小 */
long addr;
}table[txmax+1];
char infilename[256];
FILE* infile;
// the following variables for block
long dx; // data allocation index
long lev; // current depth of block nesting
long tx; // current table index
// the following array space for interpreter
#define stacksize 50000
long s[stacksize]; // datastore
2.2 pl0.c
// pl/0 compiler with code generation
#include <stdlib.h>
#include <string.h>
#include "pl0.h"
BIGBALLON
10
/*错误处理过程 error*/
/*打印相对应的错误信息 , n 为错误代号*/
void error(long n)
{
long i;
printf("Error=>");
for (i = 1; i <= cc-1; i++)
{
printf(" ");
}
printf("|%s(%d)\n", err_msg[n], n);
err++;
}
/* 读取下一个字符过程 getch( );*/
void getch()
{
if(cc == ll) //若果 cc 和 ll 相等,表示第一开始读取或者是上一次已经读取完一行,那么就再
读一行
{
if(feof(infile))
{
printf("************************************\n");
printf(" program incomplete\n");
printf("************************************\n");
exit(1);
}
ll = 0; cc = 0;
printf("%5d ", cx);
while((!feof(infile)) && ((ch=getc(infile))!='\n')) //判断是否为文件结尾或者是
一行的结尾
{
printf("%c", ch);
ll = ll + 1;
line[ll] = ch;
}
printf("\n");
BIGBALLON
11
ll = ll + 1;
line[ll] = ' ';
}
//如果前一次读取的一行还在进行词法分析,那么一个一个字符读取
cc = cc + 1;
ch = line[cc];
}
/* 词法分析过程 */
void getsym()
{
long i, j, k;
while(ch == ' ' || ch == '\t') //先读取一行
{
getch();
}
if(isalpha(ch)) // 如果当前 token的第一个字符 ch为字母,判断是否为标识符,或者是保留
字
{
k = 0; // 用于记录当前 token的长度
do
{
if(k < al) //保证 k 小于标识符最大长度
{
a[k] = ch; k = k + 1;
}
getch();
} while(isalpha(ch) || isdigit(ch)); //一直读取直到标识符读完
//处理尾部多余的字符,该程序是用' '来处理的
if(k >= kk)
{
kk = k;
}
else
{
BIGBALLON
12
do
{
kk = kk-1; a[kk] = ' '; //对尾部进行处理
} while(k < kk);
}
strcpy(id, a); i = 0; j = norw - 1; //id存放当前串的名字
//运用二分法在保留字表中查找该串是否存在,存在则为保留字,否则为标识符
do
{
k = (i+j)/2;
if(strcmp(id, word[k]) <= 0)
{
j = k - 1;
}
if(strcmp(id, word[k]) >=0)
{
i = k + 1;
}
} while(i <= j);
if(i-1 > j)
{
sym = wsym[k];
}
else
{
sym = ident;
}
}
else if(isdigit(ch)) // 如果当前 token的第一个字符 ch为数字,解析这个串,将其变为数值
{
k = 0; num = 0; sym = number;
do
{
num = num * 10 + (ch - '0');
k = k + 1;
getch();
} while(isdigit(ch));
if(k > nmax) //如果 k 超过了 nmax , 抛出 31 号错误 "The number is too
BIGBALLON
13
great."
{
error(31);
}
}
else if(ch == ':') // 如果当前 token的第一个字符 ch为':', 判断是否为复制符号
{
getch();
if(ch == '=')
{
sym = becomes; getch();
}
else
{
sym = nul;
}
}
else if(ch == '<')
{
getch();
if(ch == '=')
{
sym = leq; getch();
}
else if(ch == '>')
{
sym=neq; getch();
}
else
{
sym = lss;
}
}
else if(ch == '>')
{
getch();
if(ch == '=')
{
sym=geq; getch();
}
else
BIGBALLON
14
{
sym=gtr;
}
}
else /* 如果读到不是字母也不是数字也不是冒号也不是小于号也不是大于号 */
{/* 那就说明它不是标识符/保留字,也不是复杂的双字节操作符,应该是一个普通的符号 */
sym = ssym[(unsigned char)ch]; getch();/* 直接成符号表中查到它的类型,赋给 sym */
}
}
/* 词法分析过程 getsym总结:从源文件中读出若干有效字符,组成一个 token串,识别它的类型
为保留字/标识符/数字或是其它符号。如果是保留字,把 sym置成相应的保留字类型,如果是
标识符,把 sym置成 ident表示是标识符,于此同时,id变量中存放的即为保留字字符串或标识
符名字。如果是数字,把 sym置为 number,同时 num变量中存放该数字的值。如果是其它的操作符,
则直接把 sym置成相应类型。经过本过程后 ch变量中存放的是下一个即将被识别的字符 */
/* 目标代码生成过程 gen */
/* 参数:x:要生成的一行代码的助记符 */
/* y, z:代码的两个操作数 */
/* 本过程用于把生成的目标代码写入目标代码数组,供后面的解释器解释执行 */
void gen(enum fct x, long y, long z)
{
if(cx > cxmax)
{
printf("program too long\n");
exit(1);
}
code[cx].f = x; code[cx].l = y; code[cx].a = z;
cx = cx + 1;
}
/* 测试当前单词是否合法过程 test */
/* 参数:s1:当语法分析进入或退出某一语法单元时当前单词符合应属于的集合 */
/* s2:在某一出错状态下,可恢复语法分析正常工作的补充单词集合 */
/* n:出错信息编号,当当前符号不属于合法的 s1集合时发出的出错信息 */
void test(unsigned long s1, unsigned long s2, long n)
{
if (!(sym & s1))/* 如果当前符号不在 s1中 */
{
error(n); /* 发出 n号错误 */
s1 = s1 | s2; /* 把 s2集合补充进 s1集合 */
BIGBALLON
15
while(!(sym & s1))/* 通过循环找到下一个合法的符号,以恢复语法分析工作 */
{
getsym();
}
}
}
/* 登陆符号表过程 enter */
/* 参数:k:欲登陆到符号表的符号类型 */
void enter(enum object k) // enter object into table
{
tx = tx + 1; /* 符号表指针指向一个新的空位 */
strcpy(table[tx].name, id); /* name 是符号的名字,对于标识符,这里就是标识符的名字
*/
table[tx].kind = k; /* 符号类型,可能是常量、变量或过程名 */
switch(k)
{
case constant:
if(num > amax)
{
error(31);
num = 0;
}
table[tx].val = num;
break;
case variable: /* 如果是变量名 */
table[tx].level = lev; /* 记下它所属的层次号 */
table[tx].addr = dx; /* 记下它在当前层中的偏移量 */
dx = dx + 1; /* 偏移量自增一,为下一次做好准备 */
break;
case proc:
table[tx].level = lev; /* 如果要登陆的是过程名 */
break; /* 记录下这个过程所在层次 */
}
}
/* 登录符号过程没有考虑到重复的定义的问题。如果出现重复定义,则以最后一次的定义为准。 */
BIGBALLON
16
/* 在符号表中查找指定符号所在位置的函数 position */
/* 参数:id:要找的符号 */
/* 返回值:要找的符号在符号表中的位置,如果找不到就返回 0 */
long position(char* id) // find identifier id in table
{
long i;
strcpy(table[0].name, id); /* 先把 id放入符号表 0号位置 */
i=tx;/* 从符号表中当前位置也即最后一个符号开始找 */
while(strcmp(table[i].name, id) != 0)/* 如果当前的符号与要找的不一致 */
{
i = i - 1;/* 找前面一个 */
} /* 返回找到的位置号,如果没找到则一定正好为 0 */
return i;
}
/* 常量声明处理过程 constdeclaration */
void constdeclaration()
{
if(sym == ident)/* 常量声明过程开始遇到的第一个符号必然应为标识符 */
{
getsym(); /* 常量声明过程开始遇到的第一个符号必然应为标识符 */
if(sym == eql || sym == becomes) /* 如果是等号或赋值号 */
{
if(sym == becomes) /* 如果是赋值号(常量生明中应该是等号),抛出 1号错误 */
{
error(1); //"Found ':=' when expecting '='."
}
getsym();
if(sym == number) /* 如果是等号 ,登陆到名字表 */
{
enter(constant);
getsym();
}
else
{
BIGBALLON
17
error(2);
}
}
else
{
error(3);
}
}
else
{
error(4);
}
}
/* 变量声明处理过程 vardeclaration */
void vardeclaration()
{
if(sym == ident)
{
enter(variable); /* 将标识符登陆到符号表中 */
getsym();/* 获取下一个 token,为后面作准备 */
}
else
{
error(4);
}
}
void listcode(long cx0) // list code generated for this block
{
long i;
for(i=cx0; i<=cx-1; i++)
{
printf("%10d%5s%3d%5d\n", i, mnemonic[code[i].f], code[i].l, code[i].a);
}
}
void expression(unsigned long);
/* 因子处理过程 factor */
/* 参数说明:fsys: 如果出错可用来恢复语法分析的符号集合 */
void factor(unsigned long fsys)
BIGBALLON
18
{
long i;
test(facbegsys, fsys, 24); /* 开始因子处理前,先检查当前 token是否在 facbegsys集合
中。 */
/* 如果不是合法的 token,抛 24号错误,并通过 fsys集恢复使语法处理可以继续进行 */
while(sym & facbegsys)
{
if(sym == ident) /* 如果遇到的是标识符 */
{
i = position(id);/* 查符号表,找到当前标识符在符号表中的位置 */
if(i==0) /* 如果查符号表返回为 0,表示没有找到标识符 */
{
error(11);
}
else
{
switch(table[i].kind)
{
case constant: /* 如果这个标识符对应的是常量,值为 val,生成 lit指令,
把 val放到栈顶 */
gen(lit, 0, table[i].val);
break;
case variable: /* 如果标识符是变量名,生成 lod指令, */
/* 把位于距离当前层 level的层的偏移地址为 adr的变量放到栈顶 */
gen(lod, lev-table[i].level, table[i].addr);
break;
case proc:/* 如果在因子处理中遇到的标识符是过程名,出错了,抛 21号错 */
error(21);
break;
}
}
getsym();/* 获取下一 token,继续循环处理 */
}
else if(sym == number)/* 如果因子处理时遇到数字 */
{
if(num>amax)
{
error(31); num=0;
BIGBALLON
19
}
gen(lit,0,num);/* 生成 lit指令,把这个数值字面常量放到栈顶 */
getsym();
}
else if(sym == lparen) /* 如果遇到的是左括号 */
{
getsym();/* 获取一个 token */
expression(rparen|fsys);/* 递归调用 expression子程序分析一个子表达式 */
if(sym==rparen)/* 子表达式分析完后,应遇到右括号 */
{
getsym();/* 如果的确遇到右括号,读取下一个 token */
}
else
{
error(22); /* 否则抛出 22号错误 */
}
}
test(fsys,lparen,23);/* 一个因子处理完毕,遇到的 token应在 fsys集合中 */
/* 如果不是,抛 23号错,并找到下一个因子的开始,使语法分析可以继续运行下去 */
}
}
void term(unsigned long fsys)
{
unsigned long mulop;
factor(fsys|times|slash);
/* 每一个项都应该由因子开始,因此调用 factor子程序分析因子 */
while(sym==times || sym==slash)/* 一个因子后应当遇到乘号或除号 */
{
mulop = sym; /* 保存当前运算符 */
getsym();
factor(fsys|times|slash);/* 运算符后应是一个因子,故调 factor子程序分析因子 */
if(mulop == times) /* 如果刚才遇到乘号 */
{
gen(opr,0,4);
}
else/* 不是乘号一定是除号,生成除法指令 */
BIGBALLON
20
{
gen(opr,0,5);
}
}
}
void expression(unsigned long fsys)
{
unsigned long addop;
if(sym==plus || sym==minus)/* 一个表达式可能会由加号或减号开始,表示正负号 */
{
addop=sym; /* 把当前的正号或负号保存起来,以便下面生成相应代码 */
getsym();
term(fsys|plus|minus); /* 正负号后面应该是一个项,调 term子程序分析 */
if(addop==minus) /* 如果保存下来的符号是负号 */
{
gen(opr,0,1); /* 生成一条 1号操作指令:取反运算 */
}
}
else /* 如果不是由正负号开头,就应是一个项开头 */
{
term(fsys|plus|minus);
}
while(sym==plus || sym==minus) /* 项后应是加运算或减运算 */
{
addop=sym; /* 把运算符保存下来 */
getsym();
term(fsys|plus|minus); /* 调 term子程序分析项 */
if(addop==plus)/* 如果项与项之间的运算符是加号 */
{
gen(opr,0,2);
}
else
{
gen(opr,0,3);
}
}
}
BIGBALLON
21
void condition(unsigned long fsys)
{
unsigned long relop;
/* 用于临时记录 token(这里一定是一个二元逻辑运算符)的内容 */
if(sym==oddsym)/* 如果是 odd运算符(一元) */
{
getsym();
expression(fsys); /* 如果是 odd运算符(一元) */
gen(opr, 0, 6);/* 生成 6号操作指令:奇偶判断运算 */
}
else /* 如果不是 odd运算符(那就一定是二元逻辑运算符) */
{
expression(fsys|eql|neq|lss|gtr|leq|geq);/* 对表达式左部进行处理计算 */
if(!(sym&(eql|neq|lss|gtr|leq|geq))) /* 如果 token不是逻辑运算符中的一个 */
{
error(20);
}
else
{
relop=sym; /* 记录下当前的逻辑运算符 */
getsym();
expression(fsys);/* 对表达式右部进行处理计算 */
switch(relop)
{
case eql:
gen(opr, 0, 8);
break;
case neq:
gen(opr, 0, 9);
break;
case lss:
gen(opr, 0, 10);
break;
case geq:
gen(opr, 0, 11);
break;
BIGBALLON
22
case gtr:
gen(opr, 0, 12);
break;
case leq:
gen(opr, 0, 13);
break;
}
}
}
}
/* 语句处理过程 statement */
/* 参数说明:fsys: 如果出错可用来恢复语法分析的符号集合 */
void statement(unsigned long fsys)
{
long i,cx1,cx2;
if(sym==ident) /* 所谓"语句"可能是赋值语句,以标识符开头 */
{
i=position(id); /* 在符号表中查到该标识符所在位置 */
if(i==0)
{
error(11);
}
else if(table[i].kind!=variable) // assignment to non-variable
{
error(12); i=0;
}
getsym(); /* 获得下一个 token,正常应为赋值号 */
if(sym==becomes) /* 如果的确为赋值号 */
{
getsym(); /* 获取下一个 token,正常应为一个表达式 */
}
else
{
error(13); /* 处理表达式 */
}
expression(fsys);
BIGBALLON
23
if(i!=0)/* 如果不曾出错,i将不为 0,i所指为当前语名左部标识符在符号表中的位置 */
{
/* 产生一行把表达式值写往指定内存的 sto目标代码 */
gen(sto,lev-table[i].level,table[i].addr);
}
}
else if(sym==callsym) /* 如果是 call语句 */
{
getsym();
if(sym!=ident) /* 如果 call后跟的不是标识符 */
{
error(14); /* 抛出 14号错误 */
}
else
{
i=position(id); /* 从符号表中找出相应的标识符 */
if(i==0)
{
error(11);
}
else if(table[i].kind==proc)/* 如果这个标识符为一个过程名 */
{
gen(cal,lev-table[i].level,table[i].addr);/*生成 cal目标代码,呼叫这个过
程 */
}
else
{
error(15);/* 如果 call后跟的不是过程名,抛出 15号错误 */
}
getsym(); /* 获取下一 token,为后面作准备 */
}
}
else if(sym==ifsym) /* 如果是 if语句 */
{
getsym(); /* 获取一 token应是一个逻辑表达式 */
condition(fsys|thensym|dosym); /* 对逻辑表达式进行分析计算,出错恢复集中加入
then和 do语句 */
if(sym==thensym) /* 对逻辑表达式进行分析计算,出错恢复集中加入 then和 do语句 */
{
getsym();
}
BIGBALLON
24
else
{
error(16);
}
cx1=cx; gen(jpc,0,0);
statement(fsys); /* 分析 then后的语句 */
code[cx1].a=cx;
}
else if(sym==beginsym) /* 如果遇到 begin */
{
getsym(); statement(fsys|semicolon|endsym);
while(sym==semicolon||(sym&statbegsys))
{
if(sym==semicolon)
{
getsym();
}
else
{
error(10);
}
statement(fsys|semicolon|endsym);
}
if(sym==endsym)
{
getsym();
}
else
{
error(17);
}
}
else if(sym==whilesym)
{
cx1=cx; getsym();
condition(fsys|dosym);
cx2=cx; gen(jpc,0,0);
if(sym==dosym)
{
getsym();
}
else
{
error(18);
BIGBALLON
25
}
statement(fsys); gen(jmp,0,cx1);
code[cx2].a=cx;
}
test(fsys,0,19);
}
/* 语法分析过程 block */
/* 参数:lev:这一次语法分析所在的层次 */
/* tx:符号表指针 */
/* fsys:用于出错恢复的单词集合 */
void block(unsigned long fsys)
{
long tx0; // initial table index
/* 记录本层开始时符号表位置 */
long cx0; // initial code index
/* 记录本层开始时代码段分配位置 */
long tx1; // save current table index before processing nested procedures
long dx1; // save data allocation index
dx=3; /* 地址指示器给出每层局部量当前已分配到的相对位置。
置初始值为 3的原因是:每一层最开始的位置有三个空间用于存放静态链 SL、动态链 DL和
返回地址 RA */
tx0=tx; /* 地址指示器给出每层局部量当前已分配到的相对位置。
置初始值为 3的原因是:每一层最开始的位置有三个空间用于存放静态链 SL、动态链 DL和
返回地址 RA */
table[tx].addr=cx; /* 符号表当前位置记下当前层代码的开始位置 */
gen(jmp,0,0); /* 符号表当前位置记下当前层代码的开始位置 */
if(lev>levmax) /* 如果当前过程嵌套层数大于最大允许的套层数 */
{
error(32);
}
do /* 开始循环处理源程序中所有的声明部分 */
{
if(sym==constsym) /* 如果当前 token是 const 保留字,开始进行常量声明 */
{
getsym();
BIGBALLON
26
do/* 反复进行常量声明 */
{
constdeclaration();
while(sym==comma)
{
getsym();
constdeclaration();
}
if(sym==semicolon)/* 如果常量声明结束,应遇到分号 */
{
getsym();
}
else
{
error(5);
}
} while(sym==ident);/* 如果遇到非标识符,则常量声明结束 */
}
if(sym==varsym)/* 如果当前 token是 var保留字,开始进行变量声明,与常量声明类似 */
{
getsym();
do
{
vardeclaration();
while(sym==comma)
{
getsym(); vardeclaration();
}
if(sym==semicolon)
{
getsym();
}
else
{
error(5);
}
} while(sym==ident);
}
while(sym==procsym) /* 循环声明各子过程 */
{
BIGBALLON
27
getsym();/* 获取下一个 token,此处正常应为作为过程名的标识符 */
if(sym==ident)/* 如果 token确为标识符 */
{
enter(proc); /* 如果 token确为标识符 */
getsym(); /* 获取下一个 token,正常情况应为分号 */
}
else
{
error(4);/* 获取下一个 token,正常情况应为分号 */
}
if(sym==semicolon)
{
getsym();
}
else
{
error(5);
}
lev=lev+1; tx1=tx; dx1=dx;
block(fsys|semicolon);/* 递归调用语法分析过程,当前层次加一,同时传递表头索引、
合法单词符 */
lev=lev-1; tx=tx1; dx=dx1;
if(sym==semicolon)/* 递归返回后当前 token应为递归调用时的最后一个 end后的分号
*/
{
getsym();
test(statbegsys|ident|procsym,fsys,6);/* 检查当前 token 是否合法,不合法
则用 fsys恢复语法分析同时抛 6号错 */
}
else
{
error(5);
}
}
test(statbegsys|ident,declbegsys,7); /* 检查当前状态是否合法,不合法则用声明开
始符号作出错恢复、抛 7号错 */
} while(sym&declbegsys);/* 直到声明性的源程序分析完毕,继续向下执行,分析主程序 */
code[table[tx0].addr].a=cx;/* 把前面生成的跳转语句的跳转位置改成当前位置 */
BIGBALLON
28
table[tx0].addr=cx; // start addr of code
cx0=cx;/* 记下当前代码分配位置 */
gen(Int,0,dx);/* 生成分配空间指令,分配 dx个空间 */
statement(fsys|semicolon|endsym);/* 处理当前遇到的语句或语句块 */
gen(opr,0,0); // return/* 生成从子程序返回操作指令 */
test(fsys,0,8); /* 用 fsys检查当前状态是否合法,不合法则抛 8号错 */
listcode(cx0); /* 列出本层的类 PCODE代码 */
}
/* 通过静态链求出数据区基地址的函数 base */
/* 参数说明:l:要求的数据区所在层与当前层的层差 */
/* 返回值:要求的数据区基址 */
long base(long b, long l)
{
long b1;
b1=b;
while (l>0) // find base l levels down
{
b1=s[b1]; l=l-1;
}
return b1;
}
/*解释过程*/
void interpret()
{
long p,b,t; // program-, base-, topstack-registers
instruction i; // instruction register
printf("start PL/0\n");
t=0; b=1; p=0;
s[1]=0; s[2]=0; s[3]=0;
do
{
i=code[p]; p=p+1; //每次在 code表中读取一条指令
switch(i.f)
{
case lit:
t=t+1; s[t]=i.a;
break;
case opr:
switch(i.a) // operator
{
BIGBALLON
29
case 0: // return
t=b-1; p=s[t+3]; b=s[t+2];
break;
case 1: // 负号
s[t]=-s[t];
break;
case 2: // 加法
t=t-1; s[t]=s[t]+s[t+1];
break;
case 3: // 减法
t=t-1; s[t]=s[t]-s[t+1];
break;
case 4: // 乘法
t=t-1; s[t]=s[t]*s[t+1];
break;
case 5: // 除法
t=t-1; s[t]=s[t]/s[t+1];
break;
case 6: // odd
s[t]=s[t]%2;
break;
case 8: // ==
t=t-1; s[t]=(s[t]==s[t+1]);
break;
case 9: // !=
t=t-1; s[t]=(s[t]!=s[t+1]);
break;
case 10: // <
t=t-1; s[t]=(s[t]<s[t+1]);
break;
case 11: // >=
t=t-1; s[t]=(s[t]>=s[t+1]);
break;
BIGBALLON
30
case 12: // >
t=t-1; s[t]=(s[t]>s[t+1]);
break;
case 13: // <=
t=t-1; s[t]=(s[t]<=s[t+1]);
}
break;
case lod: //将变量的值放到栈顶
t=t+1; s[t]=s[base(b,i.l)+i.a];
break;
case sto: //将栈顶的值送到变量中
s[base(b,i.l)+i.a]=s[t]; printf("%10d\n", s[t]); t=t-1;
break;
case cal: // generate new block mark
s[t+1]=base(b,i.l); s[t+2]=b; s[t+3]=p;
b=t+1; p=i.a;
break;
case Int: //开配内存空间
t=t+i.a;
break;
case jmp: //无条件跳转
p=i.a;
break;
case jpc: //条件挑战
if(s[t]==0)
{
p=i.a;
}
t=t-1;
}
} while(p!=0);
printf("end PL/0\n");
}
int main()
{
long i;
BIGBALLON
31
for(i=0; i<256; i++)
{
ssym[i]=nul;
}
strcpy(word[0], "begin ");
strcpy(word[1], "call ");
strcpy(word[2], "const ");
strcpy(word[3], "do ");
strcpy(word[4], "end ");
strcpy(word[5], "if ");
strcpy(word[6], "odd ");
strcpy(word[7], "procedure ");
strcpy(word[8], "then ");
strcpy(word[9], "var ");
strcpy(word[10], "while ");
wsym[0]=beginsym;
wsym[1]=callsym;
wsym[2]=constsym;
wsym[3]=dosym;
wsym[4]=endsym;
wsym[5]=ifsym;
wsym[6]=oddsym;
wsym[7]=procsym;
wsym[8]=thensym;
wsym[9]=varsym;
wsym[10]=whilesym;
ssym['+']=plus;
ssym['-']=minus;
ssym['*']=times;
ssym['/']=slash;
ssym['(']=lparen;
ssym[')']=rparen;
ssym['=']=eql;
ssym[',']=comma;
ssym['.']=period;
ssym[';']=semicolon;
strcpy(mnemonic[lit],"LIT");
strcpy(mnemonic[opr],"OPR");
strcpy(mnemonic[lod],"LOD");
strcpy(mnemonic[sto],"STO");
strcpy(mnemonic[cal],"CAL");
BIGBALLON
32
strcpy(mnemonic[Int],"INT");
strcpy(mnemonic[jmp],"JMP");
strcpy(mnemonic[jpc],"JPC");
declbegsys=constsym|varsym|procsym;
statbegsys=beginsym|callsym|ifsym|whilesym;
facbegsys=ident|number|lparen;
printf("please input source program file name: ");
scanf("%s",infilename);
printf("\n");
if((infile=fopen(infilename,"r"))==NULL)
{
printf("File %s can't be opened.\n", infilename);
exit(1);
}
err=0;
cc=0;
cx=0;
ll=0;
ch=' '; //初始化 ch 为了第一次读取
kk=al;
getsym();
lev=0; tx=0;
block(declbegsys|statbegsys|period);
if(sym!=period)
{
error(9);
}
if(err==0)
{
// interpret();
}
else
printf("errors in PL/0 program\n");
fclose(infile);
return (0);
}
BIGBALLON
33
三、PL/0编译器功能的扩展
原始的 PL/0 编译器功能并不是很多,课程设计的目的是通过对 PL/0 编译器功能的扩
展从而加深对词法分析和语法分析的理解。事实证明,一个优秀的编译器并不是随随便便就
能写出来的,本次课设,要求完成八个扩展功能,在课设初期艰难地完成了 4 个简单的功
能,但是在实现 PL/0过程和函数功能的扩展以及数组功能的时候遇到了相当多的问题,在
网络中搜集了大量的资料后,勉强实现,但代码还是存在很多 bug,无意中看到一个写得相
当好的 PL/0编译器代码,于是模仿着进行修改,最终完成了八个任务,下面将对每一个功
能的扩展进行简单分析,仅仅提供一些个人的观点和思路,由于单纯叙述比较麻烦,可能会
结合部分代码来分析,但是并不是全部带代码实现,仅仅是一些重要代码,源码可以到这里
下载。
3.0 扩展任务说明
1.) 给 PL/0语言增加 C语言形式的/∗ …… ∗/的注释。
2.) 给 PL/0语言增加带 else子句的条件语句和 exit语句。
exit语句作为 while语句的非正常出口语句。
若处于多层 while语句中,则它只作为最内层 while语句的非正常出口。若它没有
处于任何 while语句中,则是一个错误。
3.) 给 PL/0语言增加输入输出语句。
4.) 给 PL/0语言增加带参数的过程,参数传递按值调用方式。
5.) 给 PL/0语言增加布尔类型,并且布尔类型的表达式按短路方式计算。
6.) 给 PL/0语言增加数组类型。
7.) 给 PL/0语言增加函数类型。
8.) 给 PL/0语言增加实数类型。
3.1 注释功能扩展
注释功能的扩展可以在getch()中进行,也可以在getsym()中进行,在最开始的时候,
我并没有考虑太多关于注释内容内嵌的情况,只是简单地在 getch 中实现,后来发现这是
有问题的,最终在 getsym中实现。
这里有提供一个思路:
处理/* */ 自然是先扫描到/,若再扫描到*,则接下来就是注释的内容,直到扫描到
*/, 在/**/ 内的部分应该被忽略掉,所以,如果你在 getch中进行处理的话,请格外小
心这种跨行的情况,很可能会出错。
/*这是另外一行 *,*号有木有
结束 */begin
write(1);
end.
BIGBALLON
34
所以这里建议在 getsym()中实现。
3.2 类型功能的扩充(Boolean real array)
原来的编译器默认只识别 integer 型,现在将其能识别的类型增加布尔型、实数型以
及数组类型。
首先先修改标识符类型的结构体:
enum object //标识符的类型,常数,变量,过程,类型,函数
{
constant, variable, proc, type, func
};
(函数类型的添加是因为我们后面会扩展函数功能)
其次是修改符号表结构体:
struct /* 符号表 */
{
char name[al+1]; /* 符号的名字 */
enum object kind;
double val; /* 如果是常量名 val中放常量的值 */
long level; /* 如果是变量名或过程名,存放层差、偏移地址和大小 */
long addr;
long funcaddr; //给函数使用,记录函数在符号表中的地址偏移量
LL paral[pnum]; //存放函数参数类型,用于类型检查
LL type1; //在符号表中增加类型项
long size; //若是数组类型还要记录大小
long drt; //数组维数
long low[now]; //数组每一维下界
long high[now]; //数组每一维上界
LL type2; //数组元素的类型
int n; //过程或函数的参数个数
}table[txmax+1]; //符号表的结构
同时,保留字表中也要增加相对应的字符串:
strcpy(word[0], "Boolean ");
strcpy(word[11], "false ");
strcpy(word[24], "true ");
strcpy(word[25], "type ");
strcpy(word[22], "real ");
strcpy(word[14], "integer ");
BIGBALLON
35
添加 类型定义 和 类型分析 函数:
void typeexpression()
{
LL sym1;
long num3, num4, size; //记录数组的上下界以及大小
long tx1;
int drtnum=1;
int j;
if( sym == intersym || sym == realsym || sym == Boolsym )
{
enter(type);
getsym();
}
else if( sym == arraysym )
{
sym1 = sym;
getsym();
if( sym == lmparen )
{
getsym();
if( sym == intersym )
{
num3=num;
getch();
getsym();
if( sym == dotdot )
{
getsym();
sym = sym1;
num4 = num;
size = num4 - num3 + 1;
enter(type);
table[tx].low[drtnum] = num3;
table[tx].high[drtnum] = num4;
table[tx].drt++;
BIGBALLON
36
getsym(); //取得右中括号
getsym(); //取得 of
if( sym == ofsym )
{
getsym();
if( sym == intersym || sym == realsym || sym == Boolsym )
{
table[tx].type2 = sym;
table[tx].drt = 1;
table[tx].size = size;
}
else
{
while( sym == arraysym )
{
//typeexpression();
table[tx].drt++;
drtnum++;
getsym();
if( sym == lmparen )
{
getsym();
table[tx].low[drtnum]=num;
getch();
getsym();
//getch();
getsym();
table[tx].high[drtnum]=num;
getsym();
getsym();
getsym();
size = size*(table[tx].high[drtnum]-
table[tx].low[drtnum]+1);
}
else
{
error(34);
}
table[tx].size = size;
}
BIGBALLON
37
if( sym == intersym || sym == realsym || sym == Boolsym )
{
table[tx].type2 = sym;
}
}
getsym();
}
else
{
error(34);
}
}
else
{
error(34);
}
}
else
{
error(34);
}
}
else
{
error(34);
}
}
else
{
error(33);
}
}
void typedeclaration()
{
char id1[al+1];
if( sym == ident )
{
strcpy( id1, id );
getsym();
if( sym == eql || sym == becomes )
BIGBALLON
38
{
if( sym == becomes )
{
error(1);
}
getsym();
strcpy( id, id1 );
typeexpression(); //传递标识符的名字
}
else
{
error(3);
}
}
else
{
error(4);
}
}
3.3 else子句和 exit语句功能扩展
else 子句的实现也并不困难,基本思路就是在 if-then 之后必定会有一个;,继续扫
描,若果扫描到的是 elsesym,则对 else之后的 token进行扫描并递归进行语句分析,否
则直接对当前的 token进行语法分析。
exit 语句的实现,首先需要一个变量来记录最近的 exit 语句的中间代码的位置
( exitcx ),还需要另外一个变量while循环的层数( whilenum ),如果当前扫描到exit,
且 whilenum != 0 ,记录下 cx 的位置,最后再修改 exit 语句的跳转位置
code[exitcx].a=cx;
3.4 输入输出语句功能扩展
输入输出语句的实现需要对指令集进行扩充,我是在 opr中进行的扩充,opr 0 14代
表的是 read操作,opr 0 15 代表的是读取换行符的操作,opr 0 16 代表的是 write操
BIGBALLON
39
作(输出的是整数),opr 0 24 代表的是 write操作(输出的是小数)
首先需要增加两个保留字 read 和 write:
strcpy(word[21], "read ");
strcpy(word[28], "write ");
其次是增加相对应的 symbol类型:
#define writesym 0x4000000000
#define readsym 0x8000000000
并向 facbegsys集合添加:
statbegsys = beginsym | callsym | ifsym | whilesym | exitsym | writesym | readsym;
facbegsys = ident| intersym | realsym | lparen | not | truesym | falsesym;
在语法分析 statement中对相应的 writesym和 readsym进行处理:
else if( sym == readsym )
{
getsym();
if( sym != lparen )
{
error(35);
}
else
{
do
{
getsym();
if( sym == ident )
{
i = position(id);
}
else
{
i = 0;
}
if(i == 0)
{
error(36);
}
else if( table[i].kind == constant || table[i].kind == proc ||
table[i].type1 == Boolsym )
{ // 不能往常量或过程以及布尔类型读入数据
error(12);
BIGBALLON
40
i = 0;
getsym();
continue;
}
else
{
if (table[i].type1 == intersym || table[i].type1 == realsym )
{ //说明要读的只是一个简单变量
getsym();
gen(opr,0,14);
/*生成指令 opr,0,14 14 在解释函数 interpret()中对应 read*/
gen(sto, lev-table[i].level, table[i].addr);
/*生成指令 sto指令 将刚才读取的数送入栈中*/
}
else
{
if( table[i].type1 == arraysym && ( table[i].type2 &
( intersym | realsym ) ) )
{ //数组类型
getsym();
for(ii=0;ii<table[i].drt;ii++)
{
if(sym==lmparen)
{
getsym();
expre(fsys|rmparen);
if( lastsym != intersym )
{
lastsym = typeerror;
error(46);
}
if( sym == rmparen )
{
getsym();
}
}
else
{
error(46);
}
}
gen(opr,0,14);
arraydo(sto,i);
}
BIGBALLON
41
else
error(39);
}
}
}while( sym == comma );
gen(opr,0,15); //取得换行符
if( sym == rparen )
{
getsym();
}
}
}
else if( sym == writesym )
{
getsym();
if( sym == lparen )
{
do
{
getsym();
expre(fsys|rparen|comma);
if( lastsym != intersym && lastsym != realsym && lastsym !=
constsym )
{
lastsym=typeerror;
error(53);
}
if( lastsym == intersym )
{
gen(opr,0,16); //生成输出指令,输出栈顶的整数
}
else if( lastsym == realsym )
{
gen(opr,0,24); //生成输出指令,输出栈顶的实数
}
}while( sym == comma );
if(sym!=rparen)
{
error(35);
BIGBALLON
42
}
else
{
getsym();
}
gen(opr,0,17); //输出换行符
}
else
{
error(35);
}
在解释器 interpret中对相应的 case进行处理:
case 14: //read指令
j = 0;
getnum = 0;
a = 0;
printf(" read number : ");
for( j = 0; j < 1023; ++j )
{
szbuffer[j] = '\0';
}
scanf( "%s", szbuffer );
tt = 1.0;
for( j = 0; j < strlen(szbuffer) && j < 1023 && szbuffer[j] != '\0';
++j )
{
if( !isdigit( szbuffer[j] ) )
{
if( szbuffer[j] == '.' )
break;
printf( "error!" );
return;
}
getnum = getnum * 10 + ( szbuffer[j] - '0' );
}
if( szbuffer[j] == '.' )
{
tt = 1.0;
for( ++j ; j < strlen(szbuffer) && j < 1023 && szbuffer[j] !=
BIGBALLON
43
'\0'; ++j )
{
tt /= 10;
getnum = getnum + ( szbuffer[j] - '0' ) * tt;
}
}
++t;
s[t] = getnum;
s[base(b,i.l)+(long)i.a] = s[t];
break;
case 15:
ch = getchar(); //读取换行符
break;
case 16: //write指令
printf( "%5d", (long)s[t] );
--t;
break;
3.5 带参数的过程功能 和 函数功能 扩充
这个功能的实现想对来说比较复杂,我们这里另外定义了一个结构体用来记录过程或
函数的参数信息:
struct prod
{
char id[al+1]; //参数名
LL sym; //参数类型
};
定义一个变量来存放参数的个数:
int prodn = 0;
增加保留字:
strcpy(word[12], "function ");
strcpy(word[20], "procedure ");
BIGBALLON
44
在 block过程中进行处理:
struct prod pnow[15]; //记录过程或函数的参数信息
while( sym == procsym || sym == funcsym )
{
prodn = 0;
if( sym == procsym )
{
getsym();
if( sym == ident )
{
enter(proc);
getsym();
}
else
{
error(4);
}
if( sym == semicolon )
{
getsym();
}
else if( sym == lparen ) //有左括号,说明是有参数的过程
{
getsym();
while( sym == ident )
{
strcpy( pnow[prodn].id, id ); //记录参数名
++prodn;
if( ch==':' )
{
getch();
getsym();
}
else
{
error(38);
}
if( sym == intersym || sym == realsym || sym == Boolsym )
{
pnow[prodn-1].sym = sym;
getsym();
}
BIGBALLON
45
else
{
error(39);
}
if( sym == semicolon || sym == rparen )
{
getsym();
}
else
{
error(5);
}
}
if( sym == semicolon )
{
getsym();
}
}
else
{
error(5);
}
//getsym();
}
else if( sym == funcsym )
{ //处理函数
k = 0;
getsym();
if( sym == ident )
{
enter(func);
getsym();
i = position(id);
}
else
{
error(4);
}
if( sym == lparen )
{ //有左括号,说明是有参数的函数
getsym();
while( sym == ident )
BIGBALLON
46
{
strcpy(pnow[prodn].id,id); //记录参数名
prodn++;
if(ch==':')
{
getch();
getsym();
}
else
{
error(38);
}
if( sym == intersym || sym == realsym )
{
table[i].paral[k]=sym;
k++;
pnow[prodn-1].sym=sym;
getsym();
}
else
{
error(39);
}
if( sym == semicolon || sym == rparen )
{
getsym();
}
else
{
error(5);
}
}
}
if( sym == nul )
{
getsym();
if( sym == intersym || sym == realsym || sym == Boolsym )
{ //声明返回值的类型
table[i].type1=sym;
getsym();
}
else
BIGBALLON
47
{
error(45);
}
}
else
{
error(44);
}
if( sym == semicolon )
{
getsym();
}
else
{
error(5);
}
}
lev = lev + 1;
tx1 = tx;
dx1 = dx;
block( fsys | semicolon ); //第 2层嵌套的子程序
lev = lev - 1;
tx = tx1;
dx = dx1;
if( sym == semicolon )
{
getsym();
test(statbegsys|ident|procsym|funcsym,fsys,6);
}
else
{
error(5);
}
}
3.6 数组类型功能的扩展
数组类型的扩展,我认为是最难的部分了,首先比较不好解决的就是数组的空间分配问
题,其次还要考虑到数组是一维还是二维,如何寻址等问题,于此同时,还要增加额外的函
BIGBALLON
48
数进行数组类型的解析和定义。
在课程设计的时候我并没有能够理解,最终是借用了别人的实现代码来学习的。
type arr=array[3..10] of integer;
var a:arr;
这是一段 pascal中一维数组的 type声明和定义,我们在 PL/0编译器中进行实现的时候,
需要考虑数组的上下界,数组的维数,数组的大小,同时还要记录下数组中元素的类型
struct /* 符号表 */
{
char name[al+1]; /* 符号的名字 */
enum object kind;
double val; /* 如果是常量名 val中放常量的值 */
long level; /* 如果是变量名或过程名,存放层差、偏移地址和大小 */
long addr;
long funcaddr; //给函数使用,记录函数在符号表中的地址偏移量
LL paral[pnum]; //存放函数参数类型,用于类型检查
LL type1; //在符号表中增加类型项
long size; //若是数组类型还要记录大小
long drt; //数组维数
long low[now]; //数组每一维下界
long high[now]; //数组每一维上界
LL type2; //数组元素的类型
int n; //过程或函数的参数个数
}table[txmax+1]; //符号表的结构
所以定义一个数组下标为 3-10的一维数组
应该开辟的内存为 8 + 1 + 2 + 1(size + drt + drt*2 + 1) = 12个内存单元。
在保存数组中元素信息的同时,还要将数组的上下界和维数保存下来。
四、关于 Flex
同样是因为课设的原因,我接触到了 flex,而且有幸拜读了《flex与 bison》这本书,
这本书是《lex 与 yacc》的后续之作,详细介绍了 flex 和 bison 的使用,有兴趣可以去
拜读一下。
4.1 Flex简介
简介什么的就不写了,可以自行百度,同时上面提到的书中也进行了详细的介绍。
BIGBALLON
49
4.2 Windows下配置和使用 Flex
1.) 下载 windows下的 Flex,并进行安装
我把它安装在了 D:\Program Files (x86)\GnuWin32下
你可以在 D:\Program Files (x86)\GnuWin32\bin目录下找到它
2.) 配置 gcc 和 flex 的环境变量
GCC的话,你可以安装 codeblocks 或者 devc++,这两个 IDE中都有 gcc
这里以我的机器为例
D:\Program Files (x86)\Dev-Cpp\MinGW64\bin
在 MinGW64\bin目录下有 gcc
在环境变量中找到 path,将这两个路径加入其中,注意用;隔开
配置好好后,WIN+R打开运行,输入 cmd打开命令行。
输入 gcc –v 可以查看 gcc的版本,如果出现版本信息则配置成功
输入 flex-V 可以查看 flex的版本,注意这里的 V是大写
BIGBALLON
50
3.) 使用 flex
这里以桌面的 bigballon.l 为例
跳转到指定的目录 cd Desktop
输入 flex bigballon.l (此时桌面会生成一个 lex.yy.c的程序)
输入 gcc lex.yy.c (此时桌面会生成一个 a.exe的可执行文件)
这里 a.exe 进行的是对 C 语言程序的词法分析,并且将词法分析的结果至 out.tok 文件。
运行 a.exe,可以看到桌面生成了 out.tok文件,打开它,正是我们要的结果。
如果你想深入了解,推荐阅读《flex与 bison》这本书。