AST
抽象语法树是目前编译器中最复杂的部分,它是编译器的中间表示,也是编译器的核心。本节将介绍AST的设计和实现。
AST的设计
基本上,所有源代码中的基础单位都会对应抽象语法树中的一个节点。抽象语法树有很多类型的节点,他们可能会相互引用。
所有的节点都必须实现Node
trait,这个trait定义了节点的基本行为。
#[enum_dispatch]
pub trait Node: RangeTrait + FmtTrait + PrintTrait {
fn emit<'a, 'b>(
&mut self,
ctx: &'b mut Ctx<'a>,
builder: &'b BuilderEnum<'a, '_>,
) -> NodeResult;
}
/// print trait to report the AST node structure
#[enum_dispatch]
pub trait PrintTrait {
fn print(&self, tabs: usize, end: bool, line: Vec<bool>);
}
一般来说,所有的节点都需要加入NodeEnum
中,并且使用#[node]
proc macro进行修饰。
同时需要在fmt.rs
中加入format相关的函数。
如果你想学习如何添加新的节点,可以先从简单的节点开始,比如BoolConstNode
#[node]
pub struct BoolConstNode {
pub value: bool,
}
你可能注意到了,Node
trait继承了RangeTrait
,这个trait定义了节点的位置信息。
#[enum_dispatch]
pub trait RangeTrait {
fn range(&self) -> Range;
}
一般来说,RangeTrait
的实现通过#[node]
宏来自动生成,你不需要手动实现它。
Node
接口中的print
函数用于打印节点的信息,它会被用于调试。print
打印的结果和tree
的输出非常像,你需要用一些工具函数来
格式化输出。以ifnode
的print
函数为例:
#![allow(unused)] fn main() { fn print(&self, tabs: usize, end: bool, mut line: Vec<bool>) { deal_line(tabs, &mut line, end); tab(tabs, line.clone(), end); println!("IfNode"); self.cond.print(tabs + 1, false, line.clone()); if let Some(el) = &self.els { self.then.print(tabs + 1, false, line.clone()); el.print(tabs + 1, true, line.clone()); } else { self.then.print(tabs + 1, true, line.clone()); } } }
emit
函数是生成llvm代码的核心,它会调用llvm api构造自己对应的llvm ir。在编译的时候,最上层节点的emit
会被调用,
该函数会递归的调用自己的子节点的emit
函数,最终生成整个程序的llvm ir。
下方是ifnode
的emit
函数:
fn emit<'a, 'b>(
&mut self,
ctx: &'b mut Ctx<'a>,
builder: &'b BuilderEnum<'a, '_>,
) -> NodeResult {
let cond_block = builder.append_basic_block(ctx.function.unwrap(), "if.cond");
let then_block = builder.append_basic_block(ctx.function.unwrap(), "if.then");
let else_block = builder.append_basic_block(ctx.function.unwrap(), "if.else");
let merge_block = builder.append_basic_block(ctx.function.unwrap(), "if.after");
builder.build_unconditional_branch(cond_block);
ctx.position_at_end(cond_block, builder);
let mut gen_def = None;
let mut skip_body = false;
let cond_range = self.cond.range();
if let NodeEnum::Def(def) = &*self.cond {
// check if it is a `let ... = ... as ...`
if let Some(e) = &def.value_expression {
if let NodeEnum::AsNode(a) = &**e {
if let Some((_, r)) = &a.tail {
// tail not allowed in `if let .. as ..`
ctx.add_diag(
r.new_err(ErrorCode::IF_LET_DOES_NOT_EXPECT_TAIL)
.add_help("remove the tailling symbol")
.clone(),
);
}
let mut transformed_is = NodeEnum::IsNode(IsNode {
expr: a.expr.clone(),
target_type: a.target_type.clone(),
range: a.range(),
});
_ = build_cond(
&mut transformed_is,
ctx,
builder,
cond_range,
then_block,
else_block,
);
ctx.position_at_end(then_block, builder);
let transformed_as = NodeEnum::AsNode(AsNode {
expr: a.expr.clone(),
target_type: a.target_type.clone(),
range: a.range(),
tail: Some((TokenType::NOT, Default::default())),
});
let mut def = def.clone();
def.value_expression = Some(Box::new(transformed_as));
gen_def = Some(def);
} else if let NodeEnum::ImplCastNode(a) = &**e {
if let Some((_, r)) = &a.tail {
// tail not allowed in `if let .. impl ..`
ctx.add_diag(
r.new_err(ErrorCode::IF_LET_DOES_NOT_EXPECT_TAIL)
.add_help("remove the tailling symbol")
.clone(),
);
}
let mut transformed_is = NodeEnum::ImplCastNode(ImplCastNode {
expr: a.expr.clone(),
target_type: a.target_type.clone(),
range: a.range(),
tail: Some((TokenType::QUESTION, Default::default())),
});
let re = build_cond(
&mut transformed_is,
ctx,
builder,
cond_range,
then_block,
else_block,
);
if let Ok(NodeOutput {
compile_time_result: CompileTimeResult::ConstBool(false),
..
}) = re
{
skip_body = true;
}
ctx.position_at_end(then_block, builder);
let transformed_as = NodeEnum::ImplCastNode(ImplCastNode {
expr: a.expr.clone(),
target_type: a.target_type.clone(),
range: a.range(),
tail: Some((TokenType::NOT, Default::default())),
});
let mut def = def.clone();
def.value_expression = Some(Box::new(transformed_as));
gen_def = Some(def);
}
}
if gen_def.is_none() {
def.range()
.new_err(ErrorCode::EXPECT_IF_LET_AS)
.add_help("adding as expression might be a solution")
.add_to_ctx(ctx);
}
} else {
let cond = &mut *self.cond;
_ = build_cond(cond, ctx, builder, cond_range, then_block, else_block);
}
// emit the else logic into the then block
ctx.position_at_end(then_block, builder);
let mut then_terminator = TerminatorEnum::None;
// emit the code inside a child context because it belongs to a sub-block
let mut child = ctx.new_child(self.then.range().start, builder);
if !skip_body {
if let Some(mut def) = gen_def {
def.emit(&mut child, builder)?;
}
then_terminator = self.then.emit(&mut child, builder)?.get_term();
}
if then_terminator.is_none() {
// there is no terminator(like return, yield and so forth) in the statement
// create an unconditional branch to merge block to finish off the "then" block
builder.build_unconditional_branch(merge_block);
}
// emit the else logic into the else block
ctx.position_at_end(else_block, builder);
let terminator = if let Some(el) = &mut self.els {
let mut child = ctx.new_child(el.range().start, builder);
let else_terminator = el.emit(&mut child, builder)?.get_term();
if else_terminator.is_none() {
// create an unconditional branch only if no terminator is detected
// otherwise, the code to be executed might be the others instead of merge block
// for example, if there is a 'return' statement in the if-then-else clause,
// it won't execute the merge block as it returns directly
builder.build_unconditional_branch(merge_block);
}
if then_terminator.is_return() && else_terminator.is_return() {
TerminatorEnum::Return
} else {
TerminatorEnum::None
}
} else {
builder.build_unconditional_branch(merge_block);
TerminatorEnum::None
};
ctx.position_at_end(merge_block, builder);
if terminator.is_return() {
builder.build_unconditional_branch(merge_block);
}
ctx.emit_comment_highlight(&self.comments[0]);
NodeOutput::default().with_term(terminator).to_result()
}
emit函数的第一个参数是节点自身,第二个参数是编译上下文。编译上下文中会包含一些需要透传的信息,比如符号表,lsp参数等,
第三个参数是builder
,用于生成中间代码。目前builder
只有llvm的实现。
emit函数的返回值比较复杂,它是一个Result
枚举类型,它的Ok
类型中包含一个Option
的PLValue
--这是代表该节点的运算结果,
一般只有表达式节点有这个值,statement节点这里会返回None
,除此之外还包含一个Option
的PLType
,这是代表该节点返回值的类型,
最后一个是一个TerminatorEnum
,用于分析某个路径是否有终结语句(break,continue,return,panic等)。返回值的Err
类型是一个PLDiag
,这个类型是用于报告错误的,包含了所有的错误信息。一般来说,在返回之前错误就会被加到编译上下文中,所以调用者不需要对其进行
任何处理。
打印AST结构
plc命令行工具有打印ast的功能,你可以使用plc xxx.pi --printast
命令来打印ast结构。
下方是一个ast打印结果的样例:
...
file: /Users/bobli/src/pivot-lang/test/sub/mod.pi
ProgramNode
└─ FuncDefNode
├─ id: name
├─ TypeNameNode
│ └─ ExternIdNode
│ └─ VarNode: void
└─ StatementsNode
└─ RetNode
file: /Users/bobli/src/pivot-lang/test/mod2.pi
ProgramNode
├─ UseNode
│ ├─ VarNode: sub
│ └─ VarNode: mod
├─ FuncDefNode
│ ├─ id: test_mod
│ ├─ TypedIdentifierNode
│ │ ├─ id: args
│ │ └─ TypeNameNode
│ │ └─ ExternIdNode
│ │ └─ VarNode: i64
│ ├─ TypeNameNode
│ │ └─ ExternIdNode
│ │ └─ VarNode: void
│ └─ StatementsNode
│ └─ RetNode
└─ StructDefNode
├─ id: Mod2
└─ TypedIdentifierNode
├─ id: y
└─ TypeNameNode
└─ ExternIdNode
└─ VarNode: bool
...