Add branching and more coherent method of function calls

We're turing-complete, babey!

* Call stack for functions now differentiates between native and quote
  calls
* Better API for builtin functions allowing for more control
* Example showing off branches

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2022-01-12 21:49:41 -08:00
parent c31be8142c
commit 90f27a4108
5 changed files with 274 additions and 66 deletions

View File

@@ -7,14 +7,38 @@ use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;
#[derive(Debug)]
pub struct Frame {
#[derive(Debug, Clone)]
pub enum Frame {
Native(NativeFrame),
Quote(QuoteFrame),
}
impl From<NativeFrame> for Frame {
fn from(other: NativeFrame) -> Self {
Frame::Native(other)
}
}
impl From<QuoteFrame> for Frame {
fn from(other: QuoteFrame) -> Self {
Frame::Quote(other)
}
}
#[derive(Debug, Clone)]
pub struct NativeFrame {
fun: BuiltinFn,
reentry: usize,
}
#[derive(Debug, Clone)]
pub struct QuoteFrame {
locals: BTreeMap<Word, Option<Value>>,
code: Vec<Inst>, // TODO - deduplicate this with some kind of shared pointer
code: Rc<Vec<Inst>>, // TODO - deduplicate this with some kind of shared pointer
pc: usize,
}
impl Frame {
impl QuoteFrame {
pub fn inst(&self) -> Option<&Inst> {
self.code.get(self.pc)
}
@@ -23,6 +47,7 @@ impl Frame {
/// The current state of a VM.
#[derive(Debug)]
pub struct Machine {
globals: BTreeMap<Word, Value>,
stack: Vec<Value>,
max_stack_size: Option<usize>,
arena: Rc<RefCell<Arena>>,
@@ -32,13 +57,19 @@ pub struct Machine {
}
impl Machine {
pub fn new(max_stack_size: Option<usize>, arena: Arena) -> Self {
pub fn new(
globals: BTreeMap<Word, Value>,
max_stack_size: Option<usize>,
arena: Arena,
scope_stack: ScopeStack,
) -> Self {
Machine {
globals,
stack: Default::default(),
max_stack_size,
arena: Rc::new(RefCell::new(arena)),
quote_table: Default::default(),
scope_stack: Default::default(),
scope_stack,
call_stack: Default::default(),
}
}
@@ -79,13 +110,32 @@ impl Machine {
self.call_stack
.iter()
.rev()
.filter_map(|frame| frame.locals.get(word))
.next()?
.as_ref()
.filter_map(|frame| {
if let Frame::Quote(frame) = frame {
frame.locals.get(word)
} else {
None
}
})
.filter_map(|value| value.as_ref())
.next()
.or_else(|| self.globals.get(word))
}
pub fn store_local(&mut self, word: Word, value: Value) {
let last = self.call_stack.last_mut().expect("no call stack");
let last = self
.call_stack
.iter_mut()
.rev()
.filter_map(|frame| {
if let Frame::Quote(frame) = frame {
Some(frame)
} else {
None
}
})
.next()
.expect("no call stack");
last.locals.insert(word, Some(value));
}
@@ -93,12 +143,32 @@ impl Machine {
self.call_stack.last()
}
pub fn frame_mut(&mut self) -> Option<&mut Frame> {
self.call_stack.last_mut()
pub fn call_quote(&mut self, quote: Quote) {
let (_span, locals, _expr, code) = self.quote_table.get(quote);
// create a new stack frame
self.call_stack.push(
QuoteFrame {
locals: locals.keys().map(|w| (*w, None)).collect(),
code: code.clone(),
pc: 0,
}
.into(),
);
}
fn pc(&self) -> usize {
self.frame().unwrap().pc
pub fn call_native(&mut self, native: BuiltinFn) {
self.call_stack.push(Frame::Native(NativeFrame {
fun: native,
reentry: 0,
}));
}
fn pc(&self) -> Option<usize> {
match self.frame() {
Some(Frame::Quote(frame)) => Some(frame.pc),
_ => None,
}
}
// /////////////////////////////////////////////////////////////////////////
@@ -108,13 +178,16 @@ impl Machine {
pub fn eval(&mut self, exprs: &Vec<SpExpr>) -> Result<()> {
self.scope_stack.push_scope();
let mut compile = Compile::new(&mut self.scope_stack, &mut self.quote_table);
let code = compile.compile(exprs);
let code = Rc::new(compile.compile(exprs));
let locals = self.scope_stack.pop_scope().unwrap();
self.call_stack.push(Frame {
locals: locals.keys().map(|w| (*w, None)).collect(),
code,
pc: 0,
});
self.call_stack.push(
QuoteFrame {
locals: locals.keys().map(|w| (*w, None)).collect(),
code,
pc: 0,
}
.into(),
);
// Run instructions
loop {
@@ -122,22 +195,56 @@ impl Machine {
if self.call_stack.is_empty() {
break;
}
let frame = self.frame().unwrap();
let inst = frame.inst();
if let Some(inst) = inst {
let inst = inst.clone();
self.eval_inst(inst)?;
} else {
self.call_stack.pop();
match self.frame().unwrap() {
Frame::Quote(frame) => {
let inst = frame.inst();
if let Some(inst) = inst {
let inst = inst.clone();
self.eval_inst(inst)?;
} else {
self.call_stack.pop();
}
}
Frame::Native(frame) => {
// there's a more obvious way to do this, but sadly, it does
// not play nicely with the borrow checker. so we have to
// record values and index items so we don't borrow anything
// mutably, simultaneously.
let current_frame = self.call_stack.len() - 1;
let fun = frame.fun.clone();
let reentry = frame.reentry;
let exit = self.do_call_native(fun, reentry)?;
match exit {
BuiltinExit::Call(quote) => {
self.call_stack.pop();
self.call_quote(quote);
}
BuiltinExit::Resume(reentry) => {
if let Frame::Native(frame) = &mut self.call_stack[current_frame] {
frame.reentry = reentry;
} else {
panic!("expected native call frame but it was replaced before we could get to it");
}
}
BuiltinExit::Return => {
self.call_stack.pop();
}
}
}
}
}
Ok(())
}
fn do_call_native(&mut self, native: BuiltinFn, reentry: usize) -> Result<BuiltinExit> {
native.call(self, reentry)
}
fn eval_inst(&mut self, inst: Inst) -> Result<()> {
let mut new_frame = None;
let next_pc = self.pc() + 1;
let current_frame = self.call_stack.len() - 1;
let next_pc = self.pc().unwrap() + 1;
match inst {
Inst::PushValue(value) => self.stack_push(value)?,
Inst::Load(word) => {
@@ -153,33 +260,26 @@ impl Machine {
let value = self.stack_pop()?;
self.store_local(word, value);
}
Inst::Call => {
let value = self.stack_pop()?;
let quote = if let Value::Quote(quote) = value {
quote
} else {
return Err(RuntimeError::CannotCall(value.name().to_string()));
};
let (_span, locals, _expr, code) = self.quote_table.get(quote);
// create a new stack frame
new_frame = Some(Frame {
locals: locals.keys().map(|w| (*w, None)).collect(),
code: code.clone(),
pc: 0,
});
}
Inst::Call => match self.stack_pop()? {
Value::Quote(quote) => {
self.call_quote(quote);
}
Value::BuiltinFn(builtin) => {
self.call_native(builtin);
}
value => return Err(RuntimeError::CannotCall(value.name().to_string())),
},
Inst::Print => {
let value = self.stack_pop()?;
println!("{}", value);
}
};
self.frame_mut().unwrap().pc = next_pc;
// Push the new stack frame *after* updating the PC
if let Some(frame) = new_frame {
self.call_stack.push(frame);
// Update this frame's PC
if let Frame::Quote(frame) = &mut self.call_stack[current_frame] {
frame.pc = next_pc;
} else {
panic!("attempted to update call stack PC, but current recorded frame was removed before we could get to it");
}
Ok(())
@@ -188,6 +288,8 @@ impl Machine {
#[derive(Debug, Default)]
pub struct MachineBuilder {
globals: BTreeMap<Word, Value>,
scope_stack: ScopeStack,
max_stack_size: Option<usize>,
max_arena_objects: Option<usize>,
}
@@ -203,7 +305,60 @@ impl MachineBuilder {
self
}
pub fn finish(self) -> Machine {
Machine::new(self.max_stack_size, Arena::new(self.max_arena_objects))
/// Registers all builtins for calling on the machine.
fn register_builtins(&mut self) {
self.scope_stack.push_scope();
self.register_builtin_fun("panic", |machine, _| {
println!("!!! panic");
println!("!!! top of stack");
for (i, value) in machine.stack().iter().enumerate().rev() {
println!("!!! {}. {:?}", i, value);
}
println!("!!! bottom of stack");
panic!();
});
self.register_builtin_fun("if", |machine, _| {
let if_false = machine.stack_pop()?;
let if_true = machine.stack_pop()?;
let condition = machine.stack_pop()?;
let value = if condition.is_truthy() {
if_true
} else {
if_false
};
if let Value::Quote(quote) = value {
Ok(BuiltinExit::Call(quote))
} else {
return Err(RuntimeError::CannotCall(
"if statement requires quote value".to_string(),
));
}
});
}
fn register_builtin_fun(
&mut self,
name: &str,
fun: fn(&mut Machine, usize) -> Result<BuiltinExit>,
) {
self.register_global(name, Value::BuiltinFn(BuiltinFn::new(Rc::new(fun))));
}
fn register_global(&mut self, name: &str, value: Value) {
let word = self.scope_stack.insert_local(name);
self.globals.insert(word, value);
}
pub fn finish(mut self) -> Machine {
self.register_builtins();
Machine::new(
self.globals,
self.max_stack_size,
Arena::new(self.max_arena_objects),
self.scope_stack,
)
}
}