License: CC BY-SA 4.0
闲话
看了半天文档终于懂了 Bison 里怎么处理带 |
的语法了。
为什么 info
要做成 Emacs 格式啊?Vimer 无能狂怒(
Lv 3.1
一元表达式
首先当然是设计 AST 了。
这里我用 std::varient
,不知道有没有更优雅的写法。
class PrimaryExpAST: public BaseAST{
public:
std::variant<std::unique_ptr<BaseAST>, int> inside_exp;
// ...
};
然后就是修改 sysy.y
.
PrimaryExp:
"(" Exp ")" {
...
} | Number {
...
};
生成 Koopa IR
一开始生成的是这样的东西:
fun @main():i32{
%entry:
%0 = 6
%1 = ne 0, %0
%2 = sub 0, %1
%3 = sub 0, %2
%4 = %3
ret %4
}
然后报错了.
发现 koopa ir
中不能出现 %4=%3
这样的废话。
查询 Koopa IR 规范, 你会发现, Koopa IR 并不支持一元运算, 而只支持如下的二元运算:
...
这是因为, 目前已知有意义的一元操作均可用二元操作表示:
...
所以,万事开头难,中间难,结尾也难(
解决方法就是在生成 IR 时避免这样的废话。
本来想用 %4 = add 0, 3
这样的废话的,但是感觉不大优雅,就换成了 class
包装的值,用 std::stack
保存。
发现不知道为什么重载不了 operator<<
,于是大力上了 template
.
class Koopa_val{
private:
int val;
bool is_im;
public:
Koopa_val(bool typ, int x){
is_im = typ;
val = x;
}
std::string get_str(){
if(is_im){
return std::to_string(val);
} else {
return std::string("%") + std::to_string(val);
}
}
template<typename T>
friend T &operator<<(T &outstr, const Koopa_val& me){
if(me.is_im){
outstr << me.val;
} else {
outstr << "%" << me.val;
}
return outstr;
}
};
重构 AST
感觉之前写的 AST 都是 std::shared_ptr<BaseAST>
,容易搞不清楚 BaseAST
到底是哪种。
于是将 .hpp
中的内容拆了一部分(output
)到 .cpp
里。
然后一通查找+替换。
输出汇编
你需要注意:
在 C/C++ 中, 并没有
%0 = ...
和%1 = ...
这样的结构. 前一条eq
指令和后一条sub
指令的指针会被按照顺序, 先后存放在%entry
基本块中的指令列表中.sub
指令中出现的%0
, 表示在内存形式中实际上就是一个指向前一条eq
指令的指针.
参考了 Gold_stein 的文章,用 unordered_map
记录值。
写到一半发现 IDE 报错了。
为什么不支持 C++20 的特性?哦原来是我 CMakeLists.txt
里写了用 C++17. 改了.
但是 LunarVim IDE 里还是会报错,看着很烦,一通搜索后发现只要在项目目录里加一个 compile_flags.txt
然后里面写 -std=c++20
就行了。
最后还是多用了两个编译器。不想修了,留到 lv4 吧(
Lv 3.2
设计 AST
感觉后面还会有一大堆的二元运算……
它们的代码都是相似的,能不能稍微偷点懒呢?
template
大法好!
template<const int level>
class BinaryExpAST: public BaseAST{
public:
std::optional<std::unique_ptr<BinaryExpAST<level>>> now_level;
std::optional<std::unique_ptr<BinaryOpAST>> binary_op;
std::unique_ptr<BinaryExpAST<level-1>> nxt_level;
void output(Ost &outstr, std::string prefix) const override;
};
别忘了给最后一级做个特化。
template<>
class BinaryExpAST<0>: public BaseAST{
public:
std::optional<std::unique_ptr<BinaryExpAST<0>>> now_level;
std::optional<std::unique_ptr<BinaryOpAST>> binary_op;
std::unique_ptr<UnaryExpAST> nxt_level;
void output(Ost &outstr, std::string prefix) const override;
};
然后发现编译不了了。
/usr/bin/ld: CMakeFiles/compiler.dir/sysy.tab.cpp.o:(.rodata._ZTVN8Ast_Base8Ast_Defs12BinaryExpASTILi1EEE[_ZTVN8Ast_Base8Ast_Defs12BinaryExpASTILi1EEE]+0x20): undefined reference to `Ast_Base::Ast_Defs::BinaryExpAST<1>::output(std::__cxx11::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) const'
一通 STFW & RTFM 后发现 Bison 里调用模板并不会(在 cpp 中)生成对应的模板代码,因此编译器找不到 BinaryExpAST<1>
对应的 output
函数,然后就报错了。
原先的解决方法很暴力:
BinaryExpAST<1> foo;
然后又是一通 STFW 后发现:
template class BinaryExpAST<1>;
我就说 cpp 不可能只有一种解决方案
然后发现每个模板都要写一次……
问 ChatGPT 后得到了如下的做法。
template<int level> class BinaryExpAST:
public BinaryExpAST_Base<BinaryExpAST<level>, BinaryExpAST<level-1>>{
public:
static void instantiate(){
BinaryExpAST<level> sth;
BinaryExpAST<level-1>::instantiate();
}
};
template<> class BinaryExpAST<0>:
public BinaryExpAST_Base<BinaryExpAST<0>, UnaryExpAST>{
public:
static void instantiate(){ }
};
template class BinaryExpAST_Base<BinaryExpAST<0>, UnaryExpAST>;
auto init_hook = []() {
BinaryExpAST<3>::instantiate();
return 0;
}();
至少能跑了
Bison 中“或”的语法
这节出现了这样的东西:
AddExp ::= MulExp | AddExp ("+" | "-") MulExp
很显然 Bison 是不支持直接用括号的。怎么办呢?
AddOp: ADD { $$ = new BinaryOpAST(OP_ADD); } ;
SubOp: SUB { $$ = new BinaryOpAST(OP_SUB); } ;
MulOp: MUL { $$ = new BinaryOpAST(OP_MUL); } ;
DivOp: DIV { $$ = new BinaryOpAST(OP_DIV); } ;
ModOp: MOD { $$ = new BinaryOpAST(OP_MOD); } ;
Lv1Op: AddOp | SubOp ;
AddExp:
MulExp
| AddExp Lv1Op MulExp {
auto ast = new BinaryExpAST<1>();
ast->now_level = cast_ast<BinaryExpAST<1>>($1);
ast->binary_op = cast_ast<BinaryOpAST>($2);
ast->nxt_level = cast_ast<BinaryExpAST<0>>($3);
$$ = ast;
}
;
Lv 3.3
前面打好基础后后面应该就容易了(
还是老流程:设计 AST,更新 Lexer & Parser,输出 Koopa IR,输出 RISC-V.
设计 AST
定义了几个别名:
using MulExp = BinaryExpAST<0>;
using AddExp = BinaryExpAST<1>;
using RelExp = BinaryExpAST<2>;
using EqExp = BinaryExpAST<3>;
using LAndExp = BinaryExpAST<4>;
using LOrExp = BinaryExpAST<5>;
然后没了。
更新 Lexer & Parser
Bison 里没找到用宏的方式,只能手打了……
输出 Koopa IR
注意逻辑运算符的输出只能是 \(0\) 或 \(1\).
实现方法就是加一个 is_logic_op()
函数,然后特判。
输出汇编
不出意外的话把几个 KOOPA_RBO_
开头的指令加进去就行了。
不出意外的话应该是出意外了。在 -riscv
上 WA+AE.
首先就是由于偷懒没有用 x0
寄存器而导致在 test 06,27 上寄存器超标,然后还有 19,20 WA 了.
WA 的两个点到 /opt/bin/testcases
里找到了数据点,发现汇编生成错了.
超标的那个就留到 lv4 吧(咕咕咕
标签:std,ast,实践,class,BinaryExpAST,编译,Part3,template,public From: https://www.cnblogs.com/x383494/p/18092891