C0编译器

发布于 2018-12-21  44 次阅读


做完了编译原理作业,记录下.github地址https://github.com/likole/c0compiler

作业要求

上机实践的目的是让学生“设计一个编译器”,故偏重于对编译器设计实践的强化。上机实践题目为“实现C0简易编译器的核心部分”。

具体内容:

C0语言的语法结构定义如下:

<程序> := [<变量定义部分>] {<自定义函数定义部分>} <主函数>
<变量定义部分> :=  int id {, id};
<自定义函数定义部分> :=   ( int id | void id)'(' ')' <分程序>
<主函数> := void main'(' ')' <分程序>
<分程序> := '{' [<变量定义部分>] <语句序列> '}'  
<语句序列> := <语句> {<语句>}
<语句> :=  <条件语句>|<循环语句> | '{'<语句序列>'}' | <自定义函数调用语句> | <赋值语句> | <返回语句> | <读语句> | <写语句> | ;
<条件语句> := if '('<表达式>')'  <语句> [else <语句> ]
<循环语句> := while '(' <表达式>')' <语句>
<自定义函数调用语句> := <自定义函数调用>;
<赋值语句> := id = <表达式>;
<返回语句> := return ['(' <表达式> ')'] ;
<读语句> := scanf '(' id ')';
<写语句> := printf '(' [ <表达式>] ')';
<表达式> :=  [+|-] <项> { (+|-) <项>} 
<项>  :=  <因子>{(*|/) <因子>}
<因子>  :=  id|'(' <表达式>')' | num | <自定义函数调用>
<自定义函数调用> := id '(' ')'

其中,id代表标识符,num代表整数,其含义及构成方式与C语言相一致;C0源程序中的变量需先定义后使用,其作用域与生存期与C语言相一致;自定义函数可超前使用(调用在前,定义在后)。

根据上面给定的C0文法及其说明和下列定义的假想栈式指令系统,按递归下降分析法设计并实现该C0语言的编译器,生成栈式目标代码;编写栈式指令系统的解释执行程序,输出目标代码的解释执行结果。

假想的栈式指令系统表:

LIT 0 a	将常数值取到栈顶,a为常数值
LOD t a	将变量值取到栈顶,a为相对地址,t为层数
STO t a	将栈顶内容送入某变量单元中,a为相对地址,t为层数
CAL 0 a	调用函数,a为函数地址
INT 0 a	在运行栈中为被调用的过程开辟a个单元的数据区
JMP 0 a	无条件跳转至a地址
JPC 0 a	条件跳转,当栈顶值为0则跳转至a地址,否则顺序执行
ADD 0 0	次栈顶与栈顶相加,退两个栈元素,结果值进栈
SUB 0 0	次栈顶减去栈顶,退两个栈元素,结果值进栈
MUL 0 0	次栈顶乘以栈顶,退两个栈元素,结果值进栈
DIV 0 0	次栈顶除以栈顶,退两个栈元素,结果值进栈
RED 0 0	从命令行读入一个输入置于栈顶
WRT 0 0	栈顶值输出至屏幕并换行
RET 0 0	函数调用结束后,返回调用点并退栈

考核要求及成绩评定标准

要求所实现程序可带运行选项,以便对输出内容进行选择。所实现程序能分阶段依次输出下列内容:C0源程序及其编译结果(生成第一个函数目标代码前的符号表内容—通过选项选择、目标代码,出错信息);解释执行目标代码,动态显示运行栈的内容—通过选项选择,输出执行结果。

目录结构

./
├── compiler 
│   ├── Generator.java //虚拟机代码生成
│   ├── impl
│   │   ├── Constant.java //常量
│   │   ├── GeneratorImpl.java //代码生成实现类
│   │   └── ScannerImpl.java //词法分析实现类
│   ├── Parser.java //语法分析
│   ├── Scanner.java //词法分析接口
│   └── utils
│       ├── Error.java //错误处理
│       └── SymbolTable.java //符号表 
├── Compiler.java //编译主程序,由界面类调用
├── entity
│   ├── Fct.java //指令枚举
│   ├── Instruction.java //指令类
│   ├── Symbol.java //符号枚举
│   └── SymSet.java //符号集
├── interpreter //执行器
│   ├── impl
│   │   └── InterpreterImpl.java //实现类
│   ├── Interpreter.java //接口
│   └── InterpreterListener.java //给主界面使用的监听器
├── Main.form //主界面
└── Main.java //主界面实现类

运行结果

1.运行界面:用户可直接打开代码文件或者输入代码。

2.编译成功:

3.源代码及错误显示:

4.虚拟机代码:

5.名字表:忘记截图了

6.执行:

部分测试用例

为了验证编译器正确性,写了些测试用例进行测试,以下样例均测试通过

int a;
int f()
{
    a=6*6;
    return (7*7);
}
void main()
{
printf(f());
printf(a);
}

函数返回值相加
int f()
{
    return (6);
}
int g()
{
    return (66);
}
void main()
{
printf(f()+g());
}

先使用后定义
int f()
{
    return (g());
}
int g()
{
    return (66);
}
void main()
{
f();
printf(g());
}

多次使用后定义
int f()
{
printf(g());
    return (g());
}
int g()
{
    return (66);
}
void main()
{
printf(f());
}

函数中访问变量,变量作用域
int a,b,c;
int g()
{
    int a;
    a=66;
b=2;
    return (a);
}
void main()
{
a=1;
printf(g());
printf(a);
printf(b);
} 

一堆分号
自动加返回

条件语句
void main()
{
int a;
a=10;
if(a){
printf(1);
a=a-1;
}else{
printf(0);
}
return;
}

void main()
{
int a;
a=0;
if(a){
printf(1);
a=a-1;
}else{
printf(0);
}
return;
}


void main()
{
int a;
a=0;
if(a){
printf(1);
a=a-1;
}
return;
}


循环
void main()
{
int a;
a=10;
while(a){
printf(a);
a=a-1;
}
return;
}

表达式测一下
a7 b4 c 28
int f8()
{
    return (2*4);
}
void main()
{
int a,b,c;
a=7;
b=a-3;
c=a*b;
printf(5*(4+5*5)+8/4+(2+3*6)/10-c);

}

输入
int a,b;
void main()
{
int c,d;
scanf(a);
scanf(d);
printf(a+d);
}

综合
(求阶乘)
int a,b,ans;
int fac()
{
    int b;
    b=a;
    if(a)
    {
        a=a-1;
        return (b*fac());
    }else
    {
        return (1);
    }
}
void fac1()
{
    ans=1;
    while(b)
    {
        ans=ans*b;
        b=b-1;
    }
}
void main()
{
    scanf(a);
    b=a;
    printf(fac());
    fac1();
    printf(ans);
}