终于可以支持一个像样的程序了
在 Part 5 中,你的编译器需要增加对局部变量的支持(当然也包括局部常量)。
你需要支持的语法规则如下(以 CompUnit
为开始符号):
CompUnit -> FuncDef
Decl -> ConstDecl | VarDecl
ConstDecl -> 'const' BType ConstDef { ',' ConstDef } ';'
BType -> 'int'
ConstDef -> Ident '=' ConstInitVal
ConstInitVal -> ConstExp
ConstExp -> AddExp
VarDecl -> BType VarDef { ',' VarDef } ';'
VarDef -> Ident
| Ident '=' InitVal
InitVal -> Exp
FuncDef -> FuncType Ident '(' ')' Block // 保证当前 Ident 只为 "main"
FuncType -> 'int'
Block -> '{' { BlockItem } '}'
BlockItem -> Decl | Stmt
Stmt -> LVal '=' Exp ';'
| [Exp] ';'
| 'return' Exp ';'
LVal -> Ident
Exp -> AddExp
AddExp -> MulExp
| AddExp ('+' | '−') MulExp
MulExp -> UnaryExp
| MulExp ('*' | '/' | '%') UnaryExp
UnaryExp -> PrimaryExp | UnaryOp UnaryExp
PrimaryExp -> '(' Exp ')' | LVal | Number
UnaryOp -> '+' | '-'
- 标识符
Ident
的定义
Ident -> Nondigit
| Ident Nondigit
| Ident Digit
Nondigit -> '_' | 'a' | 'b' | ... | 'z' | 'A' | 'B' | ... | 'Z'
Digit -> '0' | '1' | ... | '9'
对于同名标识符的规定:
- 全局变量和局部变量的作用域可以重叠,重叠部分局部变量优先;
- 同名局部变量的作用域不能重叠;
- 变量名可以与函数名相同。
ConstInitVal
中的ConstExp
必须是能在编译时求值的int
型表达式,其中可以引用已定义的常量。
VarDef
用于定义变量。当不含有=
和初始值时,其运行时实际初值未定义。- 当
VarDef
含有=
和初始值时,=
右边的InitVal
和ConstInitVal
的结构要求相同,唯一的不同是ConstInitVal
中的表达式是ConstExp
常量表达式,而InitVal
中的表达式可以是当前上下文合法的任何Exp
。
Block
内不能有同名的变量或常量。
- 单个
Exp
可以作为Stmt
。该Exp
会被求值,所求的值会被丢弃。
- 赋值号左边的
LVal
必须是变量;Exp
中的LVal
必须是当前作用域内、该Exp
语句之前有定义的变量或常量。
样例程序 1:
int main() {
int a = 123 - 122;
return a;
}
示例 IR 1:
define dso_local i32 @main(){
%1 = alloca i32
%2 = sub i32 123, 122
store i32 %2, i32* %1
%3 = load i32, i32* %1
ret i32 %3
}
输出样例 1:
1
样例程序 2:
int main() {
const int Nqn7m1 = 010;
int yiersan = 456;
int mAgIc_NuMbEr;
mAgIc_NuMbEr = 8456;
int a1a11a11 = (mAgIc_NuMbEr - yiersan) / 1000 - Nqn7m1, _CHAOS_TOKEN;
_CHAOS_TOKEN = 2;
a1a11a11 = a1a11a11 + _CHAOS_TOKEN;
return a1a11a11 - _CHAOS_TOKEN + 000;
}
示例 IR 2:
define dso_local i32 @main(){
%1 = alloca i32
%2 = alloca i32
%3 = alloca i32
%4 = alloca i32
store i32 456, i32* %4
store i32 8456, i32* %3
%5 = load i32, i32* %3
%6 = load i32, i32* %4
%7 = sub i32 %5, %6
%8 = sdiv i32 %7, 1000
%9 = sub i32 %8, 8
store i32 %9, i32* %2
store i32 2, i32* %1
%10 = load i32, i32* %2
%11 = load i32, i32* %1
%12 = add i32 %10, %11
store i32 %12, i32* %2
%13 = load i32, i32* %2
%14 = load i32, i32* %1
%15 = sub i32 %13, %14
%16 = add i32 %15, 0
ret i32 %16
}
输出样例 2:
0
样例程序 3:
int main() {
const int sudo = 0;
int rm = 5, r = 3, home = 5;
sudo = rm -r /home* 0;
return 0;
}
输出样例 3:
编译器直接以非 0 的返回值退出。(赋值号左边的 LVal
必须是变量)