📄 pl0.c
字号:
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "ctype.h"
#include "stdarg.h"
#include "pl0.h"
void error(n); //出错处理,打印出错位置和错误编码
void getsym(void); //词法分析,读取一个单词
void getch(void); //读取字符
void gen(int x, int y, int z); // 生成P-code指令,送入目标程序区
void test(symset s1, symset s2, int n); // 测试当前单词符号是否合法
void block(symset fsys); //分程序分析处理
void enter(int kind); // 登录名字表
int position(char* id); // 查找标识符在名字表中的位置
void constdeclaration(); //常量定义处理
void vardeclaration(void); //变量说明处理
void listcode(int from, int to); //列出P-code指令清单
void statement(symset fsys); //语句部分分析处理
void expression(symset fsys); //表达式分析处理
void term(symset fsys); //项分析处理
void factor(symset fsys); //因子分析处理
void condition(symset fsys); //条件分析处理
void interpret(); //P-code解释执行程序
int base(int stack[], int currentLevel, int levelDiff); //通过静态链求出数据区的基地址
///////////////////////////////////////////////////////////////////////////////////////////
int dx; //全局变量
///////////////////////////////////////////////////////////////////////////////////////////
//主程序
void main ()
{
FILE* hbin; //文件名
char s[80],*finddot;
int i;
symset set, set1, set2;
printf("PL/0 compiler version 2.0\n编写日期2004-08-25\n");
printf("作者:计011班 张弦 2001041148\n");
printf("请输入需要编译源文件名(必须有文件后缀.txt): "); //输入源文件名,必须有文件后缀,如:.txt
scanf("%s", s);
if ((infile = fopen(s, "r")) == NULL) //判断读入文件是否为空
{
printf("文件 %s 不能打开!\n", s);
exit(1);
}
#if 1
// 打开输出文件
finddot = strchr(s,'.');
if (finddot!=NULL)
{
strcpy(finddot, ".out"); //将字符指针的内容拷贝到与源文件同名的后缀名为.out的文件内
}
else
{
strcat(s, ".out");
printf("%s\n", s);
}
printf("输出文件是 %s\n", s);
if ((outfile = fopen(s, "w")) == NULL)
{
printf("文件 %s 不能打开.\n", s);
exit(1);
}
#endif
phi = createset(SYM_NULL);
relset = createset(SYM_EQU, SYM_NEQ, SYM_LSS, SYM_LEQ, SYM_GTR, SYM_GEQ, SYM_NULL);
// create begin symbol sets
declbegsys = createset(SYM_CONST, SYM_VAR, SYM_PROCEDURE, SYM_NULL);
statbegsys = createset(SYM_BEGIN, SYM_CALL, SYM_IF, SYM_WHILE, SYM_READ, SYM_WRITE, SYM_FOR, SYM_REPEAT, SYM_NULL);
facbegsys = createset(SYM_IDENTIFIER, SYM_NUMBER, SYM_LPAREN, SYM_NULL);
err = cc = cx = ll = 0; // initialize global variables
ch = ' ';
getsym(); //词法分析,读取一个单词
set1 = createset(SYM_PERIOD, SYM_NULL);
set2 = uniteset(declbegsys, statbegsys);
set = uniteset(set1, set2);
block(set);
destroyset(set1);//释放set1空间
destroyset(set2);//释放set2空间
destroyset(set);//释放set空间
destroyset(phi);//释放phi空间
destroyset(relset);//释放relset空间
destroyset(declbegsys);//释放declbegsys空间
destroyset(statbegsys);//释放statbegsys空间
destroyset(facbegsys);//释放空facbegsys间
if (sym != SYM_PERIOD) //主程序分析结束,应遇到表明程序结束的句号
error(9); // 应为'.' .
printf("全部");
listcode(0,cx);
if (err == 0) //如果出错次数为0,可以开始解释执行编译产生的代码
{
hbin = fopen("hbin.txt", "w"); //把文本文件hbin与hbin.txt文件关联起来,用于输出类PCODE代码运行结果
for (i = 0; i < cx; i++)
fwrite(&code[i], sizeof(instruction), 1, hbin); //建立并打开hbin文件
fclose(hbin);
interpret(); //开始解释执行类PCODE代码
}
else
printf("有 %d 个错误在此 PL/0 程序里.\n", err);
//关闭源程序文件
fclose(infile);
fclose(outfile);
}
///////////////////////////////////////////////////////////////////////////////////////////
//出错处理,打印出错位置和错误编码
void error(n)
{
int i;
printf("*****");
for (i=1;i<=cc-1;i++)
printf(" ");
fprintf(outfile, " ");
fprintf(outfile, "^\n");//在屏幕cc-1位置显示^与出错代码提示,由于cc是行缓冲区指针,所以^所指位置即为出错位置
printf("^\n"); //在文件cc-1位置输出^与出错代码提示
fprintf(outfile, "Error %3d: %s\n", n, err_msg[n]);
printf("Error %3d: %s\n", n, err_msg[n]);
err++; //出错总次数加一
}
///////////////////////////////////////////////////////////////////////////////////////////
//从源文件中读出若干有效字符,组成一个token串,识别它的类型为保留字/标识符/数字或是其它符号。
//如果是保留字,把sym置成相应的保留字类型,如果是标识符,把sym置成ident表示是标识符,于此同时,
//id变量中存放的即为保留字字符串或标识符名字。如果是数字,把sym置为number,同时num变量中存放
//该数字的值。如果是其它的操作符,则直接把sym置成相应类型。经过本过程后ch变量中存放的是下一
//个即将被识别的字符
//词法分析,读取一个单词
void getsym(void)
{
int i, k;
char a[MAXIDLEN + 1];
while (ch == ' '|| ch == '\t')//用于读一个有效的字符(跳过读出的字符中多余的空格)
getch();
if (isalpha(ch))//如果读出的字符是一个字母,说明是保留字或标识符
{
k = 0; //标识符缓冲区指针置0
do
{
if (k < MAXIDLEN) //如果标识符长度没有超过最大标识符长度(如果超过,就取前面一部分,把多余的抛弃)
a[k++] = ch;
getch(); //读下一个字符
}while (isalpha(ch) || isdigit(ch)); //直到读出的不是字母或数字
//由此可知PL/0的标识符构成规则是:以字母开头,后面跟若干个字母或数字
a[k] = 0; //把标识符缓冲后部没有填入相应字母或空格的空间用空格补足
strcpy(id, a); //最后读出标识符等于a
word[0] = id;
i = NRW; //i指向最后一个保留字
while (strcmp(id, word[i--]));
if (++i)
sym = wsym[i]; // symbol is a reserved word
else
sym = SYM_IDENTIFIER; // symbol is an identifier
}
else
if (isdigit(ch))
{ // symbol is a number.
k = num = 0; //数字位数k,数字num置为0
sym = SYM_NUMBER; //置sym为SYM_NUMBER,表示这一次读到的是数字
do //这个循环依次从源文件中读出字符,组成数字
{
num = num * 10 + ch - '0'; //num * 10加上最近读出的字符ASCII减'0'的ASCII得到相应的数值
k++; //数字位数加一
getch();
}while (isdigit(ch)); //直到读出的字符不是数字为止
if (k > MAXNUMLEN) //如果组成的数字位数大于最大允许的数字位数
error(30); // 发出30号错
} //至此对数字的识别处理结束
else
if (ch == ':') //如果读出的不字母也不是数字而是冒号
{
getch(); //再读一个字符
if (ch=='=') //如果读到的是等号,正好可以与冒号构成赋值号
{
sym = SYM_BECOMES; // sym的类型设为赋值号SYM_BECOMES
getch(); //再读出下一个字
}
else
sym = SYM_NULL; // 如果不是读到等号,那单独的一个冒号就什么也不是
} //以上完成对赋值号的处理
else
if (ch == '<') //如果读到小于号
{
getch(); //再读一个字符
if (ch == '=') //如果读到等号
{
sym = SYM_LEQ; // 购成一个小于等于号
getch(); //读一个字符
}
else //如果小于号后不是跟的等号
if (ch == '>')
{
sym = SYM_NEQ;// <>
getch();
}
else
sym = SYM_LSS; //那就是一个单独的小于号
}
else //如果读到不是字母也不是数字也不是冒号也不是小于号
if (ch == '>') //如果读到大于号,处理过程类似于处理小于号
{
getch();//再读一个字符
if (ch == '=') //如果读到等号
{
sym = SYM_GEQ; //购成一个大于等于号
getch(); //读一个字符
}
else //如果大于号后不是跟的等号
sym = SYM_GTR; // 那就是一个单独的大于号
}
else // 如果读到不是字母也不是数字也不是冒号也不是小于号也不是大于号
{ // other tokens
i = NSYM;
csym[0] = ch;
while (csym[i--] != ch);
if (++i)
{
sym = ssym[i]; //直接成符号表中查到它的类型,赋给sym
getch(); //读下一个字符
}
else
{
printf("程序代码未完成!\n");
fprintf(outfile, "程序代码未完成!\n");
exit(1);
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
//读取字符
void getch(void)
{
if (cc == ll) //如果行缓冲区指针指向行缓冲区最后一个字符就从文件读一行到行缓冲区
{
if (feof(infile)) //如果到达文件末尾
{
printf("\n程序代码未完成!\n");//出错,退出程序
fclose(infile);
exit(1);
}
ll = cc = 0; //行缓冲区ll长度置0,行缓冲区指针cc置行首
printf("%4d ", cx);
fprintf(outfile, "%4d ", cx); //输出cx值,宽度为4
while ( (!feof(infile))&& ((ch = getc(infile)) != '\n')) //当未到行末时,从文件读入一个字符到 ch
{
ll=ll+1;
fprintf(outfile, "%c", ch); //把ch输出到文件
printf("%c", ch); //在屏幕输出ch
line[ll] = ch; //行缓冲区ll长度加一,把读到的字符存入行缓冲区相应的位置
}
printf("\n");
fprintf(outfile, "\n");
ll=ll+1;
line[ll] = ' '; // 行缓冲区长度加一,用于容纳即将读入的回车符CR
}
cc=cc+1;
ch = line[cc]; //行缓冲区指针cc加一,指向即将读到的字符;读出字符,放入全局变量ch
}
///////////////////////////////////////////////////////////////////////////////////////////
// 生成P-code指令,送入目标程序区.
//本过程用于把生成的目标代码写入目标代码数组,供后面的解释器解释执行
void gen(int x, int y, int z)//x要生成的一行代码的助记符,y、z代码的两个操作数
{
if (cx > CXMAX)//如果cx>cxmax表示当前生成的代码行号大于允许的最大代码行数
{
fprintf(outfile, "程序代码过长!\n");
printf("程序代码过长!\n");
fclose(infile);
exit(1);
}
//把代码写入目标代码数组的当前cx所指位置
code[cx].f = x;
code[cx].l = y;
code[cx].a = z; //移动cx指针指向下一个空位
cx=cx+1;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -