📄 pl0.c
字号:
///////////////////////////////////////////////////////////////////////////////////////////
// 测试当前单词符号是否合法
void test(symset s1, symset s2, int n)
//s1:当语法分析进入或退出某一语法单元时当前单词符合应属于的集合
// s2:在某一出错状态下,可恢复语法分析正常工作的补充单词集合
// n:出错信息编号,当当前符号不属于合法的s1集合时发出的出错信息
{
symset s;
if (! inset(sym, s1))//如果当前符号不在s1中
{
showset(s1);
showset(s2);
printf("sym=%d, id=%s\n", sym, id);
error(n);//发出n号错误
s = uniteset(s1, s2);// 把s2集合补充进s1集合
while(! inset(sym, s))//通过循环找到下一个合法的符号,以恢复语法分析工作
getsym();
destroyset(s);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
//分程序分析处理
void block(symset fsys)//fsys:用于出错恢复的单词集合
{
int cx0; //记录本层开始时代码段分配位置
mask* mk;
int block_dx;//数据段内存分配指针,指向下一个被分配空间在数据段中的偏移位置
int savedTx;//记录本层开始时符号表位置
symset set1, set;
dx = 3;//地址指示器给出每层局部量当前已分配到的相对位置。
//置初始值为3的原因是:每一层最开始的位置有三个空间用于存放静态链SL、动态链DL和返回地址RA
block_dx = dx;
mk = (mask*) &table[tx];
mk->address = cx; //符号表当前位置记下当前层代码的开始位置
gen(JMP, 0, 0); //产生一行跳转指令,跳转位置暂时未知填0
if (level > MAXLEVEL) //如果当前过程嵌套层数大于最大允许的套层数
error(32);
do
{//开始循环处理源程序中所有的声明部分
if (sym == SYM_CONST) //如果当前token是const保留字,开始进行常量声明
{
getsym(); //取下一个token,正常应为用作常量名的标识符
do
{ //反复进行常量声明
constdeclaration(); //声明以当前token为标识符的常量
while (sym == SYM_COMMA) //如果遇到了逗号则反复声明下一个常量
{
getsym(); //获取下一个token,这里正好应该是标识符
constdeclaration(); //声明以当前token为标识符的常量
}
if (sym == SYM_SEMICOLON)//如果常量声明结束,应遇到分号
getsym(); //获取下一个token,为下一轮循环做好准备
else
error(5); //如果常量声明语句结束后没有遇到分号则发出5号错误
}while (sym == SYM_IDENTIFIER); //如果遇到非标识符,则常量声明结束
}
if (sym == SYM_VAR) //如果当前token是var保留字,开始进行变量声明,与常量声明类似
{
getsym(); // 获取下一个token,此处正常应为用作变量名的一个标识符
do
{ //反复进行变量声明
vardeclaration(); //以当前token为标识符声明一个变量
while (sym == SYM_COMMA)//如果遇到了逗号则反复声明下一个变量
{
getsym(); //获取下一个token,这里正好应该是标识符
vardeclaration(); //声明以当前token为标识符的变量
}
if (sym == SYM_SEMICOLON) //如果变量声明结束,应遇到分号
getsym(); //获取下一个token,为下一轮循环做好准备
else
error(5); //如果变量声明语句结束后没有遇到分号则发出5号错误
}while (sym == SYM_IDENTIFIER); //如果遇到非标识符,则变量声明结束
block_dx = dx;
}
while (sym == SYM_PROCEDURE) //循环声明各子过程
{
getsym(); //获取下一个token,此处正常应为作为过程名的标识符
if (sym == SYM_IDENTIFIER) //如果token确为标识符
{
enter(ID_PROCEDURE); //把这个过程登录到名字表中
getsym(); //获取下一个token,正常情况应为分号
}
else
error(4);
if (sym == SYM_SEMICOLON) //如果当前token为分号
getsym(); // 获取下一个token,准备进行语法分析的递归调用
else
error(5);
level++;
savedTx = tx;
set1 = createset(SYM_SEMICOLON, SYM_NULL);
set = uniteset(set1, fsys);
block(set); //递归调用语法分析过程,当前层次加一,同时传递表头索引、合法单词符
destroyset(set1);
destroyset(set);
tx = savedTx;
level--;
if (sym == SYM_SEMICOLON) //递归返回后当前token应为递归调用时的最后一个end后的分号
{
getsym(); //获取下一个token
set1 = createset(SYM_IDENTIFIER, SYM_PROCEDURE, SYM_NULL);
set = uniteset(statbegsys, set1);
test(set, fsys, 6); //检查当前token是否合法,不合法则用fsys恢复语法分析同时抛6号错
destroyset(set1);
destroyset(set);
}
else
error(5); //如果过程声明后的符号不是分号,抛出5号错误
}
set1 = createset(SYM_IDENTIFIER, SYM_NULL);
set = uniteset(statbegsys, set1);
test(set, declbegsys, 7); //检查当前状态是否合法,不合法则用声明开始符号作出错恢复、抛7号错
destroyset(set1);
destroyset(set);
}while (inset(sym, declbegsys)); //直到声明性的源程序分析完毕,继续向下执行,分析主程序
code[mk->address].a = cx; //把前面生成的跳转语句的跳转位置改成当前位置
mk->address = cx; //地址为当前代码分配地址
cx0 = cx; //记下当前代码分配位置
gen(INT, 0, block_dx); //生成分配空间指令,分配dx个空间
set1 = createset(SYM_SEMICOLON, SYM_END, SYM_NULL);
set = uniteset(set1, fsys);
statement(set); //处理当前遇到的语句或语句块
destroyset(set1);
destroyset(set);
gen(OPR, 0, 0); //生成从子程序返回操作指令
test(fsys, phi, 8); // 用fsys检查当前状态是否合法,不合法则抛8号错
listcode(cx0, cx); //列出本层的类PCODE代码
}
///////////////////////////////////////////////////////////////////////////////////////////
// 登录符号表过程
void enter(int kind) //kind欲登录到符号表的符号类型
{
int i;
mask *mk;
if ( position(id)> 0 )
error(26);
tx++;//符号表指针指向一个新的空位
strcpy(table[tx].name, id);//开始登录,name是符号的名字,对于标识符,这里就是标识符的名字
table[tx].kind = kind;//符号类型,可能是常量0、变量1或过程名2
switch (kind)//根据不同的类型进行不同的操作
{
case ID_CONSTANT://如果是常量名
if (num > MAXADDRESS)//在常量的数值大于允许的最大值的情况下
{
error(30);
num = 0; //实际登录的数字以0代替
}
table[tx].value = num; //如是合法的数值,就登录到符号表
printf("符号表: ");
for(i=0;i<MAXIDLEN;i++)
if(&table[tx].name[i]!=NULL)
printf("%c",table[tx].name[i]);
printf("%d %4d\n",table[tx].kind,table[tx].value);
break;
case ID_VARIABLE: //如果是变量名
mk = (mask*) &table[tx];
mk->level = level;//记下它所属的层次号
mk->address = dx++;//记下它在当前层中的偏移量,偏移量自增一,为下一次做好准备
printf("符号表: ");
for(i=0;i<MAXIDLEN;i++)
if(&table[tx].name[i]!=NULL)
printf("%c",table[tx].name[i]);
printf("%d %10d %4d\n",mk->kind,mk->level,mk->address);
break;
case ID_PROCEDURE: //如果要登录的是过程名
mk = (mask*) &table[tx];
mk->level = level;//记录下这个过程所在层次
printf("符号表: ");
for(i=0;i<MAXIDLEN;i++)
if(&table[tx].name[i]!=NULL)
printf("%c",table[tx].name[i]);
printf("%d %10d %4d\n",mk->kind,mk->level,mk->address);
break;
}
} //登录符号过程没有考虑到重复的定义的问题。如果出现重复定义,则以最后一次的定义为准。
///////////////////////////////////////////////////////////////////////////////////////////
// 在符号表中查找指定符号所在位置的函数
int position(char* id) //id要找的符号,要找的符号在符号表中的位置,如果找不到就返回0
{
int i;
strcpy(table[0].name, id); //先把id放入符号表0号位置
i = tx ;//从符号表中当前位置也即最后一个符号开始找
while (strcmp(table[i].name, id) != 0) //如果当前的符号与要找的不一致,找前面一个
i--;
return i; //返回找到的位置号,如果没找到则一定正好为0
}
///////////////////////////////////////////////////////////////////////////////////////////
//常量声明处理过程
void constdeclaration()
{
if (sym == SYM_IDENTIFIER) //常量声明过程开始遇到的第一个符号必然应为标识符
{
getsym(); //获取下一个token
if (sym == SYM_EQU || sym == SYM_BECOMES) //如果是等号或赋值号
{
if (sym == SYM_BECOMES)//如果是赋值号(常量生明中应该是等号)
error(1); //这里其实自动进行了错误纠正使编译继续进行,把赋值号当作等号处理
getsym(); //获取下一个token,等号或赋值号后应接上数字
if (sym == SYM_NUMBER) //如果的确是数字
{
enter(ID_CONSTANT); //把这个常量登陆到符号表
getsym(); //获取下一个token,为后面作准备
}
else
error(2); // 如果等号后接的不是数字,抛出2号错误
}
else
error(3); // 如果常量标识符后接的不是等号或赋值号,抛出3号错误
}
else
error(4); // 如果常量声明过程遇到的第一个符号不为标识符,抛出4号错误
}
///////////////////////////////////////////////////////////////////////////////////////////
//变量说明处理
void vardeclaration(void)
{
if (sym == SYM_IDENTIFIER)//变量声明过程开始遇到的第一个符号必然应为标识符
{
enter(ID_VARIABLE); //将标识符登陆到符号表中
getsym();//获取下一个token,为后面作准备
}
else
error(4); // 如果变量声明过程遇到的第一个符号不是标识符,抛出4号错误
}
///////////////////////////////////////////////////////////////////////////////////////////
//列出当前一层类PCODE目标代码过程listcode
void listcode(int from, int to)
{//省略用户选择是要列出代码的情况下才列出代码,默认直接列出代码
int i;
printf("目标代码:\n");
for (i = from; i < to; i++)
{
printf("%4d %s\t%d\t%d\n", i, mnemonic[code[i].f], code[i].l, code[i].a);
fprintf(outfile, "%4d %s\t%d\t%d\n", i, mnemonic[code[i].f], code[i].l, code[i].a);
}
}
///////////////////////////////////////////////////////////////////////////////////////////
//语句部分分析处理
void statement(symset fsys) //fsys如果出错可用来恢复语法分析的符号集合
{
int i, cx1, cx2, q;
char ida[MAXIDLEN + 1];
symset set1, set;
mask* mk;
if (sym == SYM_IDENTIFIER) //所谓"语句"可能是赋值语句,以标识符开头
{
if (! (i = position(id))) //在符号表中没有查到该标识符所在位置
error(11);
else
if (table[i].kind != ID_VARIABLE) //如果在符号表中找到该标识符,但该标识符不是变量名
{
error(12);
i = 0; //i置0作为错误标志
}
getsym(); //获得下一个token,正常应为赋值号
if (sym == SYM_BECOMES) //如果的确为赋值号
getsym(); //获取下一个token,正常应为一个表达式
else
error(13);
expression(fsys); //处理表达式
mk = (mask*) &table[i];
if (i) //如果不曾出错,i将不为0,i所指为当前语名左部标识符在符号表中的位置
gen(STO, level - mk->level, mk->address);//产生一行把表达式值写往指定内存的sto目标代码
}
else
if (sym == SYM_CALL) //如果是call语句
{
getsym(); //获取token,应是过程名型标识符
if (sym != SYM_IDENTIFIER) //如果call后跟的不是标识符
error(14);
else//从符号表中找出相应的标识符
{
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -