write_a_compiler_by_yourself

这是一个tough part

本人学艺不精,这里先列举一下用到的资料.
自己写一个编译器

手把手教你构建 C 语言编译器

一个英文一个中文,可以说是比较详细了.

还有国内清华的minidecaf,最后我还是用了北大的.

需要装docker.输入docker version报错.应该是Hyper-V或是WSL2未开启,开了之后

image-20220508192521986

1
2
3
cd "C:\Program Files\Docker\Docker"

DockerCli.exe -SwitchDaemon

一顿操作,基本就行了.如果遇到这种错误Containers feature is disabled. Enable it using the PowerShell script in an administrative

开始

编译器结构

一般而言,编译器的编写分为 3 个步骤:

  1. 词法分析器,用于将字符串转化成内部的表示结构。
  2. 语法分析器,将词法分析得到的标记流(token)生成一棵语法树。
  3. 目标代码的生成,将语法树转化成目标代码。

在这里我们只需要设计一个程序, 将输入的 SysY 源代码, 编译到 RISC-V 汇编即可. 在这种意义之下, 编译器通常由以下几个部分组成:

  • 前端: 通过词法分析和语法分析, 将源代码解析成抽象语法树 (abstract syntax tree, AST). 通过语义分析, 扫描抽象语法树, 检查其是否存在语义错误.
  • 中端: 将抽象语法树转换为中间表示 (intermediate representation, IR), 并在此基础上完成一些机器无关优化.
  • 后端: 将中间表示转换为目标平台的汇编代码, 并在此基础上完成一些机器相关优化.

在前端中, 我们的目的是把文本形式的源代码, 转换为内存中的一种树形的数据结构. 因为相比于处理字符串, 在树形结构上进行处理显然要更方便, 且效率更高. 把文本形式的源代码变成数据结构形式的 AST 有很多种方法, 但相对科学的方法是对源代码进行词法分析和语法分析.

而语法分析的目的, 按照程序的语法规则, 将输入的 token 流变成程序的 AST. 例如, 对于 SysY 程序, 关键字 int 后跟的一定是一个标识符, 而不可能是一个整数字面量, 这便是语法规则. Parser 会通过某些语法分析算法, 例如 LL 分析法或 LR 分析法, 对 token 流做一系列的分析, 并最终得到 AST.

语义分析阶段, 编译器通常会:

  • 建立符号表, 跟踪程序里变量的声明和使用, 确定程序在某处用到了哪一个变量, 同时也可发现变量重复定义/引用未定义变量之类的错误.
  • 进行类型检查, 确定程序中是否存在诸如 “对整数变量进行数组访问” 这种类型问题. 同时标注程序中表达式的类型, 以便进行后续的生成工作. 对于某些编程语言 (例如 C++11 之后的 C++, Rust 等等), 编译器还会进行类型推断.
  • 进行必要的编译期计算. SysY 中支持使用常量表达式作为数组定义时的长度, 而我们在生成 IR 之前, 必须知道数组的长度 (SysY 不支持 VLA), 这就要求编译器必须能在编译的时候算出常量表达式的值, 同时对那些无法计算的常量表达式报错. 对于某些支持元编程的语言, 这一步可能会非常复杂.

至此, 我们就能得到一个语法正确, 语义清晰的 AST 表示了.

编译器通常不会直接通过扫描 AST 来生成目标代码 (汇编)——当然这么做也不是不可以, 因为从定义上讲, AST 也是一种 “中间表示”. 只不过, AST 在形式上更接近源语言, 而且其中可能会包含一些更为高级的语义, 例如分支/循环, 甚至结构体/类等等, 这些内容要一步到位变成汇编还是比较复杂的.

所以, 编译器通常会将 AST 转换为另一种形式的数据结构, 我们把它称作 IR. IR 的抽象层次比 AST 更低, 但又不至于低到汇编代码的程度. 在此基础上, 无论是直接把 IR 进一步转换为汇编代码, 还是在 IR 之上做出一些优化, 都相对更容易.

有了 IR 的存在, 我们也可以大幅降低编译器的开发成本: 假设我们想开发 MM 种语言的编译器, 要求它们能把输入编译成 NN 种指令系统的目标代码, 在没有统一的 IR 的情况下, 我们需要开发 M \times NM×N 个相关模块. 如果我们先把所有源语言都转换到同一种 IR, 然后再将这种 IR 翻译为不同的目标代码, 我们就只需要开发 M + NM+N 个相关模块.

现实世界的确存在这样的操作, 例如 LLVM IR 就是一种被广泛使用的 IR. 有很多语言的编译器实现, 例如 Rust, Swift, Julia, 都会将源语言翻译到 LLVM IR. 同时, LLVM IR 可被生成为 x86, ARM, RISC-V 等一系列指令系统的目标代码. 此时, 编译器的前后端是完全解耦的, 两部分可以各自维护, 十分方便.

此外, IR 也可以极大地方便开发者调试自己的编译器. 在编译实践中, 你的编译器对于同一个 SysY 文件的输入, 既可以输出 Koopa IR, 也可以输出 RISC-V. 你可以借助相关测试工具来测试这两部分的正确性, 进而定位你的编译器到底是在前端/中端部分出了问题, 还是在后端的部分出了问题.

编译器进行的最后一步操作, 就是将 IR 转换为目标代码, 也就是目标指令系统的汇编代码. 通常情况下, 这一步通常要做以下几件事:

  1. 指令选择: 决定 IR 中的指令应该被翻译为哪些目标指令系统的指令. 例如前文的 Koopa IR 程序中出现的 lt 指令可以被翻译为 RISC-V 中的 slt/slti 指令.
  2. 寄存器分配: 决定 IR 中的值和指令系统中寄存器的对应关系. 例如前文的 Koopa IR 程序中的 @x, %cond, %0 等等, 它们最终可能会被放在 RISC-V 的某些寄存器中. 由于指令系统中寄存器的数量通常是有限的 (RISC-V 中只有 32 个整数通用寄存器, 且它们并不都能用来存放数据), 某些值还可能会被分配在内存中.
  3. 指令调度: 决定 IR 生成的指令序列最终的顺序如何. 我们通常希望编译器能生成一个最优化的指令序列, 它可以最大程度地利用目标平台的微结构特性, 这样生成的程序的性能就会很高. 例如编译器可能会穿插调度访存指令和其他指令, 以求减少访存导致的停顿.

EBNF, 即 Extended Backus–Naur Form, 扩展巴科斯范式, 可以用来描述编程语言的语法. 基于 SysY 的 EBNF, 我们可以从开始符号出发, 推导出任何一个符合 SysY 语法定义的 SysY 程序.

makefile

1
2
3
4
5
6
# C++ 模式
flex -o 文件名.lex.cpp 文件名.l
bison -d -o 文件名.tab.cpp 文件名.y # 此时 bison 还会生成 `文件名.tab.hpp`
# C 模式
flex -o 文件名.lex.c 文件名.l
bison -d -o 文件名.tab.c 文件名.y # 此时 bison 还会生成 `文件名.tab.h`
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
%option noyywrap
%option nounput
%option noinput

%{

#include <cstdlib>
#include <string>

// 因为 Flex 会用到 Bison 中关于 token 的定义
// 所以需要 include Bison 生成的头文件
#include "sysy.tab.hpp"

using namespace std;

%}

/* 空白符和注释 */
WhiteSpace [ \t\n\r]*
LineComment "//".*$

/* 标识符 */
Identifier [a-zA-Z_][a-zA-Z0-9_]*

/* 整数字面量 */
Decimal [1-9][0-9]*
Octal 0[0-7]*
Hexadecimal 0[xX][0-9a-fA-F]+

%%

{WhiteSpace} { /* 忽略, 不做任何操作 */ }
{LineComment} { /* 忽略, 不做任何操作 */ }

"int" { return INT; }
"return" { return RETURN; }

{Identifier} { yylval.str_val = new string(yytext); return IDENT; }

{Decimal} { yylval.int_val = strtol(yytext, nullptr, 0); return INT_CONST; }
{Octal} { yylval.int_val = strtol(yytext, nullptr, 0); return INT_CONST; }
{Hexadecimal} { yylval.int_val = strtol(yytext, nullptr, 0); return INT_CONST; }

. { return yytext[0]; }

%%

  • 文件最开头我们设置了一些选项, 你可以 RTFM 这些选项的含义. 如果不设置这些选项, Flex 就会要求我们在代码里定义一些额外的东西, 但我们实际上用不到这些东西.
  • 第二部分声明了必要的头文件, 然后是经典的 using namespace std.
  • 第三部分定义了一些正则表达式的规则, 比如空白符, 行注释等等. 这些规则其实直接写在第四部分也可以, 但那样太乱了, 还是给它们起个名字比较好理解一些.
  • 第四部分定义了 lexer 的行为:
    • 遇到空白符和注释就跳过.
    • 遇到 int, return 这种关键字就返回对应的 token.
    • 遇到标识符就把标识符存起来, 然后返回对应的 token. yytext 代表 lexer 当前匹配到的字符串的内容, 它的类型是 char *, 在此处对应读取到的一个标识符. yylval 用来向 parser 传递 lexer 读取到的内容, str_val 和之后的 int_val 是我们在 Bison 文件中定义的字段, 之后再解释.
    • 遇到整数字面量, 先把读取到的字符串转换成整数, 然后存起来, 并返回对应的 token.
    • 遇到单个字符, 就直接返回单个字符作为 token. . 这个正则表达式会匹配任意单个字符, 而 yytext[0] 实际上读取了目前匹配到的这个字符. 在 Flex/Bison 中, token 实际就是整数, 之前出现的 INT, RETURN, IDENT 等, 其实是 Bison 根据我们的定义生成的枚举 (enum). 所以此处相当于, 我们取了当前匹配到的字符, 然后把它转成了整数, 交给 Bison 生成的 parser 处理. 在 Bison 里, 我们可以直接通过写字符 (比如 '('), 来表示我们希望匹配 lexer 返回的一个字符转换得到的 token.

由于在union里也不允许存放带有构造函数、析构函数和复制构造函数等的类的对象,但是可以存放对应的类对象指针。

pragma once一般由编译器提供保证:同一个文件不会被包含多次。这里所说的”同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。无法对一个头文件中的一段代码作#pragma once声明,而只能针对文件。此方式不会出现宏名碰撞引发的奇怪问题,大型项目的编译速度也因此提供了一些。缺点是如果某个头文件有多份拷贝,此方法不能保证它们不被重复包含。在C/C++中,#pragma once是一个非标准但是被广泛支持的方式。

    #pragma once方式产生于#ifndef之后。#ifndef方式受C/C++语言标准的支持,不受编译器的任何限制;而#pragma once方式有些编译器不支持(较老编译器不支持,如GCC 3.4版本之前不支持#pragmaonce),兼容性不够好。#ifndef可以针对一个文件中的部分代码,而#pragma once只能针对整个文件。相对而言,#ifndef更加灵活,兼容性好,#pragma once操作简单,效率高。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
%code requires {
#include <memory>
#include <string>
}

%{

#include <iostream>
#include <memory>
#include <string>

// 声明 lexer 函数和错误处理函数
int yylex();
void yyerror(std::unique_ptr<std::string> &ast, const char *s);

using namespace std;

%}

// 定义 parser 函数和错误处理函数的附加参数
// 我们需要返回一个字符串作为 AST, 所以我们把附加参数定义成字符串的智能指针
// 解析完成后, 我们要手动修改这个参数, 把它设置成解析得到的字符串
%parse-param { std::unique_ptr<std::string> &ast }

// yylval 的定义, 我们把它定义成了一个联合体 (union)
// 因为 token 的值有的是字符串指针, 有的是整数
// 之前我们在 lexer 中用到的 str_val 和 int_val 就是在这里被定义的
// 至于为什么要用字符串指针而不直接用 string 或者 unique_ptr<string>?
// 请自行 STFW 在 union 里写一个带析构函数的类会出现什么情况
%union {
std::string *str_val;
int int_val;
}

// lexer 返回的所有 token 种类的声明
// 注意 IDENT 和 INT_CONST 会返回 token 的值, 分别对应 str_val 和 int_val
%token INT RETURN
%token <str_val> IDENT
%token <int_val> INT_CONST

// 非终结符的类型定义
%type <str_val> FuncDef FuncType Block Stmt Number

%%

// 开始符, CompUnit ::= FuncDef, 大括号后声明了解析完成后 parser 要做的事情
// 之前我们定义了 FuncDef 会返回一个 str_val, 也就是字符串指针
// 而 parser 一旦解析完 CompUnit, 就说明所有的 token 都被解析了, 即解析结束了
// 此时我们应该把 FuncDef 返回的结果收集起来, 作为 AST 传给调用 parser 的函数
// $1 指代规则里第一个符号的返回值, 也就是 FuncDef 的返回值
CompUnit
: FuncDef {
ast = unique_ptr<string>($1);
}
;

// FuncDef ::= FuncType IDENT '(' ')' Block;
// 我们这里可以直接写 '(' 和 ')', 因为之前在 lexer 里已经处理了单个字符的情况
// 解析完成后, 把这些符号的结果收集起来, 然后拼成一个新的字符串, 作为结果返回
// $$ 表示非终结符的返回值, 我们可以通过给这个符号赋值的方法来返回结果
// 你可能会问, FuncType, IDENT 之类的结果已经是字符串指针了
// 为什么还要用 unique_ptr 接住它们, 然后再解引用, 把它们拼成另一个字符串指针呢
// 因为所有的字符串指针都是我们 new 出来的, new 出来的内存一定要 delete
// 否则会发生内存泄漏, 而 unique_ptr 这种智能指针可以自动帮我们 delete
// 虽然此处你看不出用 unique_ptr 和手动 delete 的区别, 但当我们定义了 AST 之后
// 这种写法会省下很多内存管理的负担
FuncDef
: FuncType IDENT '(' ')' Block {
auto type = unique_ptr<string>($1);
auto ident = unique_ptr<string>($2);
auto block = unique_ptr<string>($5);
$$ = new string(*type + " " + *ident + "() " + *block);
}
;

// 同上, 不再解释
FuncType
: INT {
$$ = new string("int");
}
;

Block
: '{' Stmt '}' {
auto stmt = unique_ptr<string>($2);
$$ = new string("{ " + *stmt + " }");
}
;

Stmt
: RETURN Number ';' {
auto number = unique_ptr<string>($2);
$$ = new string("return " + *number + ";");
}
;

Number
: INT_CONST {
$$ = new string(to_string($1));
}
;

%%

// 定义错误处理函数, 其中第二个参数是错误信息
// parser 如果发生错误 (例如输入的程序出现了语法错误), 就会调用这个函数
void yyerror(unique_ptr<string> &ast, const char *s) {
cerr << "error: " << s << endl;
}
  • 设置一些必要的选项, 比如 %code requires.
  • 引用头文件, 声明 lexer 函数和错误处理函数. 如果不声明这些函数的话, parser 会找不到 Flex 中定义的 lexer, 也没办法正常报错.
  • 定义 parser 函数的参数. Bison 生成的 parser 函数返回类型一定是 int, 所以我们没办法通过返回值返回 AST, 所以只能通过参数来返回 AST 了. 当然你也可以通过全局变量来返回 AST, 但, 那样做很 dirty (如果你接触过函数式编程或了解软件工程技术的话).
  • 定义了 yylval, token 和非终结符的类型.
  • 定义了语法规则, 同时定义了 parser 解析完语法规则之后执行的操作.
  • 定义了错误处理函数.

这里主要涉及到flex和bison的知识,这个可能要看一会.

设计AST

设计 AST 这件事其实很简单, 你首先要知道:

  1. AST 保留了程序语法的结构.
  2. AST 是为了方便程序的处理而存在的, 不存在什么设计规范

注意%token是终结符,而其他的是非终结符

1
%type <str_val> FuncDef FuncType Block Stmt Number
1
2
3
4
5
%union {
std::string *str_val;
int int_val;
BaseAST *ast_val;
}

修改yylval类型.

生成了AST后,继续生成IR.

一旦完成了 IR 的生成, 你的编译器就已经初步成型了. 因为有一些基础设施的存在, 比如 LLVM IR, 现代的很多编译器都只会进行到 IR 生成这一步, 然后把后续的工作交给这些基础设施完成.

  • 编译器首先会对源代码做词法/语法分析, 生成 AST.
  • 然后在 AST 上进行语义分析, 建立符号表, 做类型检查, 报告语义错误, 等等.
  • 接着, 遍历语义分析后的 AST, 生成 IR.

我们会在每章的开头给出本章涉及内容的语法规范和语义规范. 一个功能完备的编译器应该能够检查输入程序是否符合语义规范, 并在发生语义错误时报错.

不过, 在课程实践中, 所有的测试用例均为符合语法/语义规范的 SysY 程序. 我们不要求你编写的编译器具备处理语法/语义错误的能力, 也不会考察这些内容. 但我们希望学有余力的同学, 能够在自己的编译器中检查这些问题, 并对其作出合适的处理, 比如像 clangrustc 一样, 给出精确到行列的错误信息, 甚至具备忽略错误继续扫描, 以及对错误给出修改建议的高级功能.

Koopa IR 中, 最大的单位是 Program, 它代表一个 Koopa IR 程序. Program 由若干全局变量 (Value) 和函数 (Function) 构成. Function 又由若干基本块 (BasicBlock) 构成, 基本块中是一系列指令, 指令也是 Value. 所以 Koopa IR 程序的结构如下所示:

  • ```
    Program
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    - 全局变量列表:

    - `Value` 1.
    - `Value` 2.
    -

    - 函数列表:

    - ```
    Function
  1.

  - 基本块列表:

    - ```
      BasicBlock
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74



1.

- 指令列表:
- `Value` 1.
- `Value` 2.
-

- `BasicBlock` 2.

-

- `Function` 2.

-

- **各类常量:** 整数常量 (`Integer`), 零初始化器 (`ZeroInit`), 等等.
- **参数引用:** 函数参数引用 (`FuncArgRef`) 等, 用来指代传入的参数.
- **内存分配:** 全局内存分配 (`GlobalAlloc`, 所有的全局变量都是这个玩意) 和局部内存分配 (`Alloc`).
- **访存指令:** 加载 (`Load`) 和存储 (`Store`).
- **指针运算:** `GetPtr``GetElemPtr`.
- **二元运算:** `Binary`, 比如加减乘除模/比较之类的运算都属于此类.
- **控制转移:** 条件分支 (`Branch`) 和无条件跳转 (`Jump`).
- **函数相关:** 函数调用 (`Call`) 和函数返回 (`Return`).

目标很明确了:

1. 我们应该生成一个 Koopa IR 程序.
2. 程序中有一个名字叫 `main` 的函数.
3. 函数里有一个入口基本块.
4. 基本块里有一条返回指令.
5. 返回指令的返回值就是 SysY 里 `return` 语句后跟的值, 也就是一个整数常量.

Koopa IR 目前有两种形式:

1. **文本形式:** 就是你在前文见到的字符串形式, 方便人类阅读.
2. **内存形式:** 即数据结构的形式, 你可以把它理解为另一种 “AST”, 方便程序处理.

你的编译器输出的文本形式 IR, 最终会被 Koopa IR 的相关工具 (比如 `koopac`) 读取, 变成内存形式的 IR, 然后作进一步处理. Koopa IR 的框架也提供了在两种形式的 IR 之间互相转换的接口.

所以, 考虑到上述情况, 你有以下几种生成 IR 的思路:

- 遍历 AST, 输出文本形式的 IR. 这样最简单, 适用于任何语言实现的编译器.
- 调用 Koopa IR 框架提供的接口. 使用 Rust 的同学可以尝试, 详见 Koopa IR 框架的 [crates.io](https://crates.io/crates/koopa) 以及[文档](https://docs.rs/koopa).
- 像定义 AST 一样定义表示 Koopa IR 的数据结构 (比如指令/基本块/函数等等), 然后遍历 AST 输出这种结构, 再遍历这种结构输出字符串.
- 对于使用 C/C++ 的同学, 在上一条的基础上, 你可以考虑把这种结构转换成 raw program, 然后使用 `libkoopa` 中的相关接口, 将 raw program 转换成其他形式的 Koopa IR 程序



第一种思路可能是大部分同学会采用的思路, 因为它相当简单且直观, 实现难度很低. 但其缺点是, 你在生成目标代码之前, 不得不再次将文本形式的 Koopa IR 转换成某种数据结构——这相当于再写一个编译器. 否则, 你的程序几乎无法直接基于文本形式 IR 生成汇编.

不过好在, 我们为大家提供了能够处理 Koopa IR 的库, 你可以使用其中的实现, 来将文本形式的 IR 转换为内存形式.

```c
// 解析字符串 str, 得到 Koopa IR 程序
koopa_program_t program;
koopa_error_code_t ret = koopa_parse_from_string(str, &program);
assert(ret == KOOPA_EC_SUCCESS); // 确保解析时没有出错
// 创建一个 raw program builder, 用来构建 raw program
koopa_raw_program_builder_t builder = koopa_new_raw_program_builder();
// 将 Koopa IR 程序转换为 raw program
koopa_raw_program_t raw = koopa_build_raw_program(builder, program);
// 释放 Koopa IR 程序占用的内存
koopa_delete_program(program);

// 处理 raw program
// ...

// 处理完成, 释放 raw program builder 占用的内存
// 注意, raw program 中所有的指针指向的内存均为 raw program builder 的内存
// 所以不要在 raw program 处理完毕之前释放 builder
koopa_delete_raw_program_builder(builder);
  • 最上层是 koopa_raw_program_t, 也就是 Program.
  • 之下是全局变量定义列表和函数定义列表.
    • 在 raw program 中, 列表的类型是 koopa_raw_slice_t.
    • 本质上这是一个指针数组, 其中的 buffer 字段记录了指针数组的地址 (类型是 const void **), len 字段记录了指针数组的长度, kind 字段记录了数组元素是何种类型的指针
    • 在访问时, 你可以通过 slice.buffer[i] 拿到列表元素的指针, 然后通过判断 kind 来决定把这个指针转换成什么类型.
  • koopa_raw_function_t 代表函数, 其中是基本块列表.
  • koopa_raw_basic_block_t 代表基本块, 其中是指令列表.
  • koopa_raw_value_t 代表全局变量, 或者基本块中的指令.

现在卡在了ast生成IR,根据教程貌似需要遍历ast.也就是遍历类似树的结构.当然方法也不知这一种.我从网上copy了一份代码阅读阅读.

代码鉴赏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
/*****************************************************
* Definition of the AST nodes.
*
* Please read the description of each class for details.
*
* Keltin Leung
*/

#ifndef __MIND_AST__
#define __MIND_AST__

#include "define.hpp"
#include <iostream>
#include <string>

// the prefix of an ast attribution
#define ATTR(x) ast_attr_##x##_

namespace mind {

#define MIND_AST_DEFINED
namespace ast {

/* The base class of all AST nodes.
*
* NOTE: Don't instantiate this class.
* Please read the description of the subclasses.
*/
class ASTNode {
public:
// types of the AST nodes
typedef enum {
ADD_EXPR,
AND_EXPR,
ASSIGN_EXPR,
BOOL_CONST,
BIT_NOT_EXPR,
BOOL_TYPE,
BREAK_STMT,
CONTINUE_STMT,
CALL_EXPR,
COMP_STMT,
DIV_EXPR,
EQU_EXPR,
EMPTY_STMT,
EXPR_STMT,
FUNC_DEFN,
GEQ_EXPR,
GRT_EXPR,
IF_STMT,
IF_EXPR,
INT_CONST,
INT_TYPE,
LEQ_EXPR,
LES_EXPR,
LVALUE_EXPR,
MOD_EXPR,
MUL_EXPR,
NEG_EXPR,
NEQ_EXPR,
NOT_EXPR,
OR_EXPR,
PROGRAM,
RETURN_STMT,
SUB_EXPR,
VAR_DECL,
VAR_REF,
WHILE_STMT,
FOR_STMT,
FOD,
INDEX_EXPR
} NodeType; // 定义了一个,枚举类型 ast节点类型

protected:
// names of each kind of the AST nodes
static const char *node_name[];
// kind of this node
NodeType kind; //上面声明的节点类型
// position in the source text
Location *loc; //在另一各文件中声明的 是一个结构体 有构造函数 表示ast节点位置
// for subclass constructors only
virtual void setBasicInfo(NodeType, Location *); //she

public:
// gets the node kind
virtual NodeType getKind(void);
// gets the location
virtual Location *getLocation(void);
// prints to the specified output stream
virtual void dumpTo(std::ostream &);
// for Visitor
virtual void accept(Visitor *) = 0;
// remember: let alone the memory deallocation stuff
virtual ~ASTNode(void) {}
};

/* Node representing a type.
*
* NOTE:
* it is purely an interface.
* IMPLEMENTATION:
* IntType, BoolType, ArrayType
*/
class Type : public ASTNode {
public:
type::Type *ATTR(type); // for semantic analysis
};

/* Node representing a statement.
*
* NOTE:
* it is purely an interface.
* IMPLEMENTATION:
* AssignStmt, ReturnStmt,
* IfStmt, WhileStmt,
*/
class Statement : public ASTNode { /* pure interface */
};

/* Node representing an expression.
*
* NOTE:
* it is purely an interface.
* IMPLEMENTATION:
* ...
*/
class EmptyStmt : public Statement {
public:
EmptyStmt(Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);
};
class Expr : public ASTNode {
public:
type::Type *ATTR(type); // for semantic analysis
tac::Temp ATTR(val); // for tac generation
};

/* Node representing a left-value expression (lvalue).
*
* NOTE:
* it is purely an interface.
* IMPLEMENTATION:
* VarRef, ArrayRef
*/
class Lvalue : public ASTNode {
public:
enum {
SIMPLE_VAR, // referencing simple variable
ARRAY_ELE // referencing array element
} ATTR(lv_kind);

type::Type *ATTR(type); // for semantic analysis
};

/* Node representing a program.
*
* SERIALIZED FORM:
* (program [ FUNC_1 FUNC_2 ... FUNC_N ])
*/
class Program : public ASTNode {
public:
Program(ASTNode *first, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
// FuncList* functions;
// VarList* globals;
FuncOrGlobalList *func_and_globals;
symb::Function *ATTR(main); // for semantic analysis
scope::GlobalScope *ATTR(gscope); // for semantic analysis
};

/* Node representing a variable declaration.
*
* SERIALIZED FORM:
* (var NAME TYPE)
*/
class VarDecl : public Statement {
public:
VarDecl(std::string name, Type *type, Location *l);
VarDecl(std::string name, Type *type, DimList *dim, Location *l);

VarDecl(std::string name, Type *type, Expr *init, Location *l);
virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
std::string name;
Type *type;
Expr *init;
DimList *dim;

symb::Variable *ATTR(sym); // for semantic analysis
};

class FuncDefn : public ASTNode {
public:
FuncDefn(std::string name, Type *type, VarList *formals, StmtList *stmts,
Location *l);
FuncDefn(std::string name, Type *type, VarList *formals, EmptyStmt *empty,
Location *l);
virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
std::string name;
Type *ret_type;
VarList *formals;
StmtList *stmts;
bool forward_decl; // is this FuncDefn a forward declaration or full
// definition?
symb::Function *ATTR(sym); // for semantic analysis
};

class CallExpr : public Expr {
public:
CallExpr(ExprList *expr_list, std::string func, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

std::string func;
ExprList *expr_list;
symb::Function *ATTR(sym); // for tac generation

};

class IndexExpr : public Expr {
public:
IndexExpr(Expr *, ExprList *, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

ExprList *expr_list;
DimList *ATTR(dim);
};

class FuncOrGlobal {
public:
FuncOrGlobal(FuncDefn *f, VarDecl *d) {
func = f;
decl = d;
}
FuncDefn *func; // assert ( func == NULL xor decl == NULL)
VarDecl *decl;
};
class FOD {
public:
FOD(FuncList *f, VarList *d) {
func = f;
decl = d;
}
FuncList *func;
VarList *decl;
};
/* Node representing the integer type.
*
* SERIALIZED FORM:
* (itype)
*/
class IntType : public Type {
public:
IntType(Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);
};

/* Node representing the Boolean type.
*
* SERIALIZED FORM:
* (btype)
*/
class BoolType : public Type {
public:
BoolType(Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);
};

/* Node representing an assignment statement.
*
* SERIALIZED FORM:
* (assign LEFT RIGHT)
*/
class AssignExpr : public Expr {
public:
AssignExpr(Lvalue *left, Expr *e, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Lvalue *left;
Expr *e;
};

/* Node representing a return statement.
*
* SERIALIZED FORM:
* (return EXPR)
*/
class ReturnStmt : public Statement {
public:
ReturnStmt(Expr *e, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *e;
};

/* Node representing a while statement.
*
* SERIALIZED FORM:
* (while CONDITION LOOP_BODY)
* //(for INIT COND UPDATE LOOP_BODY)
*/
class WhileStmt : public Statement {
public:
WhileStmt(Expr *cond, Statement *loop_body, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *condition;
Statement *loop_body;
};

/* Node representing a for statement.
*
* SERIALIZED FORM:
* (while CONDITION LOOP_BODY)
* //(for INIT COND UPDATE LOOP_BODY)
*/
class ForStmt : public Statement {
public:
ForStmt(Statement *init, Expr *cond, Expr *update, Statement *loop_body, Location *l);
ForStmt(Expr *init, Expr *cond, Expr *update, Statement *loop_body, Location *l);
ForStmt(Expr *first_condition, Statement *loop_body, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *condition, *update, *first_condition;
Statement *loop_body;
ASTNode *init;
scope::Scope *ATTR(scope);
};



/* Node representing an comp statement.
*
* SERIALIZED FORM:
* ( { StmtList } )
*/
class CompStmt : public Statement {
public:
CompStmt(StmtList *stmts, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
StmtList *stmts;

scope::Scope *ATTR(scope);
};
/* Node representing an if statement.
*
* SERIALIZED FORM:
* (if CONDITION TRUE_BRANCH FALSE_BRANCH)
*/
class IfStmt : public Statement {
public:
IfStmt(Expr *cond, Statement *true_branch, Statement *false_branch,
Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *condition;
Statement *true_brch;
Statement *false_brch;
};

/* Node representing an expression statement.
*
* SERIALIZED FORM:
* (expr EXPR)
*/
class ExprStmt : public Statement {
public:
ExprStmt(Expr *e, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *e;
};

class BreakStmt : public Statement {
public:
BreakStmt(Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);
};

class ContinueStmt : public Statement {
public:
ContinueStmt(Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);
};
class ContStmt : public Statement {
public:
ContStmt(Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);
};

/* Node representing the reference to a variable.
*
* NOTE:
* (object == NULL) means there is no receiver.
* SERIALIZED FORM:
* (varref VAR_NAME OBJECT_EXPR)
* (varref VAR_NAME ())
*/
class VarRef : public Lvalue {
public:
// VarRef (Expr* object, SID var_name,Location* l);
VarRef(std::string var_name, Location *l);
VarRef(std::string var_name, IndexExpr *expr, Location* l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *owner; // only to pass compilation, not used
std::string var;
IndexExpr *expr;

symb::Variable *ATTR(sym); // for tac generation
};

class PointerRef : public Lvalue {
public:
PointerRef(Expr *pointer, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *pointer;

symb::Variable *ATTR(sym); // for tac generation
};

/* Node representing an expression of lvalue.
*
* SERIALIZED FORM:
* (lvalue REFERENCE)
*/
class LvalueExpr : public Expr {
public:
LvalueExpr(Lvalue *lv, Location *l);
// LvalueExpr (Lvalue* lv, Expr* rv,
// Location* l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Lvalue *lvalue;
// Expr* rvalue;
};
/* Node representing an integer constant .
*
* SERIALIZED FORM:
* (iconst VALUE)
*/
class IntConst : public Expr {
public:
IntConst(int value, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
int value;
};

/* Node representing a Boolean constant .
*
* SERIALIZED FORM:
* (bconst VALUE)
*/
class BoolConst : public Expr {
public:
BoolConst(bool value, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
bool value;
};

/* Node representing the null expression .
*
* SERIALIZED FORM:
* (null)
*/
class NullExpr : public Expr {
public:
NullExpr(Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);
};

/* Node representing an addition.
*
* SERIALIZED FORM:
* (add EXPR1 EXPR2)
*/
class AddExpr : public Expr {
public:
AddExpr(Expr *e1, Expr *e2, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *e1;
Expr *e2;
};

/* Node representing a substraction.
*
* SERIALIZED FORM:
* (sub EXPR1 EXPR2)
*/
class SubExpr : public Expr {
public:
SubExpr(Expr *e1, Expr *e2, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *e1;
Expr *e2;
};

/* Node representing a multiplcation.
*
* SERIALIZED FORM:
* (mul EXPR1 EXPR2)
*/
class MulExpr : public Expr {
public:
MulExpr(Expr *e1, Expr *e2, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *e1;
Expr *e2;
};

/* Node representing a division.
*
* SERIALIZED FORM:
* (div EXPR1 EXPR2)
*/
class DivExpr : public Expr {
public:
DivExpr(Expr *e1, Expr *e2, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *e1;
Expr *e2;
};

/* Node representing a modular operation.
*
* SERIALIZED FORM:
* (mod EXPR1 EXPR2)
*/
class ModExpr : public Expr {
public:
ModExpr(Expr *e1, Expr *e2, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *e1;
Expr *e2;
};
/* Node representing a greater-than comparision.
*
* SERIALIZED FORM:
* (grt EXPR1 EXPR2)
*/
class GrtExpr : public Expr {
public:
GrtExpr(Expr *e1, Expr *e2, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *e1;
Expr *e2;
};

/* Node representing a greater-than-or-equal-to comparision.
*
* SERIALIZED FORM:
* (geq EXPR1 EXPR2)
*/
class GeqExpr : public Expr {
public:
GeqExpr(Expr *e1, Expr *e2, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *e1;
Expr *e2;
};

/* Node representing a less-than comparision.
*
* SERIALIZED FORM:
* (les EXPR1 EXPR2)
*/
class LesExpr : public Expr {
public:
LesExpr(Expr *e1, Expr *e2, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *e1;
Expr *e2;
};

/* Node representing a less-than-or-equal-to comparision.
*
* SERIALIZED FORM:
* (leq EXPR1 EXPR2)
*/
class LeqExpr : public Expr {
public:
LeqExpr(Expr *e1, Expr *e2, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *e1;
Expr *e2;
};

/* Node representing an equivalence comparision.
*
* SERIALIZED FORM:
* (equ EXPR1 EXPR2)
*/
class EquExpr : public Expr {
public:
EquExpr(Expr *e1, Expr *e2, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *e1;
Expr *e2;
};
/* Node representing an non-equivalence comparision.
*
* SERIALIZED FORM:
* (neq EXPR1 EXPR2)
*/
class NeqExpr : public Expr {
public:
NeqExpr(Expr *e1, Expr *e2, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *e1;
Expr *e2;
};

/* Node representing a logical-and operation .
*
* SERIALIZED FORM:
* (and EXPR1 EXPR2)
*/
class AndExpr : public Expr {
public:
AndExpr(Expr *e1, Expr *e2, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *e1;
Expr *e2;
};

/* Node representing a logical-or operation .
*
* SERIALIZED FORM:
* (and EXPR1 EXPR2)
*/
class OrExpr : public Expr {
public:
OrExpr(Expr *e1, Expr *e2, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *e1;
Expr *e2;
};
class IfExpr : public Expr {
public:
IfExpr(Expr *cond, Expr *e1, Expr *e2, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *condition;
Expr *true_brch;
Expr *false_brch;
};

/* Node representing a negating operation.
*
* SERIALIZED FORM:
* (neg EXPR)
*/
class NegExpr : public Expr {
public:
NegExpr(Expr *e, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *e;
};
/* Node representing a logical-not operation.
*
* SERIALIZED FORM:
* (not EXPR)
*/
class NotExpr : public Expr {
public:
NotExpr(Expr *e, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *e;
};
/* Node representing a bitwise-not operation.
*
* SERIALIZED FORM:
* (bitnot EXPR)
*/
class BitNotExpr : public Expr {
public:
BitNotExpr(Expr *e, Location *l);

virtual void accept(Visitor *);
virtual void dumpTo(std::ostream &);

public:
Expr *e;
};
extern bool print_decorated_ast;
} // namespace ast
} // namespace mind

#endif // __MIND_AST__
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
/*****************************************************
* Implementation of the "ASTNode" abstract class.
*
* Keltin Leung
*/

#include "ast/ast.hpp"
#include "config.hpp"
#include "location.hpp"
#include "options.hpp"

using namespace mind;
using namespace mind::ast;

/* Names of the AST nodes.
*
* NOTE: The order of node_name's must be the same with that of NodeType's.
*/
const char *ASTNode::node_name[] = {"add",
"and",
"assign",
"bconst",
"bitnot",

"btype",
"break",
"continue",
"call",
"comp",
"div",

"equ",
"empty",
"expr",
"func",
"geq",

"grt",
"if",
"if",
"iconst",
"itype",
"leq",
"les",
"lvalue",
"mod",
"mul",
"neg",
"neq",
"not",
"or",
"program",
"return",
"sub",
"var",
"varref",
"while",
"for",
"FuncOrDecl",
"index"};

/* Whether to print the decorated abstract syntax tree.
*/
bool print_decorated_ast = false;

/* Sets the basic information of a node.
*
* PARAMETERS:
* k - the node kind
* l - position in the source text
*/
void ASTNode::setBasicInfo(NodeType k, Location *l) {
kind = k;
loc = l;
}

/* Gets the node kind.
*
* RETURNS:
* the node kind
*/
ASTNode::NodeType ASTNode::getKind(void) { return kind; }

/* Gets the source text location of this node
*
* RETURNS:
* the source text location
*/
Location *ASTNode::getLocation(void) { return loc; }

/* Dumps this node to an output stream.
*
* PARAMETERS:
* os - the output stream
*/
void ASTNode::dumpTo(std::ostream &os) {
os << "(" << node_name[kind];
if (Option::getLevel() != Option::PARSER)
os << " (" << loc->line << " " << loc->col << ")";
incIndent(os);
}

/* Outputs an ASTNode.
*
* PARAMETERS:
* os - the output stream
* p - the ASTNode
* RETURNS:
* the output stream
*/
std::ostream &mind::operator<<(std::ostream &os, ASTNode *p) {
if (p == NULL)
os << "!!NULL!!";
else
p->dumpTo(os);
return os;
}
/* Outputs a FuncList.
*
* PARAMETERS:
* os - the output stream
* l - the Func list
* RETURNS:
* the output stream
*/
std::ostream &mind::operator<<(std::ostream &os, FuncList *l) {
os << "[";
if (!l->empty()) {
incIndent(os);
os << " ";
FuncList::iterator it = l->begin();
os << *it;
while (++it != l->end()) {
newLine(os);
os << *it;
}
decIndent(os);
newLine(os);
}
os << "]";

return os;
};

/* Outputs a VarList.
*
* PARAMETERS:
* os - the output stream
* l - the VarDecl list
* RETURNS:
* the output stream
*/
std::ostream &mind::operator<<(std::ostream &os, VarList *l) {
os << "[";
if (!l->empty()) {
incIndent(os);
os << " ";
VarList::iterator it = l->begin();
os << *it;
while (++it != l->end()) {
newLine(os);
os << *it;
}
decIndent(os);
newLine(os);
}
os << "]";

return os;
}

/* Outputs a StmtList.
*
* PARAMETERS:
* os - the output stream
* l - the Statement list
* RETURNS:
* the output stream
*/
std::ostream &mind::operator<<(std::ostream &os, StmtList *l) {
os << "[";
if (!l->empty()) {
incIndent(os);
os << " ";
StmtList::iterator it = l->begin();
os << *it;
while (++it != l->end()) {

newLine(os);
os << *it;
}
decIndent(os);
newLine(os);
}
os << "]";

return os;
}

/* Outputs an ExprList .
*
* PARAMETERS:
* os - the output stream
* l - the Expr list
* RETURNS:
* the output stream
*/
std::ostream &mind::operator<<(std::ostream &os, ExprList *l) {
os << "[";
if (!l->empty()) {
incIndent(os);
os << " ";
ExprList::iterator it = l->begin();
os << *it;
while (++it != l->end()) {
newLine(os);
os << *it;
}
decIndent(os);
newLine(os);
}
os << "]";

return os;
}
std::ostream &mind::operator<<(std::ostream &os, DimList *l) {
os << "[";
if (!l->empty()) {
incIndent(os);
os << " ";
DimList::iterator it = l->begin();
os << *it;
while (++it != l->end()) {
newLine(os);
os << *it;
}
decIndent(os);
newLine(os);
}
os << "]";

return os;
}
std::ostream &mind::operator<<(std::ostream &os, FuncOrGlobalList *l) {
os << "[";
if (!l->empty()) {
incIndent(os);
os << " ";
FuncOrGlobalList::iterator it = l->begin();
os << *it;
while (++it != l->end()) {

newLine(os);
os << *it;
}
decIndent(os);
newLine(os);
}
os << "]";

return os;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/*****************************************************
* Definition of the AST visitor pattern.
*
* It is only an interface (we provide an empty body
* just to make your life easier..).
*
* Keltin Leung
*/

#ifndef __MIND_VISITOR__
#define __MIND_VISITOR__

#include "ast/ast.hpp"

namespace mind {

namespace ast {

/* AST Visitor Pattern.
*
* HOW TO USE:
* 1. Create a subclass of Visitor;
* 2. Implement visit(ast::Program*) (required);
* 3. Implement the other visiting functions as you need;
* 4. We provide an empty function body ({ }) instead of
* setting it pure (= 0) so that you do not need to
* implement all the functions as you do with a pure
* interface.
*/
class Visitor {
public:
// Expressions
virtual void visit(CallExpr *) {}
virtual void visit(AddExpr *) {}
virtual void visit(AndExpr *) {}
virtual void visit(AssignExpr *) {}
virtual void visit(IfExpr *) {}
virtual void visit(OrExpr *) {}
virtual void visit(BoolConst *) {}
virtual void visit(DivExpr *) {}
virtual void visit(EquExpr *) {}
virtual void visit(GeqExpr *) {}
virtual void visit(GrtExpr *) {}
virtual void visit(NeqExpr *) {}
virtual void visit(IntConst *) {}
virtual void visit(LeqExpr *) {}
virtual void visit(LesExpr *) {}
virtual void visit(LvalueExpr *) {}
virtual void visit(ModExpr *) {}
virtual void visit(MulExpr *) {}
virtual void visit(NegExpr *) {}
virtual void visit(NotExpr *) {}
virtual void visit(BitNotExpr *) {}
virtual void visit(SubExpr *) {}
virtual void visit(IndexExpr *) {}

// Lvalues
virtual void visit(VarRef *) {}
// Types
virtual void visit(BoolType *) {}
virtual void visit(IntType *) {}

// Statements
virtual void visit(ExprStmt *) {}
virtual void visit(CompStmt *) {}
virtual void visit(WhileStmt *) {}
virtual void visit(ForStmt *) {}
virtual void visit(EmptyStmt *) {}
virtual void visit(BreakStmt *) {}
virtual void visit(ContinueStmt *) {}
virtual void visit(IfStmt *) {}
virtual void visit(ReturnStmt *) {}
// Declarations
virtual void visit(Program *) = 0;
virtual void visit(FuncDefn *) {}
virtual void visit(VarDecl *) {}

virtual ~Visitor() {}
};
} // namespace ast
} // namespace mind

#endif // __MIND_VISITOR__

Update

新找到一个项目,journey to compiler

这个比较好一点,我尝试照着这个做一下.可能要分几个专题.

lexical scanning

首先开始词法分析,主要是识别+ - * /和字面数字

However, there will be times when we need to “put back” a character if we have read too far ahead in the input stream. We also want to track what line we are currently on so that we can print the line number in our debug messages. All of this is done by the next() function:

大致流程

parser

语法分析设计到BNF或者EBNF.需要设计AST.

1
2
3
4
5
6
7
8
9
10
11
12
// AST node types
enum {
A_ADD, A_SUBTRACT, A_MULTIPLY, A_DIVIDE, A_INTLIT
};

// Abstract Syntax Tree structure
struct ASTnode {
int op; // "Operation" to be performed on this tree
struct ASTnode *left; // Left and right child trees
struct ASTnode *right;
int intvalue; // For A_INTLIT, the integer value
};

defs文件新增ASTnode

1
2
3
4
5
6
7
8
9
struct ASTnode {
// op to be performed on this tree
// e.g. A_ADD
int op;
// left and right child tree
struct ASTnode *left;
struct ASTnode *right;
int intvalue; //for integer
};

增加产生ast节点函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct ASTnode *binexpr(void)
{
struct ASTnode *n, *left, *right;
int nodetype;

// 左值
// Get the integer literal on the left.
// Fetch the next token at the same time.
left = primary();

if (Token.token == _EOF)
{
return left;
}
nodetype = tok2aop(Token.token);

scan(&Token);
// 创建右值
right = binexpr();

n = mkastnode(nodetype, left, right, 0);
return n;
}

以及对于ast节点赋值的一些操作.

还需要遍历树结构,这里采用先序遍历.

misc

Docker -v | volume 挂载宿主机目录导致容器内文件被覆盖问题 - 二柒席地而坐 - 博客园 (cnblogs.com)

-------------本文结束感谢您的阅读-------------
感谢阅读.

欢迎关注我的其它发布渠道