Add Bool builtin type, branches, and some more stuff

* obj::Bool builtin type is used for truthiness and decision-making
* Branches are compiled and seem to be working for basic integer
  comparison
* Updated version of Shredder to what is current as of writing
* CheckTruth VM instruction that will explicitly set the condition flag
* Probably some other stuff

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2020-11-06 16:57:25 -08:00
parent 692bb521ec
commit be9a659159
17 changed files with 435 additions and 78 deletions

View File

@@ -76,6 +76,12 @@ impl Visit for CollectLocals<'_> {
fn visit_fun_expr(&mut self, _expr: &FunExpr) -> Self::Out {
// Do not collect names for function expressions, since they have their own scope.
}
fn visit_if_expr(&mut self, expr: &IfExpr) -> Self::Out {
DefaultAccept::default_accept(expr, self);
}
fn visit_cond_body(&mut self, cond_body: &CondBody) -> Self::Out {
DefaultAccept::default_accept(cond_body, self);
}
fn visit_atom(&mut self, atom: &Atom) -> Self::Out {
DefaultAccept::default_accept(atom, self);
}

View File

@@ -1,6 +1,6 @@
use crate::{
compile::{basic_block::*, error::*, Compile},
obj::prelude::*,
obj::{prelude::*, reserved::*},
syn::{ast::*, visit::*},
vm::inst::*,
};
@@ -25,10 +25,7 @@ pub enum Thunk {
///
/// Only one of these thunks will be executed. At the end of either thunk, the program will
/// continue at the address following this branch.
Branch {
thunk_true: Box<Thunk>,
thunk_false: Box<Thunk>,
},
Branch(ThunkBranch),
/// Based on the conditional flag in the VM, code for this loop will continue to execute.
///
@@ -94,11 +91,11 @@ impl Thunk {
Thunk::List(thunks) => thunks
.iter()
.fold(0, |n, thunk| n + thunk.basic_block_count()),
Thunk::Branch {
Thunk::Branch(ThunkBranch {
preamble,
thunk_true,
thunk_false,
// length is true + false block count, + 1 for the branch basic block at the start
} => thunk_true.basic_block_count() + thunk_false.basic_block_count() + 1,
}) => preamble.basic_block_count() + thunk_true.basic_block_count() + thunk_false.basic_block_count() + 1,
// length is thunk, + 1 for branch at the start of the loop
Thunk::Loop(thunk) => thunk.basic_block_count() + 1,
Thunk::Nop => 0,
@@ -128,6 +125,16 @@ impl From<Vec<Thunk>> for Thunk {
}
}
//
// struct ThunkBranch
//
#[derive(Debug, Clone, PartialEq)]
pub struct ThunkBranch {
preamble: Box<Thunk>,
thunk_true: Box<Thunk>,
thunk_false: Box<Thunk>,
}
//
// struct Flatten
//
@@ -150,6 +157,9 @@ impl Flatten {
let last_block = thunk.basic_block_count();
self.flatten_next(last_block, thunk);
assert_eq!(self.blocks.len(), last_block);
// push an extra null block at the very end so that anything pointing to `last_block` will
// have a valid block index
self.blocks.insert(last_block, BasicBlock::Block { exit: last_block + 1, block: Default::default() });
self.blocks
}
@@ -174,13 +184,17 @@ impl Flatten {
}
assert_eq!(next_block, self.this_block());
}
Thunk::Branch {
Thunk::Branch(ThunkBranch {
preamble,
thunk_true,
thunk_false,
} => {
let branch_block = self.this_block();
let block_true = self.this_block() + 1;
}) => {
let preamble_block = self.this_block();
let branch_block = preamble_block + preamble.basic_block_count();
let block_true = branch_block + 1;
let block_false = block_true + thunk_true.basic_block_count();
self.flatten_next(branch_block, *preamble);
assert_eq!(self.this_block(), branch_block);
self.blocks.insert(
branch_block,
BasicBlock::Branch {
@@ -188,7 +202,9 @@ impl Flatten {
block_false,
},
);
assert_eq!(self.this_block(), block_true);
self.flatten_next(next_block, *thunk_true);
assert_eq!(self.this_block(), block_false);
self.flatten_next(next_block, *thunk_false);
assert_eq!(self.this_block(), next_block);
}
@@ -263,7 +279,7 @@ impl Visit for CompileBody<'_> {
let mut thunk = if let Some(expr) = stmt.expr.as_ref() {
self.visit_expr(expr)?
} else {
Inst::PushSym(crate::obj::reserved::NIL_NAME.sym).into()
Inst::PushSym(NIL_NAME.sym).into()
};
thunk.push(Inst::Return);
@@ -394,7 +410,7 @@ impl Visit for CompileBody<'_> {
// 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(crate::obj::reserved::NIL_NAME.sym));
code.push(Inst::PushSym(NIL_NAME.sym));
code.push(Inst::Return);
}
@@ -429,6 +445,46 @@ impl Visit for CompileBody<'_> {
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) => {
@@ -490,13 +546,12 @@ fn test_flatten_thunk() {
let end_body = vec![Inst::PushSym(Sym::new(1)), Inst::Call(1)];
let thunk = Thunk::List(vec![
// do something before
Thunk::Body(init_body.clone()),
// branch
Thunk::Branch {
Thunk::Branch(ThunkBranch {
preamble: Thunk::Body(init_body.clone()).into(),
thunk_true: Thunk::Body(true_body.clone()).into(),
thunk_false: Thunk::Body(false_body.clone()).into(),
},
}),
// do something after
Thunk::Body(end_body.clone()),
]);
@@ -504,7 +559,7 @@ fn test_flatten_thunk() {
let block_count = thunk.basic_block_count();
let blocks = thunk.flatten();
assert_eq!(blocks.len(), block_count);
assert_eq!(blocks.len(), block_count + 1);
let mut iter = blocks.into_iter();
assert_eq!(
@@ -557,5 +612,16 @@ fn test_flatten_thunk() {
}
)
);
// empty block
assert_eq!(
iter.next().unwrap(),
(
5,
BasicBlock::Block {
exit: 6,
block: vec![],
}
)
);
assert!(iter.next().is_none());
}