Remove old Body compiler now that List compiler is in place
AST body is compiled first to a LISP-like IR via the ListCompiler, which in turn converts down to a thunk. The direct AST -> Thunk compiler is no longer needed. Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
@@ -1,10 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
compile::{basic_block::*, error::*, Compile},
|
compile::basic_block::*,
|
||||||
obj::{prelude::*, reserved::*},
|
|
||||||
syn::{ast::*, visit::*},
|
|
||||||
vm::inst::*,
|
vm::inst::*,
|
||||||
};
|
};
|
||||||
use std::{collections::BTreeMap, mem};
|
use std::mem;
|
||||||
|
|
||||||
/// A basic block of VM code.
|
/// A basic block of VM code.
|
||||||
///
|
///
|
||||||
@@ -223,318 +221,6 @@ impl Flatten {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// struct CompileBody
|
|
||||||
//
|
|
||||||
|
|
||||||
/// Compiles an AST body down to a `Thunk`.
|
|
||||||
///
|
|
||||||
/// Thunks are the basic building blocks of the IR. Thunks form a chain of decision paths that may
|
|
||||||
/// be taken, which allows an optimizer to remove dead code, detect endless loops, and so on. This
|
|
||||||
/// allows for shrinking blocks of code without having to recalculate jump addresses.
|
|
||||||
pub struct CompileBody<'c> {
|
|
||||||
compile: &'c mut Compile,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'c> CompileBody<'c> {
|
|
||||||
pub fn new(compile: &'c mut Compile) -> Self {
|
|
||||||
CompileBody { compile }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn compile(&mut self, body: &'c Body) -> Result<Thunk> {
|
|
||||||
let thunk = self.visit_body(body)?;
|
|
||||||
Ok(thunk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// impl Visit for CompileBody
|
|
||||||
//
|
|
||||||
|
|
||||||
impl Visit for CompileBody<'_> {
|
|
||||||
// XXX
|
|
||||||
// Trying to "future-proof" by using Result<_> in case there's some reason that an error
|
|
||||||
// may need to be thrown in the future so I don't have to wrap every return value in Ok(_)
|
|
||||||
type Out = Result<Thunk>;
|
|
||||||
|
|
||||||
fn visit_body(&mut self, body: &Body) -> Self::Out {
|
|
||||||
self.compile.collect_locals(body);
|
|
||||||
let mut thunk = Thunk::Nop;
|
|
||||||
|
|
||||||
for stmt in body.iter() {
|
|
||||||
thunk.push_thunk(stmt.accept(self)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(thunk)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_stmt(&mut self, stmt: &Stmt) -> Self::Out {
|
|
||||||
DefaultAccept::default_accept(stmt, self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_assign_stmt(&mut self, assign: &AssignStmt) -> Self::Out {
|
|
||||||
// - push rhs
|
|
||||||
// - push lhs (which handles the assignment)
|
|
||||||
let mut thunk = self.visit_expr(&assign.rhs)?;
|
|
||||||
thunk.push_thunk(self.visit_lhs_expr(&assign.lhs)?);
|
|
||||||
Ok(thunk)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_return_stmt(&mut self, stmt: &ReturnStmt) -> Self::Out {
|
|
||||||
let mut thunk = if let Some(expr) = stmt.expr.as_ref() {
|
|
||||||
self.visit_expr(expr)?
|
|
||||||
} else {
|
|
||||||
Inst::PushSym(NIL_NAME.sym).into()
|
|
||||||
};
|
|
||||||
thunk.push(Inst::Return);
|
|
||||||
|
|
||||||
Ok(thunk)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_lhs_expr(&mut self, lhs_expr: &LhsExpr) -> Self::Out {
|
|
||||||
// Do different things depending on the LHS
|
|
||||||
let mut thunk;
|
|
||||||
match &lhs_expr {
|
|
||||||
LhsExpr::SetAttr(expr) => {
|
|
||||||
// - push lhs expression (without accessor)
|
|
||||||
// - setattr (access) NOTE : rhs should already be on stack
|
|
||||||
thunk = self.visit_expr(&expr.expr)?;
|
|
||||||
let attr = global_sym(expr.access.to_string());
|
|
||||||
thunk.push(Inst::SetAttr(attr));
|
|
||||||
}
|
|
||||||
LhsExpr::Name(local_name) => {
|
|
||||||
let sym = global_sym(local_name.to_string());
|
|
||||||
if let Some(local) = self.compile.lookup_local(sym) {
|
|
||||||
thunk = Inst::PopLocal(Some(local)).into();
|
|
||||||
} else {
|
|
||||||
let global = self
|
|
||||||
.compile
|
|
||||||
.lookup_global(sym)
|
|
||||||
.expect("name expected to exist someplace(?)");
|
|
||||||
thunk = Inst::PopGlobal(Some(global)).into();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(thunk)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_expr(&mut self, expr: &Expr) -> Self::Out {
|
|
||||||
DefaultAccept::default_accept(expr, self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_bin_expr(&mut self, expr: &BinExpr) -> Self::Out {
|
|
||||||
// - push lhs
|
|
||||||
// - push rhs
|
|
||||||
// - call operator's function
|
|
||||||
let mut thunk = self.visit_expr(&expr.lhs)?;
|
|
||||||
thunk.push_thunk(self.visit_expr(&expr.rhs)?);
|
|
||||||
let inst = match expr.op {
|
|
||||||
BinOp::Plus => Inst::BinPlus,
|
|
||||||
BinOp::Minus => Inst::BinMinus,
|
|
||||||
BinOp::Times => Inst::BinMul,
|
|
||||||
BinOp::Div => Inst::BinDiv,
|
|
||||||
BinOp::Eq => Inst::BinEq,
|
|
||||||
BinOp::Neq => Inst::BinNeq,
|
|
||||||
BinOp::Lt => Inst::BinLt,
|
|
||||||
BinOp::Le => Inst::BinLe,
|
|
||||||
BinOp::Gt => Inst::BinGt,
|
|
||||||
BinOp::Ge => Inst::BinGe,
|
|
||||||
BinOp::And => Inst::BinAnd,
|
|
||||||
BinOp::Or => Inst::BinOr,
|
|
||||||
};
|
|
||||||
|
|
||||||
thunk.push(inst);
|
|
||||||
Ok(thunk)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_un_expr(&mut self, expr: &UnExpr) -> Self::Out {
|
|
||||||
// - push expr
|
|
||||||
// - call operator's function
|
|
||||||
let mut thunk = self.visit_expr(&expr.expr)?;
|
|
||||||
match expr.op {
|
|
||||||
UnOp::Plus => thunk.push(Inst::UnPos),
|
|
||||||
UnOp::Minus => thunk.push(Inst::UnNeg),
|
|
||||||
}
|
|
||||||
Ok(thunk)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_call_expr(&mut self, expr: &CallExpr) -> Self::Out {
|
|
||||||
// - push expr
|
|
||||||
// - push args in order
|
|
||||||
// - call function
|
|
||||||
let mut thunk = self.visit_expr(&expr.expr)?;
|
|
||||||
for arg in expr.args.iter() {
|
|
||||||
thunk.push_thunk(self.visit_expr(&arg)?);
|
|
||||||
}
|
|
||||||
thunk.push(Inst::Call(expr.args.len()));
|
|
||||||
Ok(thunk)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_index_expr(&mut self, expr: &IndexExpr) -> Self::Out {
|
|
||||||
// - eval expr
|
|
||||||
// - eval index
|
|
||||||
// - index
|
|
||||||
let mut thunk = self.visit_expr(&expr.expr)?;
|
|
||||||
thunk.push_thunk(self.visit_expr(&expr.index)?);
|
|
||||||
thunk.push(Inst::Index);
|
|
||||||
Ok(thunk)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_access_expr(&mut self, expr: &AccessExpr) -> Self::Out {
|
|
||||||
// - eval expr
|
|
||||||
// - getattr (expr.access)
|
|
||||||
let mut thunk = self.visit_expr(&expr.expr)?;
|
|
||||||
thunk.push_thunk(Thunk::Body(vec![Inst::GetAttr(global_sym(
|
|
||||||
expr.access.to_string(),
|
|
||||||
))]));
|
|
||||||
Ok(thunk)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_fun_expr(&mut self, expr: &FunExpr) -> Self::Out {
|
|
||||||
// TODO(fun) : need captures for functions, built dynamically (or statically?)
|
|
||||||
// - static is not possible, since captures are *created* at runtime, and there's no
|
|
||||||
// instruction that will look up just one scope level - it's either locals or globals.
|
|
||||||
// - an entire "create function" instruction is probably the best way to solve it, don't
|
|
||||||
// try to be clever, just implement it like that (since I mean, python does too...)
|
|
||||||
|
|
||||||
// - push const
|
|
||||||
// (functions are unique const values so a new function will be created for every literal
|
|
||||||
// function defined in code)
|
|
||||||
|
|
||||||
// This is pretty much the only place where a new scope layer gets pushed beyond the start
|
|
||||||
// of the program
|
|
||||||
self.compile.push_scope_layer();
|
|
||||||
for param in expr.params.iter() {
|
|
||||||
let sym = global_sym(param.to_string());
|
|
||||||
self.compile.create_local(sym);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile function body
|
|
||||||
let mut code = self.visit_body(&expr.body)?.flatten().to_vec();
|
|
||||||
|
|
||||||
// If the last instruction is not a return, or if there are no instructions, then return
|
|
||||||
// :nil value.
|
|
||||||
if !matches!(code.last(), Some(Inst::Return)) {
|
|
||||||
code.push(Inst::PushSym(NIL_NAME.sym));
|
|
||||||
code.push(Inst::Return);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remap (Sym -> Name) to be (Name -> Sym) and make sure it's all in order.
|
|
||||||
let scope_locals: BTreeMap<_, _> = self
|
|
||||||
.compile
|
|
||||||
.pop_scope_layer()
|
|
||||||
.unwrap()
|
|
||||||
.into_iter()
|
|
||||||
.map(|(sym, name)| (name, sym))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// this should be in numeric order since:
|
|
||||||
// 1. locals are created exactly once or looked up
|
|
||||||
// 2. scope_locals is a btreemap, keyed by names, which are in order from 0..N
|
|
||||||
let locals: FunLocals = scope_locals
|
|
||||||
.into_iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(index, (name, sym))| {
|
|
||||||
assert_eq!(index, name.index());
|
|
||||||
sym
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let (hdl, _fun) =
|
|
||||||
self.compile
|
|
||||||
.push_const(UserFun::new_obj(code, locals, expr.params.len()));
|
|
||||||
|
|
||||||
// TODO(compile) : determine return value at the end of the body (preferably at parse-time)
|
|
||||||
|
|
||||||
// oh yeah, we were compiling a function body weren't we
|
|
||||||
Ok(Inst::PushConst(hdl).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_if_expr(&mut self, expr: &IfExpr) -> Self::Out {
|
|
||||||
// base if condition
|
|
||||||
let mut thunk = self.visit_cond_body(&expr.cond_body)?;
|
|
||||||
{
|
|
||||||
// elif branches
|
|
||||||
let mut prev_thunk: &mut Thunk = &mut thunk;
|
|
||||||
for elif_cond_body in expr.elif.iter() {
|
|
||||||
let elif_thunk = self.visit_cond_body(elif_cond_body)?;
|
|
||||||
if let Thunk::Branch(thunk_branch) = prev_thunk {
|
|
||||||
thunk_branch.thunk_false = Box::new(elif_thunk);
|
|
||||||
prev_thunk = &mut thunk_branch.thunk_false;
|
|
||||||
} else {
|
|
||||||
unreachable!("accidentally found a non-branch thunk in elif expression")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// el branch
|
|
||||||
if let (Some(el_body), Thunk::Branch(thunk_branch)) = (&expr.el, prev_thunk) {
|
|
||||||
thunk_branch.thunk_false = Box::new(self.visit_body(el_body)?);
|
|
||||||
//prev_thunk = &mut thunk_branch.thunk_false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(thunk)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_cond_body(&mut self, cond_body: &CondBody) -> Self::Out {
|
|
||||||
let mut preamble = self.visit_expr(&cond_body.cond)?;
|
|
||||||
// Attempt to call the __bool__ function on this object which leaves a value on the stack
|
|
||||||
preamble.push_thunk(vec![
|
|
||||||
Inst::GetAttr(BOOL_MEMBER_NAME.sym),
|
|
||||||
Inst::Call(0),
|
|
||||||
Inst::CheckTruth,
|
|
||||||
]);
|
|
||||||
Ok(Thunk::Branch(ThunkBranch {
|
|
||||||
preamble: preamble.into(),
|
|
||||||
thunk_true: self.visit_body(&cond_body.body)?.into(),
|
|
||||||
thunk_false: Box::new(Thunk::Nop),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_atom(&mut self, atom: &Atom) -> Self::Out {
|
|
||||||
let thunk = match atom {
|
|
||||||
Atom::Ident(ident) => {
|
|
||||||
// Small gotcha:
|
|
||||||
// Looking up a name will either result in a local or a global lookup. If it's
|
|
||||||
// a local variable first, then it's determined as a local and that's the end
|
|
||||||
// of the story... except when we're at the top scope level, we're both "local"
|
|
||||||
// *and* global.
|
|
||||||
//
|
|
||||||
// This checks to make sure that it's both a local variable and that there's more
|
|
||||||
// than one scope layer.
|
|
||||||
let sym = global_sym(ident.to_string());
|
|
||||||
if let (true, Some(local)) = (
|
|
||||||
self.compile.scope().layers_len() > 1,
|
|
||||||
self.compile.lookup_local(sym),
|
|
||||||
) {
|
|
||||||
// get local
|
|
||||||
Inst::LoadLocal(local).into()
|
|
||||||
} else {
|
|
||||||
// get or create global
|
|
||||||
// create_global only makes a new global with this symbol name if one has not
|
|
||||||
// been created yet
|
|
||||||
let global = self.compile.create_global(sym);
|
|
||||||
Inst::LoadGlobal(global).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Atom::Sym(sym) => {
|
|
||||||
// push symbol
|
|
||||||
Inst::PushSym(global_sym(sym.clone())).into()
|
|
||||||
}
|
|
||||||
Atom::Num(num) => {
|
|
||||||
// push const
|
|
||||||
let (hdl, _) = self.compile.const_int(*num);
|
|
||||||
Inst::PushConst(hdl).into()
|
|
||||||
}
|
|
||||||
Atom::String(s) => {
|
|
||||||
// push const
|
|
||||||
let (hdl, _) = self.compile.const_str(s);
|
|
||||||
Inst::PushConst(hdl).into()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(thunk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Tests
|
// Tests
|
||||||
//
|
//
|
||||||
|
|||||||
Reference in New Issue
Block a user