Objs replace Values

* Obj is now a trait instead of a struct
* Value enum is gone, replaced with individual structs that implement
  Obj
* All instances where a Value was used, a Gc<(dyn Obj + 'static)> takes
  its place. ObjPtr is shorthand for this.
* A __str__ method for objects is implemented for a couple builtin types
  as a proof-of-concept
* println and print builtin functions are implemented using this
  interface

There's still a lot to do, mostly with interned values. For example,
whenever a new string object is created, a new __str__ function object
is created each time, rather than reusing the first one that was
created. Stuff like that.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2022-01-18 14:39:06 -08:00
parent 248dfd3630
commit 070e497e1f
6 changed files with 251 additions and 148 deletions

View File

@@ -1,4 +1,4 @@
use crate::object::{BuiltinExit, Int, Value};
use crate::object::{BuiltinExit, Obj, StrObj};
use crate::vm::{error::RuntimeError, machine::MachineBuilder};
impl MachineBuilder {
@@ -19,6 +19,7 @@ impl MachineBuilder {
panic!();
});
/*
//
// if
//
@@ -26,6 +27,7 @@ impl MachineBuilder {
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 {
@@ -41,25 +43,75 @@ impl MachineBuilder {
.into());
}
});
*/
//
// print
//
self.register_builtin_fun("print", |machine, _| {
let value = machine.stack_pop()?;
print!("{}", value);
Ok(BuiltinExit::Return)
self.register_builtin_fun("print", |machine, reentry| {
const CALL_STR: usize = 1;
if reentry == 0 {
let obj = machine
.stack_peek()
.ok_or_else(|| RuntimeError::StackUnderflow)?;
// Push the value's __str__ value, call it, and then print it.
let str_fun = obj
.get("__str__")
.ok_or_else(|| RuntimeError::UnsetWord("__str__".to_string()))?;
let call_site = machine.call_stack().last().unwrap().call_site().cloned();
str_fun.call(call_site, machine)?;
Ok(BuiltinExit::Resume(CALL_STR))
} else if reentry == CALL_STR {
let obj_ptr = machine.stack_pop()?;
let str_obj: &(dyn Obj + 'static) = &*obj_ptr;
if let Some(string) = str_obj.as_any().downcast_ref::<StrObj>() {
print!("{}", string.value());
Ok(BuiltinExit::Return)
} else {
panic!(
"expected StrObj from __str__ function but got {:?} instead",
str_obj
);
}
} else {
unreachable!()
}
});
//
// println
//
self.register_builtin_fun("println", |machine, _| {
let value = machine.stack_pop()?;
println!("{}", value);
Ok(BuiltinExit::Return)
self.register_builtin_fun("println", |machine, reentry| {
const CALL_STR: usize = 1;
if reentry == 0 {
let obj = machine
.stack_peek()
.ok_or_else(|| RuntimeError::StackUnderflow)?;
// Push the value's __str__ value, call it, and then print it.
let str_fun = obj
.get("__str__")
.ok_or_else(|| RuntimeError::UnsetWord("__str__".to_string()))?;
let call_site = machine.call_stack().last().unwrap().call_site().cloned();
str_fun.call(call_site, machine)?;
Ok(BuiltinExit::Resume(CALL_STR))
} else if reentry == CALL_STR {
let obj_ptr = machine.stack_pop()?;
let str_obj: &(dyn Obj + 'static) = &*obj_ptr;
if let Some(string) = str_obj.as_any().downcast_ref::<StrObj>() {
println!("{}", string.value());
Ok(BuiltinExit::Return)
} else {
panic!(
"expected StrObj from __str__ function but got {:?} instead",
str_obj
);
}
} else {
unreachable!()
}
});
/*
//
// ==
//
@@ -79,5 +131,6 @@ impl MachineBuilder {
machine.stack_push(Value::Int((lhs != rhs) as Int))?;
Ok(BuiltinExit::Return)
});
*/
}
}

View File

@@ -12,9 +12,12 @@ pub enum RuntimeError {
#[error("unset word '{0}'")]
UnsetWord(String),
#[error("cannot call non-quote value '{0}'")]
#[error("cannot call value '{0}'")]
CannotCall(String),
#[error("expected {0} value")]
WrongValue(String),
#[error("at {0}")]
Span(Span, Box<RuntimeError>),
}
@@ -24,13 +27,13 @@ pub type Result<T, E = RuntimeError> = std::result::Result<T, E>;
/// Add a location to the implemented value.
pub trait WithLocation {
type Out;
fn with_location(self, span: Span) -> Self::Out;
fn with_location(self, span: Option<Span>) -> Self::Out;
}
impl<T> WithLocation for Result<T, RuntimeError> {
type Out = Result<T, RuntimeError>;
fn with_location(self, span: Span) -> Self::Out {
fn with_location(self, span: Option<Span>) -> Self::Out {
if let Err(e) = self {
Err(e.with_location(span))
} else {
@@ -42,7 +45,11 @@ impl<T> WithLocation for Result<T, RuntimeError> {
impl WithLocation for RuntimeError {
type Out = RuntimeError;
fn with_location(self, span: Span) -> Self::Out {
RuntimeError::Span(span, Box::new(self))
fn with_location(self, span: Option<Span>) -> Self::Out {
if let Some(span) = span {
RuntimeError::Span(span, Box::new(self))
} else {
self
}
}
}

View File

@@ -1,11 +1,11 @@
use crate::object::Value;
use crate::object::ObjPtr;
use crate::scope::Word;
use crate::syn::span::Spanned;
#[derive(Debug, Clone)]
pub enum Inst {
/// Push a constant value to the stack.
PushValue(Value),
PushValue(ObjPtr),
/// Load a word's value onto the stack.
Load(Word),

View File

@@ -16,7 +16,7 @@ impl Frame {
pub fn call_site(&self) -> Option<&Span> {
use Frame::*;
match self {
Native(frame) => Some(&frame.call_site),
Native(frame) => frame.call_site.as_ref(),
Quote(frame) => {
if frame.pc >= frame.code.len() {
frame.code.last().map(|inst| inst.span())
@@ -42,14 +42,14 @@ impl From<QuoteFrame> for Frame {
#[derive(Debug, Clone)]
pub struct NativeFrame {
call_site: Span,
call_site: Option<Span>,
fun: BuiltinFn,
reentry: usize,
}
#[derive(Debug, Clone)]
pub struct QuoteFrame {
locals: BTreeMap<Word, Option<Value>>,
locals: BTreeMap<Word, Option<ObjPtr>>,
code: Rc<Vec<SpInst>>,
pc: usize,
}
@@ -63,8 +63,8 @@ impl QuoteFrame {
/// The current state of a VM.
#[derive(Debug)]
pub struct Machine {
globals: BTreeMap<Word, Value>,
stack: Vec<Value>,
globals: BTreeMap<Word, ObjPtr>,
stack: Vec<ObjPtr>,
max_stack_size: Option<usize>,
quote_table: QuoteTable,
scope_stack: ScopeStack,
@@ -73,7 +73,7 @@ pub struct Machine {
impl Machine {
pub fn new(
globals: BTreeMap<Word, Value>,
globals: BTreeMap<Word, ObjPtr>,
max_stack_size: Option<usize>,
scope_stack: ScopeStack,
) -> Self {
@@ -95,15 +95,19 @@ impl Machine {
self.max_stack_size
}
pub fn stack(&self) -> &Vec<Value> {
pub fn call_stack(&self) -> &Vec<Frame> {
&self.call_stack
}
pub fn stack(&self) -> &Vec<ObjPtr> {
&self.stack
}
pub fn stack_mut(&mut self) -> &mut Vec<Value> {
pub fn stack_mut(&mut self) -> &mut Vec<ObjPtr> {
&mut self.stack
}
pub fn stack_push(&mut self, value: Value) -> Result<()> {
pub fn stack_push(&mut self, value: ObjPtr) -> Result<()> {
if let Some(max) = self.max_stack_size() {
if self.stack().len() >= max {
return Err(RuntimeError::StackOverflow.into());
@@ -113,13 +117,17 @@ impl Machine {
Ok(())
}
pub fn stack_pop(&mut self) -> Result<Value> {
pub fn stack_pop(&mut self) -> Result<ObjPtr> {
self.stack_mut()
.pop()
.ok_or_else(|| RuntimeError::StackUnderflow.into())
}
pub fn lookup_word(&self, word: &Word) -> Option<&Value> {
pub fn stack_peek(&self) -> Option<&ObjPtr> {
self.stack().last()
}
pub fn lookup_word(&self, word: &Word) -> Option<&ObjPtr> {
self.call_stack
.iter()
.rev()
@@ -135,7 +143,7 @@ impl Machine {
.or_else(|| self.globals.get(word))
}
pub fn store_local(&mut self, word: Word, value: Value) {
pub fn store_local(&mut self, word: Word, value: ObjPtr) {
let last = self
.call_stack
.iter_mut()
@@ -156,7 +164,7 @@ impl Machine {
self.call_stack.last()
}
fn call_quote(&mut self, quote: Quote) {
pub(crate) fn call_quote(&mut self, quote: Quote) {
let (_span, locals, _expr, code) = self.quote_table.get(quote);
// create a new stack frame
@@ -170,7 +178,7 @@ impl Machine {
);
}
fn call_native(&mut self, call_site: Span, native: BuiltinFn) {
pub(crate) fn call_native(&mut self, call_site: Option<Span>, native: BuiltinFn) {
self.call_stack.push(Frame::Native(NativeFrame {
call_site,
fun: native,
@@ -193,9 +201,7 @@ impl Machine {
if let Err(mut error) = self.eval_stmt_list(stmts) {
// go through the call stack and make the error
for frame in self.call_stack.iter() {
if let Some(call_site) = frame.call_site() {
error = error.with_location(call_site.clone());
}
error = error.with_location(frame.call_site().cloned());
}
Err(error)
} else {
@@ -203,22 +209,7 @@ impl Machine {
}
}
fn eval_stmt_list(&mut self, stmts: Vec<SpStmt>) -> Result<()> {
// TODO - figure out the best way to figure out the call stack
// locations, and build an error out of that
self.scope_stack.push_scope();
let mut compile = Compile::new(&mut self.scope_stack, &mut self.quote_table);
let code = Rc::new(compile.compile(stmts));
let locals = self.scope_stack.pop_scope().unwrap();
self.call_stack.push(
QuoteFrame {
locals: locals.keys().map(|w| (*w, None)).collect(),
code,
pc: 0,
}
.into(),
);
pub fn resume(&mut self) -> Result<()> {
// Run instructions
loop {
// nothing left to execute
@@ -243,7 +234,7 @@ impl Machine {
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)?;
let exit = fun.call(self, reentry)?;
match exit {
BuiltinExit::Call(quote) => {
@@ -268,8 +259,22 @@ impl Machine {
Ok(())
}
fn do_call_native(&mut self, native: BuiltinFn, reentry: usize) -> Result<BuiltinExit> {
native.call(self, reentry)
fn eval_stmt_list(&mut self, stmts: Vec<SpStmt>) -> Result<()> {
// TODO - figure out the best way to figure out the call stack
// locations, and build an error out of that
self.scope_stack.push_scope();
let mut compile = Compile::new(&mut self.scope_stack, &mut self.quote_table);
let code = Rc::new(compile.compile(stmts));
let locals = self.scope_stack.pop_scope().unwrap();
self.call_stack.push(
QuoteFrame {
locals: locals.keys().map(|w| (*w, None)).collect(),
code,
pc: 0,
}
.into(),
);
self.resume()
}
fn eval_inst(&mut self, inst: SpInst) -> Result<()> {
@@ -290,15 +295,10 @@ impl Machine {
let value = self.stack_pop()?;
self.store_local(word.clone(), value);
}
Inst::Call => match self.stack_pop()? {
Value::Quote(quote) => {
self.call_quote(quote);
}
Value::BuiltinFn(builtin) => {
self.call_native(inst.span().clone(), builtin);
}
value => return Err(RuntimeError::CannotCall(value.name().to_string()).into()),
},
Inst::Call => {
let value = self.stack_pop()?;
value.call(Some(inst.span().clone()), self)?;
}
};
// Update this frame's PC
@@ -314,7 +314,7 @@ impl Machine {
#[derive(Debug, Default)]
pub struct MachineBuilder {
pub(super) globals: BTreeMap<Word, Value>,
pub(super) globals: BTreeMap<Word, ObjPtr>,
pub(super) scope_stack: ScopeStack,
pub(super) max_stack_size: Option<usize>,
}
@@ -332,11 +332,11 @@ impl MachineBuilder {
) {
self.register_global(
name,
Value::BuiltinFn(BuiltinFn::new(name.to_string(), Rc::new(fun))),
BuiltinFnObj::new(BuiltinFn::new(name.to_string(), Rc::new(fun))).into_gc(),
);
}
pub(super) fn register_global(&mut self, name: &str, value: Value) {
pub(super) fn register_global(&mut self, name: &str, value: ObjPtr) {
let word = self.scope_stack.insert_local(name);
self.globals.insert(word, value);
}