diff --git a/src/compile.rs b/src/compile.rs index e69de29..f6c3776 100644 --- a/src/compile.rs +++ b/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) -> Vec { + // 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), + List(Vec), +} + +impl From for Thunk { + fn from(inst: Inst) -> Thunk { + Thunk::Block(vec![inst]) + } +} + +impl Thunk { + fn flatten(self) -> Vec { + use Thunk::*; + match self { + Block(block) => block, + List(list) => list.into_iter().flat_map(Thunk::flatten).collect(), + } + } +} diff --git a/src/main.rs b/src/main.rs index 6d22818..9990b70 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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(()) } diff --git a/src/object.rs b/src/object.rs index a7258fb..cec18b3 100644 --- a/src/object.rs +++ b/src/object.rs @@ -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; +// ///////////////////////////////////////////////////////////////////////////// +// 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, Vec)>, +} + +impl QuoteTable { + pub fn new() -> Self { + Default::default() + } + + pub fn insert( + &mut self, + span: Span, + scope: Scope, + quote: Vec, + compiled: Vec, + ) -> Quote { + let next = Quote(self.table.len()); + self.table.push((span, scope, quote, compiled)); + next + } + + pub fn get(&self, quote: Quote) -> &(Span, Scope, Vec, Vec) { + &self.table[quote.0] + } +} + +// ///////////////////////////////////////////////////////////////////////////// +// Value +// ///////////////////////////////////////////////////////////////////////////// + #[derive(Debug, Clone)] pub enum Value { Array(Vec), Float(Float), Int(Int), Str(Str), - Quote(Vec), + 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>, 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, @@ -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. diff --git a/src/syn/ast.rs b/src/syn/ast.rs index 68f0ce0..6ebea01 100644 --- a/src/syn/ast.rs +++ b/src/syn/ast.rs @@ -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; diff --git a/src/syn/lexer.rs b/src/syn/lexer.rs index b103495..76a63b6 100644 --- a/src/syn/lexer.rs +++ b/src/syn/lexer.rs @@ -5,12 +5,14 @@ use regex::{Regex, RegexBuilder}; lazy_static! { static ref LEX_PAT: Regex = RegexBuilder::new( r#"^( - (?P[a-zA-Z_?\-*+/=.'@$%^&|][0-9a-zA-Z_?\-*+/=.'@$%^&|]*) + (?P=[a-zA-Z_?\-*+/.'@$%^&|][0-9a-zA-Z_?\-*+/=.'@$%^&|]*) + | (?P[a-zA-Z_?\-*+/=.'@$%^&|][0-9a-zA-Z_?\-*+/=.'@$%^&|]*) | (?P[0-9]+\.[0-9]+([eE][+\-][0-9]+)?) | (?P[0-9]+) | (?P\[) | (?P\]) | (?P:) + | (?P!) | (?P"([^"\\]|\\["'\\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"); diff --git a/src/syn/mod.rs b/src/syn/mod.rs index 63ccd89..a4a4984 100644 --- a/src/syn/mod.rs +++ b/src/syn/mod.rs @@ -4,3 +4,4 @@ pub mod lexer; pub mod parser; pub mod span; pub mod token; +pub mod words; diff --git a/src/syn/parser.rs b/src/syn/parser.rs index 787caf0..449ee5f 100644 --- a/src/syn/parser.rs +++ b/src/syn/parser.rs @@ -117,7 +117,7 @@ impl<'t> Parser<'t> { pub fn next_atom(&mut self) -> Result { 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) diff --git a/src/syn/token.rs b/src/syn/token.rs index cbf7e82..086ccb3 100644 --- a/src/syn/token.rs +++ b/src/syn/token.rs @@ -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", } } } diff --git a/src/syn/words.rs b/src/syn/words.rs new file mode 100644 index 0000000..45cfd36 --- /dev/null +++ b/src/syn/words.rs @@ -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, + layers: Vec, +} + +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 { + self.layers.pop() + } + + pub fn lookup_local(&self, word: impl AsRef) -> Option { + 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) -> Option { + 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) -> 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; + +#[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)); +} diff --git a/src/vm/error.rs b/src/vm/error.rs index ced3e8a..226c9e7 100644 --- a/src/vm/error.rs +++ b/src/vm/error.rs @@ -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, } diff --git a/src/vm/eval.rs b/src/vm/eval.rs deleted file mode 100644 index 8ddef14..0000000 --- a/src/vm/eval.rs +++ /dev/null @@ -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) -> 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 diff --git a/src/vm/inst.rs b/src/vm/inst.rs new file mode 100644 index 0000000..1283bba --- /dev/null +++ b/src/vm/inst.rs @@ -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" diff --git a/src/vm/machine.rs b/src/vm/machine.rs index 44e7846..8006442 100644 --- a/src/vm/machine.rs +++ b/src/vm/machine.rs @@ -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>, + code: Vec, // 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, + max_stack_size: Option, + arena: Rc>, + quote_table: QuoteTable, + scope_stack: ScopeStack, + call_stack: Vec, +} + +impl Machine { + pub fn new(max_stack_size: Option, 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 { + self.max_stack_size + } + + pub fn stack(&self) -> &Vec { + &self.stack + } + + pub fn stack_mut(&mut self) -> &mut Vec { + &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 { + 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) -> 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, max_arena_objects: Option, @@ -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, - max_stack_size: Option, - arena: Rc>, -} - -impl Machine { - pub fn new(max_stack_size: Option, arena: Arena) -> Self { - Machine { - stack: Default::default(), - max_stack_size, - arena: Rc::new(RefCell::new(arena)), - } - } - - pub fn max_stack_size(&self) -> Option { - self.max_stack_size - } - - pub fn stack(&self) -> &Vec { - &self.stack - } - pub fn stack_mut(&mut self) -> &mut Vec { - &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 { - self.stack_mut().pop() - } -} diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 32e1719..0dff4e1 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -1,3 +1,3 @@ pub mod error; -pub mod eval; +pub mod inst; pub mod machine;