Skip to content

Instantly share code, notes, and snippets.

@mbillingr
Last active March 5, 2019 15:23
Show Gist options
  • Save mbillingr/1138a88528cf9ab638fb2190425c2ce0 to your computer and use it in GitHub Desktop.
Save mbillingr/1138a88528cf9ab638fb2190425c2ce0 to your computer and use it in GitHub Desktop.
Stack-based Object VM
// `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