自定义文法编译器

编译原理试点班 自定义文法编译器

1 文法

1.1 基本定义

<adds> -> '+' | '-'
<muls> -> '*' | '/'
<cmps> -> '>' | '>=' | '<' | '<=' | '==' | '!='
<leftop> -> '-' | '+' | '&'
<escape> -> '\n' | '\t' | '\0' | '\"' 转义字符
<letter> -> 'a' - 'z' | 'A' - 'Z'
<nonzerodigit> -> '1' - '9'
<digit> -> '0' - '9'
<CHAR> -> <digit> | <letter> | <adds> | <muls> | <cmps> | <leftop> | <escape>
<STRING> -> '"' <CHAR>+ '"'
<INTEGER> -> 0 | <nonzerodigit> <digit>*
<keyWords> -> "int" | "char" | "void" | "if" | "else" | "while" | "for" | "scanf" | "printf" | "return" | "switch" | "case" | "default"
<IDEN> -> <letter> [ <letter> | <digit> ]*
<type> -> "int" | "char" | "void"

1.2 变量和函数定义

变量

<Define> -> <type> <DefineTail>
<DefineTail> -> <IDEN> ( '(' <ParaList> ')' <FunTail> | <VarDefine> <DefineList> )
<DefineList> -> ';' | ',' <IDEN> <VarDefine> <DefineList>
<VarDefine> -> [ NUM ] { '=' <STRING>} | '=' <Exp> | 空 数组或者普通标识符,只有字符数组能进行初始化

函数

参数定义:
<ParaList> -> <type> <IDEN> { ',' <ParaList> } | 空
直接分号说明这是函数声明,否则就开始定义函数体:
<FunTail> -> ';' | <Complex>

1.3 运算(表达式文法)

根据优先级进行递归下降
<Exp> -> 空 | <AssignExp>
<AssignExp> -> <OrExp> { '=' <AssignExp> }
<OrExp> -> <AndExp> { '||' <OrExp> }
<AndExp> > <CmpExp> { '&&' <AndExp> }
<CmpExp> -> <AddExp> { <cmps> <CmpExp> }
<AddExp> -> <Item> { <adds> <AddExp> }
<Item> -> <Factor> { <muls> <Item> }
<Factor> -> <value> | <leftop> <Factor>

支持如下的表达式
a = b = c
a || b || c
a && b && c
a >= b <= c == d != e > f < g
a + b - c
a * b / c
-a +a &a
把a,b,c看成是经过优先级更高的表达式计算之后返回的结果

<value> -> <IDEN> <Idexp> | '(' <Exp> ')' | <MakeConst>
<MakeConst> -> STRING | CHAR | INTEGER
<Idexp> -> 空 | '[' <Exp> ']' | '(' <Arguments> ’)‘ 可能是数组或者函数调用
<Arguments> -> 空 | <Exp> {',' <Argu> }
<Argu> -> <Exp> { ',' <Argu> }

a = b;正常变量
a = b[10]; 数组
a = b(5, 3);函数调用

1.4 复合语句

<program> -> <Define> <program> 整个程序可以看做是不断在定义变量和函数
<FunTail> -> ';' | <Complex> 函数中可以是声明,也可以定义
<Complex> -> '{' <SubProgram> '}' 复合语句定义
<SubProgram> -> <Define> <SubProgram> | <Sentence> <SubProgram>
<Sentence> -> "if" <IfStatement> | "while" <Whilestatement> | "for" <Forstatement> | "return" <ReturnStatement> | "printf" <PrintfStatement> | "scanf" <ScanfStatement> | "switch" <SwitchStatement> | <Exp> ';'
<SwitchStatement> -> '(' <Exp> ')' '{' <CaseStatement> '}'
<CaseStatement> -> "case" <MakeConst> <SubProgram> <CaseStatement> | "default" <SubProgram> <CaseStatement> | 空
<ReturnStatement> -> <Exp>
<PrintfStatement> -> '(' <STRING> { ',' <Exp> } ')' ';'
<ScanfStatement> -> '(' <STRING> ',' <Exp> ')' ';'
<IfStatement> -> '(' <Exp> ')' [ <Complex> | <Sentence> ] { "else" [ <Complex> | <Sentence> ] }
<WhileStatement> -> '(' <Exp> ')' [ <Complex> | <Sentence> ]
<ForStatement> -> '(' <Define> ';' <Exp> ';' <Exp> ')' [ <Complex> | <Sentence> ]

1.5 典型语句案例

//变量定义
int a = 5, b = 10, c[20];
char d = 'q', str[15] = "hello world";

int func(int a,int b);//函数声明
int func(int a,int b)//函数定义
{
   
	return a + b;
}
void process();

scanf("%d", &a);//输入
printf("%d\n", a);//输出

if ( a > 5 )
{
   
	...
}
else
{
   
	...
}

for ( int i=1; i <= 5 ;i = i + 1 )
{
   
	...
}

int i=0;
while ( i <= 100 )
{
   
	...
	i = i + 1;
}

void func(int val)
{
   
    switch (val) //我的文法中,默认每个case在结尾自带break
    {
   
        case 1:
            printf("in case 1\n");
        case 2:
            printf("in case 2\n");
        default:
            printf("in default\n");
    }
}

1.6 文法说明

本文法大部分与C0相同,有删减也有增加。
删减
C0的const关键字。我认为这个功能难度不是特别大,主要是给变量加一个属性判断是不是常量,但是我现在要加入这个功能的话还是需要修改的比较仔细,可能会漏过一些边边角角。而且这个功能意义不是特别大,所以我决定删掉。
增加和改动
1. C0的变量定义都是在程序的首部,这样的灵活性太低了,所以我修改成了程序是不断由变量定义和语句组合而成的。具体文法可参考上面的<SubProgram>的文法定义。
2. 增加switch语句,支持case,default等。并且默认每个case后面都自带break,不需要自己添加break。
3. 有些版本的C0文法循环只有while,我把for和while循环都实现了。

2 系统总体设计

系统总体设计图如下所示
在这里插入图片描述
开发环境:
ubuntu 14.04, c++,nasm 2.14.02 ,gcc 4.8.5

开发软件:
codeblocks,vscode

3 系统详细设计

3.1 词法分析

词法分析有两种方法,一种是基于表驱动的词法分析,也就对每个字符都进行判断来转换状态,另一种是基于硬编码方式的词法分析。表驱动的方式需要考虑非常多的状态转换,而且包含大量的goto语句,对比之下,用硬编码的方式来实现是更好的选择。

我的实现方式就是先把所有关键字都存起来,然后不断地读取字符,根据字符是字母,数字,还是单引号,双引号,进入不同的判断分支继续读取字符。在关键词部分,把这一段字符读完了之后,可以得到一个字符串,把字符串和所有关键词比较,如果相同,那么他就是关键词,否则就是标识符。
对于其他的数字,字符,字符串,也是要进行相应的详细的判断,详细代码可见lexer.cpp

3.2 语法分析

语法分析中,我使用的是LL(1)文法,同时文法中没有左递归。实现方法类似于递归下降,每次用GetNext()函数进行词法分析,得到下一个Token,根据上面的文法,利用Token判断自己应该跳转到哪个函数,在跳转的过程中,还会生成函数,变量,中间代码插入到符号表中。详细代码在parser.cpp中。

3.3 符号表

代码只放了主要部分,每个属性的含义都有解释。主要的结构就是Symboltable中存储了所有的函数和变量,每个函数里面也有自己的变量。

Variable

class Variable
{
   
public:
    string name;//变量名称
    Tag tag;//变量类型,比如int,char
    bool literal;//代表是否是字面量,比如3, "asd"
    bool canbeleft;//左值,代表可以寻址

    vector<int> scopePath;//变量作用域

    bool isPointer;//是否要解引用
    bool isArray;//是否是数组
    int arraySize;//数组大小
    int val;//整数值
    string strval;//字符串值

    int size;//变量的大小,char,int和数组是不同的
    int offset;//在栈中的偏移量,全局变量为0,局部变量要计算
    Variable* initData;//初始值,可以为空,也指向一个字面量,或者一个表达式的结果
};

Function

class Function
{
   
public:
    Tag retType;    //返回值的类型
    string name;    //函数的名称
    bool Defined;   //函数是否被定义了,还是只是声明了
    vector<Variable*> paralist; //参数表
    int maxDep; //最大栈深度,用来为局部变量提前申请空间
    int curEsp; //栈顶指针位置
    vector<int> scopeEsp; //管理多层域的深度
    vector<IRcode*> codes; //函数内的所有四元式
    IRcode* ReturnPoint;    //函数返回点,给return使用
};

SymbolTable

class SymTable
{
   
public:
    map<string, vector<Variable*>* > varTable;//变量表,根据名称来存
    map<string, Variable*> strTable;//字符串表
    map<string, Function*> funTable;//函数表

    vector<Variable*> varList;//所有变量
    vector<Function*> funList;//所有函数

    Function *curFun;//当前正在parse的函数

    Variable *one, *four, *voidval;//给数组计算步长用

    int scopeId;//当前作用域编号
    vector<int> scopePath;//作用域路径
};

3.4 作用域管理

在symboltable中存储两个量,scopeId和scopePath,scopeId表示当前作用域的编号,scopePath表示当前作用域的路径,在进入函数定义,或者进入大括号内等时候,scopeId增加,scopePath要push当前作用域编号,离开就pop,定义变量时把变量的作用域路径设置成scopePath即可。下面是一个例子。

int a,b; // 作用域: 0   
int func()
{
   
	int a,b; //作用域: 0/1  
	if ( a > b)
	{
   
		int a,b,c;//作用域: 0/1/2
	}
}
void main()
{
   
	int a;//作用域:0/3
}
所以4个变量a的作用域分别为0,0/1,0/1/2,0/3,这样就可以通过作用域把他们区分开来。

3.5 存储分配方案

采用静态存储的方式存储所有字符串常量和全局变量。
采用动态存储的方式来给局部变量分配空间。也就是计算每个函数栈的深度。
要注意的是,关于函数栈最大深度的计算,不能直接算里面所有的变量大小之和,因为如果函数里有if,那么在if里面定义的变量出了if就失效了,也就是他们不再使用这片内存,如果之后仍旧分配给他们会非常浪费,所以也要像作用域一样管理起来。
我使用的是curESP,scopeEsp,maxdep这两个量,curESP表示当前栈顶指针位置,scopeESP管理函数内每一层栈的深度,maxdep表示函数内栈最深是多少。这样每个变量就可以根据curESP计算自己的偏移量,函数根据maxdep在一开始分配空间,下面是一个例子。

int func()
{
   
	//一个int为4字节
	int a,b; //scopeESP:8 curESP = 8 maxdep = 8
	if ( a > b)
	{
   
		int a,b,c;//scopeESP:8/12 curEsp = 20 maxdep = 20
	}
	//scopeESP:8 curESP = 8 maxdep = 20
}
void main()//这是另一个函数
{
   
	int a;//栈:4 curESP = 4 maxdep = 4
}

函数栈我采用如下的方式管理
比如函数int func(int a, char b),参数从右往左进栈,之后调用call。 此时会默认把返回地址push进去,进入之后push ebp

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值