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

@@ -0,0 +1,110 @@
use crate::object::{QuoteTable, Value};
use crate::syn::{ast::*, words::*};
use crate::vm::inst::*;
pub struct Compile<'s> {
scope_stack: &'s mut ScopeStack,
quote_table: &'s mut QuoteTable,
}
impl<'s> Compile<'s> {
pub fn new(scope_stack: &'s mut ScopeStack, quote_table: &'s mut QuoteTable) -> Self {
Compile {
scope_stack,
quote_table,
}
}
pub fn compile(&mut self, expr_list: &Vec<SpExpr>) -> Vec<Inst> {
// Scoping is done here.
// Local scopes are implicit. If a variable is assigned to at
// all in the current scope, it's considered to be a local.
let mut code = Vec::new();
for expr in expr_list {
self.discover_locals(expr);
let thunk = self.compile_expr(expr);
code.extend(thunk.flatten());
}
code
}
fn compile_expr(&mut self, expr: &SpExpr) -> Thunk {
match expr.inner() {
Expr::Atom(atom) => self.compile_atom(atom),
// this gets compiled whenever it gets evaluated
Expr::Quote(exprs) => {
self.scope_stack.push_scope();
let compiled = self.compile(exprs);
let locals = self.scope_stack.pop_scope().unwrap();
let quote = self
.quote_table
.insert(expr.span(), locals, exprs.clone(), compiled);
Inst::PushValue(Value::Quote(quote)).into()
}
}
}
fn compile_atom(&mut self, atom: &SpAtom) -> Thunk {
match atom.inner() {
Atom::Float(f) => Inst::PushValue(Value::Float(*f)).into(),
Atom::Int(i) => Inst::PushValue(Value::Int(*i)).into(),
Atom::Str(s) => Inst::PushValue(Value::Str(s.clone())).into(),
Atom::Assign(text) => {
let word = self.scope_stack.insert_local(text);
Inst::Store(word).into()
}
Atom::Word(text) => {
// XXX : probably something better than this
// Check builtins
if text == "." {
return Inst::Print.into();
}
// Look for locally defined symbols first. One can't be found,
// then create a local variable. The local variable *should* be
// defined already, but sometimes things happen.
let word = if let Some(word) = self.scope_stack.lookup_scoped(text) {
word
} else {
self.scope_stack.insert_local(text)
};
Inst::Load(word).into()
}
Atom::Apply => Inst::Call.into(),
}
}
/// Discovers and inserts local variables into the scope.
///
/// If any assignment for a local variable appears in the scope, it will be
/// inserted.
fn discover_locals(&mut self, expr: &SpExpr) {
if let Expr::Atom(atom) = expr.inner() {
if let Atom::Assign(text) = atom.inner() {
self.scope_stack.insert_local(text);
}
}
}
}
#[derive(Debug, Clone)]
enum Thunk {
Block(Vec<Inst>),
List(Vec<Thunk>),
}
impl From<Inst> for Thunk {
fn from(inst: Inst) -> Thunk {
Thunk::Block(vec![inst])
}
}
impl Thunk {
fn flatten(self) -> Vec<Inst> {
use Thunk::*;
match self {
Block(block) => block,
List(list) => list.into_iter().flat_map(Thunk::flatten).collect(),
}
}
}

View File

@@ -7,7 +7,6 @@ use std::io::Read;
use std::path::PathBuf; use std::path::PathBuf;
use structopt::StructOpt; use structopt::StructOpt;
use syn::parser::Parser; use syn::parser::Parser;
use vm::eval::Eval;
use vm::machine::MachineBuilder; use vm::machine::MachineBuilder;
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
@@ -46,7 +45,7 @@ fn main() -> Result {
.max_arena_objects(opt.max_arena_objects) .max_arena_objects(opt.max_arena_objects)
.finish(); .finish();
let mut eval = Eval::new(&mut machine); machine.eval(&exprs)?;
eval.eval_expr_list(&exprs)?;
Ok(()) Ok(())
} }

View File

@@ -4,8 +4,13 @@
#![allow(dead_code)] #![allow(dead_code)]
use crate::syn::ast::SpExpr; use crate::syn::ast::SpExpr;
use crate::{
syn::{span::Span, words::Scope},
vm::inst::Inst,
};
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use std::fmt::{self, Display};
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
pub type Str = String; pub type Str = String;
@@ -13,22 +18,106 @@ pub type Int = i64;
pub type Float = f64; pub type Float = f64;
pub type VTable = HashMap<String, Value>; pub type VTable = HashMap<String, Value>;
// /////////////////////////////////////////////////////////////////////////////
// Quote
// /////////////////////////////////////////////////////////////////////////////
/// A handle to a quote pointing to an element in a `QuoteTable`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Quote(usize);
impl Quote {
pub fn index(&self) -> usize {
self.0
}
}
/// A table of compiled quotes, their expression trees, and their spans.
#[derive(Debug, Clone, Default)]
pub struct QuoteTable {
table: Vec<(Span, Scope, Vec<SpExpr>, Vec<Inst>)>,
}
impl QuoteTable {
pub fn new() -> Self {
Default::default()
}
pub fn insert(
&mut self,
span: Span,
scope: Scope,
quote: Vec<SpExpr>,
compiled: Vec<Inst>,
) -> Quote {
let next = Quote(self.table.len());
self.table.push((span, scope, quote, compiled));
next
}
pub fn get(&self, quote: Quote) -> &(Span, Scope, Vec<SpExpr>, Vec<Inst>) {
&self.table[quote.0]
}
}
// /////////////////////////////////////////////////////////////////////////////
// Value
// /////////////////////////////////////////////////////////////////////////////
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Value { pub enum Value {
Array(Vec<Value>), Array(Vec<Value>),
Float(Float), Float(Float),
Int(Int), Int(Int),
Str(Str), Str(Str),
Quote(Vec<SpExpr>), Quote(Quote),
ObjPtr(ObjPtr), ObjPtr(ObjPtr),
} }
impl Value {
pub fn name(&self) -> &str {
use Value::*;
match self {
Array(_) => "array",
Float(_) => "float",
Int(_) => "int",
Str(_) => "str",
Quote(_) => "quote",
ObjPtr(_) => "object",
}
}
}
impl Display for Value {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use Value::*;
match self {
Array(_) => write!(fmt, "[array]"),
Float(f) => write!(fmt, "{}", f),
Int(i) => write!(fmt, "{}", i),
Str(s) => write!(fmt, "{}", s),
Quote(q) => write!(fmt, "[quoted value #{}]", q.index()),
ObjPtr(o) => write!(fmt, "[object #{}]", o.slot()),
}
}
}
// /////////////////////////////////////////////////////////////////////////////
// Obj
// /////////////////////////////////////////////////////////////////////////////
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjPtr { pub struct ObjPtr {
arena: Weak<RefCell<Arena>>, arena: Weak<RefCell<Arena>>,
slot: usize, slot: usize,
} }
impl ObjPtr {
pub fn slot(&self) -> usize {
self.slot
}
}
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Obj { pub struct Obj {
vtable: VTable, vtable: VTable,
@@ -48,6 +137,10 @@ impl Obj {
} }
} }
// /////////////////////////////////////////////////////////////////////////////
// Arena
// /////////////////////////////////////////////////////////////////////////////
#[derive(Debug)] #[derive(Debug)]
pub struct Arena { pub struct Arena {
slots: Vec<SlotRange>, slots: Vec<SlotRange>,
@@ -170,6 +263,10 @@ impl Arena {
} }
} }
// /////////////////////////////////////////////////////////////////////////////
// Slots
// /////////////////////////////////////////////////////////////////////////////
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SlotRange { pub enum SlotRange {
/// A list of slots that are in a range, exclusive. /// A list of slots that are in a range, exclusive.

View File

@@ -9,10 +9,12 @@ pub enum Expr {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Atom { pub enum Atom {
Assign(Str),
Word(Str), Word(Str),
Float(Float), Float(Float),
Int(Int), Int(Int),
Str(Str), Str(Str),
Apply,
} }
pub type SpAtom = Spanned<Atom>; pub type SpAtom = Spanned<Atom>;

View File

@@ -5,12 +5,14 @@ use regex::{Regex, RegexBuilder};
lazy_static! { lazy_static! {
static ref LEX_PAT: Regex = RegexBuilder::new( static ref LEX_PAT: Regex = RegexBuilder::new(
r#"^( r#"^(
(?P<word>[a-zA-Z_?\-*+/=.'@$%^&|][0-9a-zA-Z_?\-*+/=.'@$%^&|]*) (?P<assign>=[a-zA-Z_?\-*+/.'@$%^&|][0-9a-zA-Z_?\-*+/=.'@$%^&|]*)
| (?P<word>[a-zA-Z_?\-*+/=.'@$%^&|][0-9a-zA-Z_?\-*+/=.'@$%^&|]*)
| (?P<float>[0-9]+\.[0-9]+([eE][+\-][0-9]+)?) | (?P<float>[0-9]+\.[0-9]+([eE][+\-][0-9]+)?)
| (?P<int>[0-9]+) | (?P<int>[0-9]+)
| (?P<lquote>\[) | (?P<lquote>\[)
| (?P<rquote>\]) | (?P<rquote>\])
| (?P<colon>:) | (?P<colon>:)
| (?P<bang>!)
| (?P<str>"([^"\\]|\\["'\\ntrb])*") | (?P<str>"([^"\\]|\\["'\\ntrb])*")
)"# )"#
) )
@@ -85,7 +87,9 @@ impl<'t> Lexer<'t> {
if let Some(cap) = LEX_PAT.captures(&self.text[self.end.byte..]) { if let Some(cap) = LEX_PAT.captures(&self.text[self.end.byte..]) {
self.end = self.end.next_str(cap.get(0).unwrap().as_str()); self.end = self.end.next_str(cap.get(0).unwrap().as_str());
let sp_token = if let Some(_) = cap.name("word") { let sp_token = if let Some(_) = cap.name("assign") {
self.make_token(Token::Assign)
} else if let Some(_) = cap.name("word") {
self.make_token(Token::Word) self.make_token(Token::Word)
} else if let Some(_) = cap.name("float") { } else if let Some(_) = cap.name("float") {
self.make_token(Token::Float) self.make_token(Token::Float)
@@ -99,6 +103,8 @@ impl<'t> Lexer<'t> {
self.make_token(Token::RQuote) self.make_token(Token::RQuote)
} else if let Some(_) = cap.name("colon") { } else if let Some(_) = cap.name("colon") {
self.make_token(Token::Colon) self.make_token(Token::Colon)
} else if let Some(_) = cap.name("bang") {
self.make_token(Token::Bang)
} else { } else {
panic!( panic!(
"matched lex pattern, but did not catch this capture: {:?}", "matched lex pattern, but did not catch this capture: {:?}",
@@ -172,6 +178,21 @@ mod test {
assert!(lexer.is_eof()); assert!(lexer.is_eof());
} }
#[test]
fn test_assign() {
let mut lexer = Lexer::new(r"= == === =a ==a ===a = a");
assert_token!(lexer, Token::Word, "=");
assert_token!(lexer, Token::Word, "==");
assert_token!(lexer, Token::Word, "===");
assert_token!(lexer, Token::Assign, "=a");
assert_token!(lexer, Token::Word, "==a");
assert_token!(lexer, Token::Word, "===a");
assert_token!(lexer, Token::Word, "=");
assert_token!(lexer, Token::Word, "a");
assert!(lexer.is_eof());
}
#[test] #[test]
fn test_numbers() { fn test_numbers() {
let mut lexer = Lexer::new(r"1 12 123 9 98 987 987654321 1248 9764321 1.2 2.3"); let mut lexer = Lexer::new(r"1 12 123 9 98 987 987654321 1248 9764321 1.2 2.3");

View File

@@ -4,3 +4,4 @@ pub mod lexer;
pub mod parser; pub mod parser;
pub mod span; pub mod span;
pub mod token; pub mod token;
pub mod words;

View File

@@ -117,7 +117,7 @@ impl<'t> Parser<'t> {
pub fn next_atom(&mut self) -> Result<SpAtom> { pub fn next_atom(&mut self) -> Result<SpAtom> {
use Token::*; use Token::*;
let token = self.expect_any_token(&[Word, Float, Int, Str])?; let token = self.expect_any_token(&[Assign, Word, Float, Int, Str, Bang])?;
Ok(self.token_to_atom(token)) Ok(self.token_to_atom(token))
} }
@@ -126,10 +126,12 @@ impl<'t> Parser<'t> {
let (span, token) = token.into_split(); let (span, token) = token.into_split();
let text = span.text_at(self.lexer.text()); let text = span.text_at(self.lexer.text());
let atom = match token { let atom = match token {
Token::Assign => Atom::Assign(text[1..].to_string()),
Token::Word => Atom::Word(text.to_string()), Token::Word => Atom::Word(text.to_string()),
Token::Float => Atom::Float(text.parse().unwrap()), Token::Float => Atom::Float(text.parse().unwrap()),
Token::Int => Atom::Int(text.parse().unwrap()), Token::Int => Atom::Int(text.parse().unwrap()),
Token::Str => Atom::Str(unescape_string(text)), Token::Str => Atom::Str(unescape_string(text)),
Token::Bang => Atom::Apply,
_ => panic!("invalid token specified for token_to_atom, it should be an atom"), _ => panic!("invalid token specified for token_to_atom, it should be an atom"),
}; };
SpAtom::new(span, atom) SpAtom::new(span, atom)

View File

@@ -3,6 +3,9 @@ use crate::syn::span::Spanned;
/// Token types. /// Token types.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Token { pub enum Token {
/// Assignment.
Assign,
/// Word. /// Word.
Word, Word,
@@ -23,12 +26,16 @@ pub enum Token {
/// Colon. /// Colon.
Colon, Colon,
/// Bang (apply).
Bang,
} }
impl Token { impl Token {
pub fn name(&self) -> &'static str { pub fn name(&self) -> &'static str {
use Token::*; use Token::*;
match self { match self {
Assign => "assignment",
Word => "word", Word => "word",
Float => "float", Float => "float",
Int => "int", Int => "int",
@@ -36,6 +43,7 @@ impl Token {
LQuote => "quote begin", LQuote => "quote begin",
RQuote => "quote end", RQuote => "quote end",
Colon => "colon", Colon => "colon",
Bang => "bang",
} }
} }
} }

88
src/syn/words.rs Normal file
View File

@@ -0,0 +1,88 @@
use std::collections::BTreeMap;
/// A lexical Word (or identifier).
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Word(usize);
#[derive(Debug, Default)]
pub struct ScopeStack {
words: Vec<String>,
layers: Vec<Scope>,
}
impl ScopeStack {
pub fn get(&self, word: &Word) -> &String {
&self.words[word.0]
}
pub fn push_scope(&mut self) {
self.layers.push(Default::default());
}
pub fn pop_scope(&mut self) -> Option<Scope> {
self.layers.pop()
}
pub fn lookup_local(&self, word: impl AsRef<str>) -> Option<Word> {
self.layers.last().and_then(|words| {
words
.iter()
.find(|(_, s)| s.as_str() == word.as_ref())
.map(|(w, _)| *w)
})
}
pub fn lookup_scoped(&self, word: impl AsRef<str>) -> Option<Word> {
self.layers
.iter()
.filter_map(|words| {
words
.iter()
.find(|(_, s)| s.as_str() == word.as_ref())
.map(|(w, _)| *w)
})
.next()
}
pub fn insert_local(&mut self, word: impl AsRef<str>) -> Word {
let word = word.as_ref();
if let Some(local) = self.lookup_local(word) {
local
} else {
let word = word.to_string();
let next = Word(self.words.len());
let top = self.layers.last_mut().expect("no scope");
top.insert(next, word.clone());
self.words.push(word);
next
}
}
}
pub type Scope = BTreeMap<Word, String>;
#[test]
fn test_scope_stack() {
let mut scope = ScopeStack::default();
scope.push_scope();
assert_eq!(scope.insert_local("zero"), Word(0));
assert_eq!(scope.insert_local("zero"), Word(0));
assert_eq!(scope.insert_local("one"), Word(1));
assert_eq!(scope.insert_local("zero"), Word(0));
assert_eq!(scope.insert_local("one"), Word(1));
scope.push_scope();
assert_eq!(scope.insert_local("zero"), Word(2));
assert_eq!(scope.insert_local("one"), Word(3));
assert_eq!(scope.insert_local("zero"), Word(2));
assert_eq!(scope.insert_local("one"), Word(3));
scope.push_scope();
assert_eq!(scope.insert_local("zero"), Word(4));
assert_eq!(scope.insert_local("one"), Word(5));
scope.pop_scope();
assert_eq!(scope.insert_local("zero"), Word(2));
assert_eq!(scope.insert_local("one"), Word(3));
scope.pop_scope();
assert_eq!(scope.insert_local("zero"), Word(0));
assert_eq!(scope.insert_local("one"), Word(1));
}

View File

@@ -4,6 +4,15 @@ use thiserror::Error;
pub enum RuntimeError { pub enum RuntimeError {
#[error("stack overflow")] #[error("stack overflow")]
StackOverflow, StackOverflow,
#[error("stack underflow")]
StackUnderflow,
#[error("unset word '{0}'")]
UnsetWord(String),
#[error("cannot call non-quote value '{0}'")]
CannotCall(String),
//#[error("stack underflow")] //#[error("stack underflow")]
//StackUnderflow, //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::object::*;
use crate::vm::error::*; use crate::syn::{ast::SpExpr, words::*};
use crate::vm::{error::*, inst::*};
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc; 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 { pub struct MachineBuilder {
max_stack_size: Option<usize>, max_stack_size: Option<usize>,
max_arena_objects: 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)) 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 error;
pub mod eval; pub mod inst;
pub mod machine; pub mod machine;