Finally have things printing to the screen

Nothing fancy yet. This commit adds a bunch of stuff (oops):

* compiling code
* VM with instructions
* remove eval.rs and just eval in the Machine struct itself
* squash most warnings now that we're using stuff here

And probably more. But that's all for now folks

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2022-01-12 17:35:48 -08:00
parent a190157eeb
commit 1c669decc4
14 changed files with 561 additions and 93 deletions

View File

@@ -4,6 +4,15 @@ use thiserror::Error;
pub enum RuntimeError {
#[error("stack overflow")]
StackOverflow,
#[error("stack underflow")]
StackUnderflow,
#[error("unset word '{0}'")]
UnsetWord(String),
#[error("cannot call non-quote value '{0}'")]
CannotCall(String),
//#[error("stack underflow")]
//StackUnderflow,
}

View File

@@ -1,40 +0,0 @@
use crate::object::*;
use crate::syn::ast::*;
use crate::vm::{error::*, machine::Machine};
/// An evaluation context for the VM.
pub struct Eval<'m> {
machine: &'m mut Machine,
}
impl<'m> Eval<'m> {
pub fn new(machine: &'m mut Machine) -> Self {
Self { machine }
}
pub fn eval_expr_list(&mut self, exprs: &Vec<SpExpr>) -> Result<()> {
for expr in exprs {
self.eval_expr(expr)?;
}
Ok(())
}
pub fn eval_expr(&mut self, expr: &SpExpr) -> Result<()> {
match expr.inner() {
Expr::Atom(atom) => match atom.inner() {
Atom::Float(f) => self.machine.stack_push(Value::Float(*f))?,
Atom::Int(i) => self.machine.stack_push(Value::Int(*i))?,
Atom::Str(s) => self.machine.stack_push(Value::Str(s.clone()))?,
Atom::Word(_) => todo!("TODO - word lookup"),
},
Expr::Quote(quote) => {
self.machine.stack_push(Value::Quote(quote.clone()))?;
}
}
Ok(())
}
}
// IDEA: Eval chain
// chain of eval-able things, including macros. An eval can add something to the
// top of the eval chain and immediately jump to it, such as an include

32
src/vm/inst.rs Normal file
View File

@@ -0,0 +1,32 @@
use crate::object::Value;
use crate::syn::words::Word;
#[derive(Debug, Clone)]
pub enum Inst {
/// Push a constant value to the stack.
PushValue(Value),
/// Load a word's value onto the stack.
Load(Word),
/// Pop a value off the stack and store it into the word.
Store(Word),
// Duplicate the top stack value
//Dup,
/// Applies the top stack value, which should be a macro.
Call,
/// Pops and prints the top stack value.
Print,
}
// TODO - do we want separate function definition syntax? We would be able to
// know ahead of time which words are functions with this.
// : sq dup * ;
// vs.
// [ dup * ] =sq
// - do we want to have separate function call syntax?
// 5 sq call
// 5 sq !
// and then store macros in the function variables. the ! means "apply"

View File

@@ -1,9 +1,191 @@
use crate::compile::Compile;
use crate::object::*;
use crate::vm::error::*;
use crate::syn::{ast::SpExpr, words::*};
use crate::vm::{error::*, inst::*};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;
#[derive(Default)]
#[derive(Debug)]
pub struct Frame {
locals: BTreeMap<Word, Option<Value>>,
code: Vec<Inst>, // TODO - deduplicate this with some kind of shared pointer
pc: usize,
}
impl Frame {
pub fn inst(&self) -> Option<&Inst> {
self.code.get(self.pc)
}
}
/// The current state of a VM.
#[derive(Debug)]
pub struct Machine {
stack: Vec<Value>,
max_stack_size: Option<usize>,
arena: Rc<RefCell<Arena>>,
quote_table: QuoteTable,
scope_stack: ScopeStack,
call_stack: Vec<Frame>,
}
impl Machine {
pub fn new(max_stack_size: Option<usize>, arena: Arena) -> Self {
Machine {
stack: Default::default(),
max_stack_size,
arena: Rc::new(RefCell::new(arena)),
quote_table: Default::default(),
scope_stack: Default::default(),
call_stack: Default::default(),
}
}
// /////////////////////////////////////////////////////////////////////////
// Properties
// /////////////////////////////////////////////////////////////////////////
pub fn max_stack_size(&self) -> Option<usize> {
self.max_stack_size
}
pub fn stack(&self) -> &Vec<Value> {
&self.stack
}
pub fn stack_mut(&mut self) -> &mut Vec<Value> {
&mut self.stack
}
pub fn stack_push(&mut self, value: Value) -> Result<()> {
if let Some(max) = self.max_stack_size() {
if self.stack().len() >= max {
return Err(RuntimeError::StackOverflow);
}
}
self.stack_mut().push(value);
Ok(())
}
pub fn stack_pop(&mut self) -> Result<Value> {
self.stack_mut()
.pop()
.ok_or_else(|| RuntimeError::StackUnderflow)
}
pub fn lookup_word(&self, word: &Word) -> Option<&Value> {
self.call_stack
.iter()
.rev()
.filter_map(|frame| frame.locals.get(word))
.next()?
.as_ref()
}
pub fn store_local(&mut self, word: Word, value: Value) {
let last = self.call_stack.last_mut().expect("no call stack");
last.locals.insert(word, Some(value));
}
pub fn frame(&self) -> Option<&Frame> {
self.call_stack.last()
}
pub fn frame_mut(&mut self) -> Option<&mut Frame> {
self.call_stack.last_mut()
}
fn pc(&self) -> usize {
self.frame().unwrap().pc
}
// /////////////////////////////////////////////////////////////////////////
// Eval
// /////////////////////////////////////////////////////////////////////////
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 locals = self.scope_stack.pop_scope().unwrap();
self.call_stack.push(Frame {
locals: locals.keys().map(|w| (*w, None)).collect(),
code,
pc: 0,
});
// Run instructions
loop {
// nothing left to execute
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();
}
}
Ok(())
}
fn eval_inst(&mut self, inst: Inst) -> Result<()> {
let mut new_frame = None;
let next_pc = self.pc() + 1;
match inst {
Inst::PushValue(value) => self.stack_push(value)?,
Inst::Load(word) => {
let value = self
.lookup_word(&word)
.ok_or_else(|| {
RuntimeError::UnsetWord(self.scope_stack.get(&word).to_string())
})?
.clone();
self.stack_push(value)?;
}
Inst::Store(word) => {
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::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);
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct MachineBuilder {
max_stack_size: Option<usize>,
max_arena_objects: Option<usize>,
@@ -24,46 +206,3 @@ impl MachineBuilder {
Machine::new(self.max_stack_size, Arena::new(self.max_arena_objects))
}
}
/// The current state of a VM.
#[derive(Debug)]
pub struct Machine {
stack: Vec<Value>,
max_stack_size: Option<usize>,
arena: Rc<RefCell<Arena>>,
}
impl Machine {
pub fn new(max_stack_size: Option<usize>, arena: Arena) -> Self {
Machine {
stack: Default::default(),
max_stack_size,
arena: Rc::new(RefCell::new(arena)),
}
}
pub fn max_stack_size(&self) -> Option<usize> {
self.max_stack_size
}
pub fn stack(&self) -> &Vec<Value> {
&self.stack
}
pub fn stack_mut(&mut self) -> &mut Vec<Value> {
&mut self.stack
}
pub fn stack_push(&mut self, value: Value) -> Result<()> {
if let Some(max) = self.max_stack_size() {
if self.stack.len() >= max {
return Err(RuntimeError::StackOverflow);
}
}
self.stack_mut().push(value);
Ok(())
}
pub fn stack_pop(&mut self) -> Option<Value> {
self.stack_mut().pop()
}
}

View File

@@ -1,3 +1,3 @@
pub mod error;
pub mod eval;
pub mod inst;
pub mod machine;