Finish up branch implementation
* while and unconditional loops are now supported fully * break and continue keywords for loop control * List::thunkify() has been broken into its own structure so it can be broken out further as necessary Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
@@ -17,6 +17,10 @@ pub enum List {
|
||||
body: Box<List>,
|
||||
el: Box<List>,
|
||||
},
|
||||
Loop {
|
||||
cond: Option<Box<List>>,
|
||||
body: Box<List>,
|
||||
},
|
||||
Lambda {
|
||||
params: Vec<String>,
|
||||
expr: Box<List>,
|
||||
@@ -34,6 +38,8 @@ pub enum List {
|
||||
name: String,
|
||||
value: Box<List>,
|
||||
},
|
||||
Break,
|
||||
Continue,
|
||||
Return(Box<List>),
|
||||
Call(Box<List>, Vec<List>),
|
||||
Do(Vec<List>),
|
||||
@@ -41,164 +47,7 @@ pub enum List {
|
||||
|
||||
impl List {
|
||||
pub fn thunkify(self, compile: &mut Compile) -> Thunk {
|
||||
match self {
|
||||
List::Sym(sym) => {
|
||||
Inst::PushSym(global_sym(sym.clone())).into()
|
||||
}
|
||||
List::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)) = (
|
||||
compile.scope().layers_len() > 1,
|
||||
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 = compile.create_global(sym);
|
||||
Inst::LoadGlobal(global).into()
|
||||
}
|
||||
}
|
||||
List::Int(int) => {
|
||||
// push const
|
||||
let (hdl, _) = compile.const_int(int);
|
||||
Inst::PushConst(hdl).into()
|
||||
}
|
||||
List::String(s) => {
|
||||
// push const
|
||||
let (hdl, _) = compile.const_str(s);
|
||||
Inst::PushConst(hdl).into()
|
||||
}
|
||||
List::If { cond, body, el, } => {
|
||||
let mut preamble = cond.thunkify(compile);
|
||||
// push CheckTruth here since there's not much of a better place to do so
|
||||
preamble.push_thunk(vec![
|
||||
Inst::GetAttr(BOOL_MEMBER_NAME.sym),
|
||||
Inst::Call(0),
|
||||
Inst::CheckTruth,
|
||||
]);
|
||||
let thunk_true = body.thunkify(compile).into();
|
||||
let thunk_false = el.thunkify(compile).into();
|
||||
Thunk::Branch {
|
||||
preamble: preamble.into(),
|
||||
thunk_true,
|
||||
thunk_false,
|
||||
}
|
||||
}
|
||||
List::Lambda { params, expr } => {
|
||||
// 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
|
||||
compile.push_scope_layer();
|
||||
let params_len = params.len();
|
||||
for param in params.into_iter() {
|
||||
let sym = global_sym(param);
|
||||
compile.create_local(sym);
|
||||
}
|
||||
|
||||
// Compile function body
|
||||
let mut code = expr.thunkify(compile)
|
||||
.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<_, _> = 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) =
|
||||
compile.push_const(UserFun::new_obj(code, locals, 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
|
||||
Inst::PushConst(hdl).into()
|
||||
}
|
||||
List::Assign { name, rhs, } => {
|
||||
let mut thunk = rhs.thunkify(compile);
|
||||
let sym = global_sym(name.to_string());
|
||||
if let Some(local) = compile.lookup_local(sym) {
|
||||
thunk.push(Inst::PopLocal(Some(local)));
|
||||
} else {
|
||||
let global = compile.lookup_global(sym)
|
||||
.expect("name expected to exist someplace(?)");
|
||||
thunk.push(Inst::PopGlobal(Some(global)));
|
||||
}
|
||||
thunk
|
||||
}
|
||||
List::Access { expr, access, } => {
|
||||
let mut thunk = expr.thunkify(compile);
|
||||
thunk.push(Inst::GetAttr(global_sym(access.to_string())));
|
||||
thunk
|
||||
}
|
||||
List::Update { expr, name, value, } => {
|
||||
let mut thunk = expr.thunkify(compile);
|
||||
let (hdl, _) = compile.const_str(name);
|
||||
thunk.push(Inst::PushConst(hdl));
|
||||
thunk.push_thunk(value.thunkify(compile));
|
||||
thunk
|
||||
}
|
||||
List::Return(expr) => {
|
||||
let mut thunk = expr.thunkify(compile);
|
||||
thunk.push(Inst::Return);
|
||||
thunk
|
||||
}
|
||||
List::Call(fun, args) => {
|
||||
let argc = args.len();
|
||||
let mut thunk = fun.thunkify(compile);
|
||||
for arg in args {
|
||||
thunk.push_thunk(arg.thunkify(compile));
|
||||
}
|
||||
thunk.push(Inst::Call(argc));
|
||||
thunk
|
||||
}
|
||||
List::Do(stmts) => {
|
||||
Thunk::List(stmts.into_iter()
|
||||
.map(|stmt| stmt.thunkify(compile))
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
Thunkify { compile }.thunkify(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +89,15 @@ impl<'c> Visit for CompileList<'c> {
|
||||
}
|
||||
|
||||
fn visit_stmt(&mut self, stmt: &Stmt) -> Self::Out {
|
||||
DefaultAccept::default_accept(stmt, self)
|
||||
match stmt {
|
||||
Stmt::Expr(e) => self.visit_expr(e),
|
||||
Stmt::Assign(a) => self.visit_assign_stmt(a),
|
||||
Stmt::Return(r) => self.visit_return_stmt(r),
|
||||
Stmt::Loop(b) => self.visit_loop_stmt(b),
|
||||
Stmt::While(c) => self.visit_while_stmt(c),
|
||||
Stmt::Break => List::Break,
|
||||
Stmt::Continue => List::Continue,
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_assign_stmt(&mut self, assign: &AssignStmt) -> Self::Out {
|
||||
@@ -274,6 +131,20 @@ impl<'c> Visit for CompileList<'c> {
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_while_stmt(&mut self, cond_body: &CondBody) -> Self::Out {
|
||||
List::Loop {
|
||||
cond: Some(Box::new(self.visit_expr(&cond_body.cond))),
|
||||
body: Box::new(self.visit_body(&cond_body.body)),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_loop_stmt(&mut self, body: &Body) -> Self::Out {
|
||||
List::Loop {
|
||||
cond: None,
|
||||
body: Box::new(self.visit_body(body)),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &Expr) -> Self::Out {
|
||||
DefaultAccept::default_accept(expr, self)
|
||||
}
|
||||
@@ -378,3 +249,185 @@ impl<'c> Visit for CompileList<'c> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Thunkify<'c> {
|
||||
compile: &'c mut Compile,
|
||||
}
|
||||
|
||||
impl Thunkify<'_> {
|
||||
pub fn thunkify(&mut self, list: List) -> Thunk {
|
||||
match list {
|
||||
List::Sym(sym) => {
|
||||
Inst::PushSym(global_sym(sym.clone())).into()
|
||||
}
|
||||
List::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()
|
||||
}
|
||||
}
|
||||
List::Int(int) => {
|
||||
// push const
|
||||
let (hdl, _) = self.compile.const_int(int);
|
||||
Inst::PushConst(hdl).into()
|
||||
}
|
||||
List::String(s) => {
|
||||
// push const
|
||||
let (hdl, _) = self.compile.const_str(s);
|
||||
Inst::PushConst(hdl).into()
|
||||
}
|
||||
List::If { cond, body, el, } => {
|
||||
let mut preamble = self.thunkify(*cond);
|
||||
// push CheckTruth here since there's not much of a better place to do so
|
||||
preamble.push_thunk(vec![
|
||||
Inst::GetAttr(BOOL_MEMBER_NAME.sym),
|
||||
Inst::Call(0),
|
||||
Inst::CheckTruth,
|
||||
]);
|
||||
let thunk_true = self.thunkify(*body).into();
|
||||
let thunk_false = self.thunkify(*el).into();
|
||||
Thunk::Branch {
|
||||
preamble: preamble.into(),
|
||||
thunk_true,
|
||||
thunk_false,
|
||||
}
|
||||
}
|
||||
List::Loop { cond, body, } => {
|
||||
let preamble = cond.map(|list| {
|
||||
let mut preamble = self.thunkify(*list);
|
||||
preamble.push_thunk(vec![
|
||||
Inst::GetAttr(BOOL_MEMBER_NAME.sym),
|
||||
Inst::Call(0),
|
||||
Inst::CheckTruth,
|
||||
]);
|
||||
preamble.into()
|
||||
});
|
||||
let body = self.thunkify(*body).into();
|
||||
Thunk::Loop { preamble, body, }
|
||||
}
|
||||
List::Lambda { params, expr } => {
|
||||
// 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();
|
||||
let params_len = params.len();
|
||||
for param in params.into_iter() {
|
||||
let sym = global_sym(param);
|
||||
self.compile.create_local(sym);
|
||||
}
|
||||
|
||||
// Compile function body
|
||||
let mut code = self.thunkify(*expr)
|
||||
.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, 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
|
||||
Inst::PushConst(hdl).into()
|
||||
}
|
||||
List::Assign { name, rhs, } => {
|
||||
let mut thunk = self.thunkify(*rhs);
|
||||
let sym = global_sym(name.to_string());
|
||||
if let Some(local) = self.compile.lookup_local(sym) {
|
||||
thunk.push(Inst::PopLocal(Some(local)));
|
||||
} else {
|
||||
let global = self.compile.lookup_global(sym)
|
||||
.expect("name expected to exist someplace(?)");
|
||||
thunk.push(Inst::PopGlobal(Some(global)));
|
||||
}
|
||||
thunk
|
||||
}
|
||||
List::Access { expr, access, } => {
|
||||
let mut thunk = self.thunkify(*expr);
|
||||
thunk.push(Inst::GetAttr(global_sym(access.to_string())));
|
||||
thunk
|
||||
}
|
||||
List::Update { expr, name, value, } => {
|
||||
let mut thunk = self.thunkify(*expr);
|
||||
let (hdl, _) = self.compile.const_str(name);
|
||||
thunk.push(Inst::PushConst(hdl));
|
||||
thunk.push_thunk(self.thunkify(*value));
|
||||
thunk
|
||||
}
|
||||
List::Return(expr) => {
|
||||
let mut thunk = self.thunkify(*expr);
|
||||
thunk.push(Inst::Return);
|
||||
thunk
|
||||
}
|
||||
List::Call(fun, args) => {
|
||||
let argc = args.len();
|
||||
let mut thunk = self.thunkify(*fun);
|
||||
for arg in args {
|
||||
thunk.push_thunk(self.thunkify(arg));
|
||||
}
|
||||
thunk.push(Inst::Call(argc));
|
||||
thunk
|
||||
}
|
||||
List::Do(stmts) => {
|
||||
Thunk::List(stmts.into_iter()
|
||||
.map(|stmt| self.thunkify(stmt))
|
||||
.collect())
|
||||
}
|
||||
List::Break => Thunk::Break,
|
||||
List::Continue => Thunk::Continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user