Last active
March 5, 2019 15:23
-
-
Save mbillingr/1138a88528cf9ab638fb2190425c2ce0 to your computer and use it in GitHub Desktop.
Stack-based Object VM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// `error_chain!` can recurse deeply | |
#![recursion_limit = "1024"] | |
#[macro_use] | |
extern crate error_chain; | |
use std::collections::HashMap; | |
use std::fmt::Debug; | |
use std::rc::Rc; | |
type Int = i64; | |
type Stack = Vec<Object>; | |
type Table = Rc<HashMap<String, Object>>; | |
#[derive(Clone)] | |
struct BuiltinFunction(Rc<dyn Fn(&mut Stack) -> Result<()>>); | |
//type Function = Vec<Op>; | |
trait ObjectInterface: Debug { | |
fn add(&self, _stack: &mut Stack) -> Result<()> { | |
Err(ErrorKind::TypeError.into()) | |
} | |
fn call(&self, _stack: &mut Stack) -> Result<()> { | |
Err(ErrorKind::TypeError.into()) | |
} | |
fn call_method(&self, _name: &str, _stack: &mut Stack) -> Result<()> { | |
Err(ErrorKind::TypeError.into()) | |
} | |
fn resolve_method(&self, _name: &str) -> Option<Object> { | |
None | |
} | |
fn to_int(&self, _stack: &mut Stack) -> Result<Int> { | |
Err(ErrorKind::TypeError.into()) | |
} | |
fn expect_int(&self) -> Result<Int> { | |
Err(ErrorKind::TypeError.into()) | |
} | |
fn expect_str(&self) -> Result<&str> { | |
Err(ErrorKind::TypeError.into()) | |
} | |
fn set_attr(&mut self, _key: String, _value: Object) -> Result<()> { | |
Err(ErrorKind::TypeError.into()) | |
} | |
fn get_attr(&self, _key: &str) -> Result<Object> { | |
Err(ErrorKind::TypeError.into()) | |
} | |
} | |
trait BoxClone: ObjectInterface { | |
fn box_clone(&self) -> Box<dyn BoxClone>; | |
} | |
impl Clone for Box<BoxClone> { | |
fn clone(&self) -> Self { | |
self.box_clone() | |
} | |
} | |
#[derive(Clone)] | |
enum Object { | |
Nil, | |
Dynamic(Box<dyn BoxClone>), | |
Int(Int), | |
String(String), | |
Table(Table), | |
} | |
impl Debug for Object { | |
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | |
match self { | |
Object::Nil => write!(f, "Nil"), | |
Object::Dynamic(obj) => write!(f, "Dynamic({:?})", obj), | |
Object::Int(i) => write!(f, "{:?}", i), | |
Object::String(s) => write!(f, "{:?}", s), | |
Object::Table(t) => { | |
if let Ok(Object::Nil) = t.get_attr("__class__") { | |
write!( | |
f, | |
"{}", | |
t.get_attr("__name__").unwrap().expect_str().unwrap() | |
) | |
} else if let Some(func) = t.resolve_method("__repr__") { | |
// use temporary stack to call __repr__ (let's hope it does not have any side effects) | |
let mut stack = vec![self.clone()]; | |
func.call(&mut stack).unwrap(); | |
let repr = stack.pop().unwrap(); | |
write!(f, "{}", repr.expect_str().unwrap()) | |
} else { | |
write!(f, "{:?}", t) | |
} | |
} | |
} | |
} | |
} | |
impl ObjectInterface for Object { | |
fn add(&self, stack: &mut Stack) -> Result<()> { | |
match self { | |
Object::Nil => Err(ErrorKind::TypeError.into()), | |
Object::Dynamic(obj) => obj.add(stack), | |
Object::Int(i) => { | |
let other = stack.pop().ok_or(ErrorKind::TypeError)?; | |
let result = i + other.expect_int()?; | |
stack.push(Object::Int(result)); | |
Ok(()) | |
} | |
Object::String(_) => unimplemented!(), | |
Object::Table(t) => t.add(stack), | |
} | |
} | |
fn call(&self, stack: &mut Stack) -> Result<()> { | |
match self { | |
Object::Dynamic(obj) => obj.call(stack), | |
Object::Table(t) => t.call(stack), | |
_ => Err(ErrorKind::TypeError.into()), | |
} | |
} | |
fn call_method(&self, name: &str, stack: &mut Stack) -> Result<()> { | |
match self { | |
Object::Dynamic(obj) => obj.call_method(name, stack), | |
Object::Table(t) => t.call_method(name, stack), | |
_ => Err(ErrorKind::TypeError.into()), | |
} | |
} | |
fn resolve_method(&self, name: &str) -> Option<Object> { | |
match self { | |
Object::Dynamic(obj) => obj.resolve_method(name), | |
Object::Table(t) => t.resolve_method(name), | |
_ => None, | |
} | |
} | |
fn to_int(&self, stack: &mut Stack) -> Result<Int> { | |
match self { | |
Object::Dynamic(obj) => obj.to_int(stack), | |
Object::Int(i) => Ok(*i), | |
Object::Table(t) => t.to_int(stack), | |
_ => Err(ErrorKind::TypeError.into()), | |
} | |
} | |
fn expect_int(&self) -> Result<Int> { | |
match self { | |
Object::Int(i) => Ok(*i), | |
_ => Err(ErrorKind::TypeError.into()), | |
} | |
} | |
fn expect_str(&self) -> Result<&str> { | |
match self { | |
Object::String(s) => Ok(s), | |
_ => Err(ErrorKind::TypeError.into()), | |
} | |
} | |
fn set_attr(&mut self, key: String, value: Object) -> Result<()> { | |
match self { | |
Object::Dynamic(obj) => obj.set_attr(key, value), | |
Object::Table(obj) => obj.set_attr(key, value), | |
_ => Err(ErrorKind::TypeError.into()), | |
} | |
} | |
fn get_attr(&self, key: &str) -> Result<Object> { | |
match self { | |
Object::Dynamic(obj) => obj.get_attr(key), | |
Object::Table(obj) => obj.get_attr(key), | |
_ => Err(ErrorKind::TypeError.into()), | |
} | |
} | |
} | |
fn instantiate(cls: Table) -> Table { | |
let mut instance = HashMap::new(); | |
instance.insert("__class__".to_string(), Object::Table(cls)); | |
Rc::new(instance) | |
} | |
impl ObjectInterface for Table { | |
fn add(&self, stack: &mut Stack) -> Result<()> { | |
self.call_method("__add__", stack) | |
} | |
fn call(&self, stack: &mut Stack) -> Result<()> { | |
if let Some(Object::Nil) = self.get("__class__") { | |
// todo: think of a better way to represent types | |
let instance = instantiate(self.clone()); | |
let init = self.get("__init__"); | |
stack.push(Object::Table(instance)); | |
if let Some(init) = init { | |
init.call(stack)? | |
} | |
return Ok(()); | |
} | |
self.call_method("__call__", stack) | |
} | |
fn call_method(&self, name: &str, stack: &mut Stack) -> Result<()> { | |
if let Some(method) = self.resolve_method(name) { | |
stack.push(Object::Table(self.clone())); | |
method.call(stack) | |
} else { | |
Err(ErrorKind::AttributeError(name.to_string()).into()) | |
} | |
} | |
fn resolve_method(&self, name: &str) -> Option<Object> { | |
if let Some(f) = self.get(name) { | |
return Some(f.clone()); | |
} | |
if let Some(base) = self.get("__class__") { | |
return base.resolve_method(name); | |
} | |
None | |
} | |
fn to_int(&self, stack: &mut Stack) -> Result<Int> { | |
self.call_method("__int__", stack)?; | |
stack.pop().ok_or(ErrorKind::StackUnderflow)?.to_int(stack) | |
} | |
fn set_attr(&mut self, key: String, value: Object) -> Result<()> { | |
match Rc::get_mut(self) { | |
None => Err(ErrorKind::MutError.into()), | |
Some(inner) => { | |
inner.insert(key, value); | |
Ok(()) | |
} | |
} | |
} | |
fn get_attr(&self, key: &str) -> Result<Object> { | |
//self.get(key).cloned().ok_or_else(||ErrorKind::AttributeError(key.to_string()).into()) | |
self.resolve_method(key) | |
.ok_or_else(|| ErrorKind::AttributeError(key.to_string()).into()) | |
} | |
} | |
impl Debug for BuiltinFunction { | |
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { | |
write!(f, "<builtin>") | |
} | |
} | |
impl ObjectInterface for BuiltinFunction { | |
fn call(&self, stack: &mut Stack) -> Result<()> { | |
self.0(stack) | |
} | |
} | |
impl BoxClone for BuiltinFunction { | |
fn box_clone(&self) -> Box<dyn BoxClone> { | |
Box::new((*self).clone()) | |
} | |
} | |
/*impl DynObject for Function { | |
fn call(&self, stack: &mut Stack) -> Result<()> { | |
run(self, stack) | |
} | |
}*/ | |
/*impl BoxClone for Function { | |
fn box_clone(&self) -> Box<dyn BoxClone> { | |
Box::new((*self).clone()) | |
} | |
}*/ | |
#[derive(Debug, Clone)] | |
enum Op { | |
Add, | |
Call, | |
Dup, | |
Lit(Object), | |
GetAttr(String), | |
} | |
// We'll put our errors in an `errors` module, and other modules in | |
// this crate will `use errors::*;` to get access to everything | |
// `error_chain!` creates. | |
mod errors { | |
// Create the Error, ErrorKind, ResultExt, and Result types | |
error_chain! { | |
errors { | |
AttributeError(t: String) { | |
description("attribute not found") | |
display("object does not have attribute: '{}'", t) | |
} | |
MutError | |
StackUnderflow | |
TypeError | |
} | |
} | |
} | |
use errors::*; | |
fn run(ops: &[Op], stack: &mut Stack) -> Result<()> { | |
for op in ops { | |
match op { | |
Op::Add => { | |
let a = stack.pop().ok_or(ErrorKind::StackUnderflow)?; | |
a.add(stack)?; | |
} | |
Op::Call => { | |
let a = stack.pop().ok_or(ErrorKind::StackUnderflow)?; | |
a.call(stack)?; | |
} | |
Op::Dup => { | |
let obj = stack.last().cloned().ok_or(ErrorKind::StackUnderflow)?; | |
stack.push(obj); | |
} | |
Op::Lit(obj) => stack.push(obj.clone()), | |
Op::GetAttr(attr) => { | |
let obj = stack.pop().ok_or(ErrorKind::StackUnderflow)?; | |
let val = obj.get_attr(attr)?; | |
stack.push(val); | |
} | |
} | |
} | |
Ok(()) | |
} | |
struct HashMapBuilder { | |
hm: HashMap<String, Object>, | |
} | |
impl HashMapBuilder { | |
fn new() -> Self { | |
HashMapBuilder { hm: HashMap::new() } | |
} | |
fn with_field<V: IntoObject>(mut self, key: &str, value: V) -> Self { | |
self.hm.insert(key.to_string(), value.into_object()); | |
self | |
} | |
fn build(self) -> Object { | |
Object::Table(Rc::new(self.hm)) | |
} | |
} | |
trait IntoObject { | |
fn into_object(self) -> Object; | |
} | |
impl IntoObject for Object { | |
fn into_object(self) -> Object { | |
self | |
} | |
} | |
impl IntoObject for Int { | |
fn into_object(self) -> Object { | |
Object::Int(self) | |
} | |
} | |
impl IntoObject for String { | |
fn into_object(self) -> Object { | |
Object::String(self) | |
} | |
} | |
impl<T> IntoObject for T | |
where | |
T: BoxClone + 'static, | |
{ | |
fn into_object(self) -> Object { | |
Object::Dynamic(Box::new(self)) | |
} | |
} | |
fn lit<T: IntoObject>(val: T) -> Op { | |
Op::Lit(val.into_object()) | |
} | |
fn main() { | |
if let Err(ref e) = _main() { | |
eprintln!("error: {}", e); | |
for e in e.iter().skip(1) { | |
eprintln!("caused by: {}", e); | |
} | |
// The backtrace is not always generated. Try to run this example | |
// with `RUST_BACKTRACE=1`. | |
if let Some(backtrace) = e.backtrace() { | |
eprintln!("backtrace: {:?}", backtrace); | |
} | |
::std::process::exit(1); | |
} | |
} | |
fn _main() -> Result<()> { | |
// define a class Number that supports addition | |
let number = HashMapBuilder::new() | |
.with_field("__name__", "Number".to_string()) | |
.with_field("__class__", Object::Nil) | |
.with_field( | |
"__init__", | |
BuiltinFunction(Rc::new(|stack| { | |
// todo: there is too much stuff on the stack here... | |
let mut this = stack.pop().ok_or(ErrorKind::StackUnderflow)?; | |
let val = stack.pop().ok_or(ErrorKind::StackUnderflow)?; | |
this.set_attr("value".to_string(), val)?; | |
stack.push(this); | |
Ok(()) | |
})), | |
) | |
.with_field( | |
"__repr__", | |
BuiltinFunction(Rc::new(|stack| { | |
let this = stack.pop().ok_or(ErrorKind::StackUnderflow)?; | |
let cls = this.get_attr("__class__")?; | |
let name = cls.get_attr("__name__")?; | |
let val = this.get_attr("value")?.to_int(stack)?; | |
stack.push(format!("{}({})", name.expect_str()?, val).into_object()); | |
Ok(()) | |
})), | |
) | |
.with_field( | |
"__add__", | |
BuiltinFunction(Rc::new(|stack| { | |
let this = stack.pop().ok_or(ErrorKind::StackUnderflow)?; | |
let other = stack.pop().ok_or(ErrorKind::StackUnderflow)?; | |
let cls = this.get_attr("__class__")?; | |
let a = this.get_attr("value")?; | |
let b = other.get_attr("value")?; | |
stack.push(b); | |
/*stack.push(a); | |
run(&[Op::Add], stack)?;*/ | |
a.add(stack)?; | |
// create new instance of this type | |
cls.call(stack) | |
})), | |
) | |
.with_field( | |
"sqr", | |
BuiltinFunction(Rc::new(|stack| { | |
let this = stack.pop().ok_or(ErrorKind::StackUnderflow)?; | |
let cls = this.get_attr("__class__")?; | |
let val = this.get_attr("value")?.to_int(stack)?; | |
stack.push((val * val).into_object()); | |
// create new instance of this type | |
cls.call(stack) | |
})), | |
) | |
.with_field( | |
"__int__", | |
BuiltinFunction(Rc::new(|stack| { | |
let this = stack.pop().ok_or(ErrorKind::StackUnderflow)?; | |
let val = this.get_attr("value")?; | |
stack.push(val); | |
Ok(()) | |
})), | |
) | |
.build(); | |
println!(" The class: {:?}", number); | |
let mut stack = vec![]; | |
run( | |
&[ | |
lit(2), | |
lit(number.clone()), | |
Op::Call, | |
lit(3), | |
lit(number.clone()), | |
Op::Call, | |
], | |
&mut stack, | |
)?; | |
println!(" Two instances on the stack: {:?}", stack); | |
run(&[Op::Add], &mut stack)?; | |
println!(" Stack after performing `Add`: {:?}", stack); | |
run( | |
&[Op::Dup, Op::GetAttr("sqr".to_string()), Op::Call], | |
&mut stack, | |
)?; | |
println!("Stack after calling the `sqr` attribute: {:?}", stack); | |
Ok(()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment