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:
110
src/compile.rs
110
src/compile.rs
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
use syn::parser::Parser;
|
||||
use vm::eval::Eval;
|
||||
use vm::machine::MachineBuilder;
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
@@ -46,7 +45,7 @@ fn main() -> Result {
|
||||
.max_arena_objects(opt.max_arena_objects)
|
||||
.finish();
|
||||
|
||||
let mut eval = Eval::new(&mut machine);
|
||||
eval.eval_expr_list(&exprs)?;
|
||||
machine.eval(&exprs)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,8 +4,13 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::syn::ast::SpExpr;
|
||||
use crate::{
|
||||
syn::{span::Span, words::Scope},
|
||||
vm::inst::Inst,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::fmt::{self, Display};
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
pub type Str = String;
|
||||
@@ -13,22 +18,106 @@ pub type Int = i64;
|
||||
pub type Float = f64;
|
||||
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)]
|
||||
pub enum Value {
|
||||
Array(Vec<Value>),
|
||||
Float(Float),
|
||||
Int(Int),
|
||||
Str(Str),
|
||||
Quote(Vec<SpExpr>),
|
||||
Quote(Quote),
|
||||
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)]
|
||||
pub struct ObjPtr {
|
||||
arena: Weak<RefCell<Arena>>,
|
||||
slot: usize,
|
||||
}
|
||||
|
||||
impl ObjPtr {
|
||||
pub fn slot(&self) -> usize {
|
||||
self.slot
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Obj {
|
||||
vtable: VTable,
|
||||
@@ -48,6 +137,10 @@ impl Obj {
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Arena
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Arena {
|
||||
slots: Vec<SlotRange>,
|
||||
@@ -170,6 +263,10 @@ impl Arena {
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Slots
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SlotRange {
|
||||
/// A list of slots that are in a range, exclusive.
|
||||
|
||||
@@ -9,10 +9,12 @@ pub enum Expr {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Atom {
|
||||
Assign(Str),
|
||||
Word(Str),
|
||||
Float(Float),
|
||||
Int(Int),
|
||||
Str(Str),
|
||||
Apply,
|
||||
}
|
||||
|
||||
pub type SpAtom = Spanned<Atom>;
|
||||
|
||||
@@ -5,12 +5,14 @@ use regex::{Regex, RegexBuilder};
|
||||
lazy_static! {
|
||||
static ref LEX_PAT: Regex = RegexBuilder::new(
|
||||
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<int>[0-9]+)
|
||||
| (?P<lquote>\[)
|
||||
| (?P<rquote>\])
|
||||
| (?P<colon>:)
|
||||
| (?P<bang>!)
|
||||
| (?P<str>"([^"\\]|\\["'\\ntrb])*")
|
||||
)"#
|
||||
)
|
||||
@@ -85,7 +87,9 @@ impl<'t> Lexer<'t> {
|
||||
|
||||
if let Some(cap) = LEX_PAT.captures(&self.text[self.end.byte..]) {
|
||||
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)
|
||||
} else if let Some(_) = cap.name("float") {
|
||||
self.make_token(Token::Float)
|
||||
@@ -99,6 +103,8 @@ impl<'t> Lexer<'t> {
|
||||
self.make_token(Token::RQuote)
|
||||
} else if let Some(_) = cap.name("colon") {
|
||||
self.make_token(Token::Colon)
|
||||
} else if let Some(_) = cap.name("bang") {
|
||||
self.make_token(Token::Bang)
|
||||
} else {
|
||||
panic!(
|
||||
"matched lex pattern, but did not catch this capture: {:?}",
|
||||
@@ -172,6 +178,21 @@ mod test {
|
||||
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]
|
||||
fn test_numbers() {
|
||||
let mut lexer = Lexer::new(r"1 12 123 9 98 987 987654321 1248 9764321 1.2 2.3");
|
||||
|
||||
@@ -4,3 +4,4 @@ pub mod lexer;
|
||||
pub mod parser;
|
||||
pub mod span;
|
||||
pub mod token;
|
||||
pub mod words;
|
||||
|
||||
@@ -117,7 +117,7 @@ impl<'t> Parser<'t> {
|
||||
|
||||
pub fn next_atom(&mut self) -> Result<SpAtom> {
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -126,10 +126,12 @@ impl<'t> Parser<'t> {
|
||||
let (span, token) = token.into_split();
|
||||
let text = span.text_at(self.lexer.text());
|
||||
let atom = match token {
|
||||
Token::Assign => Atom::Assign(text[1..].to_string()),
|
||||
Token::Word => Atom::Word(text.to_string()),
|
||||
Token::Float => Atom::Float(text.parse().unwrap()),
|
||||
Token::Int => Atom::Int(text.parse().unwrap()),
|
||||
Token::Str => Atom::Str(unescape_string(text)),
|
||||
Token::Bang => Atom::Apply,
|
||||
_ => panic!("invalid token specified for token_to_atom, it should be an atom"),
|
||||
};
|
||||
SpAtom::new(span, atom)
|
||||
|
||||
@@ -3,6 +3,9 @@ use crate::syn::span::Spanned;
|
||||
/// Token types.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Token {
|
||||
/// Assignment.
|
||||
Assign,
|
||||
|
||||
/// Word.
|
||||
Word,
|
||||
|
||||
@@ -23,12 +26,16 @@ pub enum Token {
|
||||
|
||||
/// Colon.
|
||||
Colon,
|
||||
|
||||
/// Bang (apply).
|
||||
Bang,
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn name(&self) -> &'static str {
|
||||
use Token::*;
|
||||
match self {
|
||||
Assign => "assignment",
|
||||
Word => "word",
|
||||
Float => "float",
|
||||
Int => "int",
|
||||
@@ -36,6 +43,7 @@ impl Token {
|
||||
LQuote => "quote begin",
|
||||
RQuote => "quote end",
|
||||
Colon => "colon",
|
||||
Bang => "bang",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
88
src/syn/words.rs
Normal file
88
src/syn/words.rs
Normal 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));
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
32
src/vm/inst.rs
Normal 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"
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
pub mod error;
|
||||
pub mod eval;
|
||||
pub mod inst;
|
||||
pub mod machine;
|
||||
|
||||
Reference in New Issue
Block a user