Implement __bool__, add if builtin, vtable imporvements

* `if` builtin is the big one here - branching! Yay!
* The `if` builtin requires __bool__ to be implemented for things. This
  is added for all builtin objects.
* Vtables have slightly better ergonomics.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2022-02-04 17:29:41 -08:00
parent 5ec955d544
commit cb9e09064a
9 changed files with 168 additions and 68 deletions

View File

@@ -1,7 +1,7 @@
0 [ "fail - 0 is not truthy" println! ] [ "OK" println! ] if! [ "fail - 0 is not truthy" println! ] [ "OK" println! ] 0 if!
1 [ "OK" println! ] [ "fail - 1 is truthy" println! ] if! [ "OK" println! ] [ "fail - 1 is truthy" println! ] 1 if!
1.1 [ "OK" println! ] [ "fail - 1.1 is truthy" println! ] if! [ "OK" println! ] [ "fail - 1.1 is truthy" println! ] 1.1 if!
0.1 [ "OK" println! ] [ "fail - 0.1 is truthy" println! ] if! [ "OK" println! ] [ "fail - 0.1 is truthy" println! ] 0.1 if!
-0.1 [ "OK" println! ] [ "fail - -0.1 is truthy" println! ] if! [ "OK" println! ] [ "fail - -0.1 is truthy" println! ] -0.1 if!
-0.0 [ "fail - -0.0 is not truthy" println! ] [ "OK" println! ] if! [ "fail - -0.0 is not truthy" println! ] [ "OK" println! ] -0.0 if!
0.0 [ "fail - 0.0 is not truthy" println! ] [ "OK" println! ] if! [ "fail - 0.0 is not truthy" println! ] [ "OK" println! ] 0.0 if!

View File

@@ -1,4 +1,4 @@
use crate::obj::{Obj, VTable}; use crate::obj::prelude::*;
use crate::syn::span::Span; use crate::syn::span::Span;
use crate::vm::{error::*, machine::Machine}; use crate::vm::{error::*, machine::Machine};
use gc::{Finalize, Gc, Trace}; use gc::{Finalize, Gc, Trace};
@@ -14,9 +14,27 @@ pub struct BoolObj {
impl BoolObj { impl BoolObj {
pub fn new(value: Bool) -> Gc<Self> { pub fn new(value: Bool) -> Gc<Self> {
thread_local! { thread_local! {
static VTABLE: VTable = vtable! {
"__bool__" => builtin_fn!("__bool__", |_, _| {
// return self, don't do anything
Ok(BuiltinExit::Return)
}),
"__str__" => builtin_fn!("__str__", |machine, _| {
let obj_ptr: ObjPtr = machine.stack_pop()?;
let obj: &(dyn Obj + 'static) = &*obj_ptr;
if let Some(bool_) = obj.as_any().downcast_ref::<BoolObj>() {
let string = StrObj::new(bool_.value.to_string());
machine.stack_push(string)?;
Ok(BuiltinExit::Return)
} else {
Err(RuntimeError::WrongValue("bool".to_string()))
}
}),
};
static BOOLS: [Gc<BoolObj>; 2] = [ static BOOLS: [Gc<BoolObj>; 2] = [
Gc::new(BoolObj { value: false, vtable: vtable! {} }), Gc::new(BoolObj { value: false, vtable: VTABLE.with(Clone::clone), }),
Gc::new(BoolObj { value: true, vtable: vtable! {} }), Gc::new(BoolObj { value: true, vtable: VTABLE.with(Clone::clone), }),
]; ];
} }
BOOLS.with(|bools| Gc::clone(&bools[value as usize])) BOOLS.with(|bools| Gc::clone(&bools[value as usize]))

View File

@@ -1,4 +1,4 @@
use crate::obj::{quote::Quote, Obj, VTable}; use crate::obj::{Obj, VTable};
use crate::syn::span::Span; use crate::syn::span::Span;
use crate::vm::{error::Result, machine::Machine}; use crate::vm::{error::Result, machine::Machine};
use gc::{unsafe_empty_trace, Finalize, Gc, Trace}; use gc::{unsafe_empty_trace, Finalize, Gc, Trace};
@@ -12,9 +12,6 @@ use std::rc::Rc;
/// pre-empt itself with the understanding that it will eventually return. /// pre-empt itself with the understanding that it will eventually return.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum BuiltinExit { pub enum BuiltinExit {
/// Pop this builtin off the call stack, and then call the given quote.
Call(Quote),
/// Continue with the assumption that some other function has been pushed to /// Continue with the assumption that some other function has been pushed to
/// the call stack, updating the reentry for this function call to the /// the call stack, updating the reentry for this function call to the
/// specified constant. /// specified constant.

View File

@@ -1,4 +1,4 @@
use crate::obj::{builtin::BuiltinExit, str::StrObj, Obj, ObjPtr, VTable}; use crate::obj::prelude::*;
use crate::syn::span::Span; use crate::syn::span::Span;
use crate::vm::{error::*, machine::Machine}; use crate::vm::{error::*, machine::Machine};
use gc::{Finalize, Gc, Trace}; use gc::{Finalize, Gc, Trace};
@@ -14,23 +14,36 @@ pub struct FloatObj {
impl FloatObj { impl FloatObj {
pub fn new(value: Float) -> Gc<Self> { pub fn new(value: Float) -> Gc<Self> {
// TODO : intern float value // TODO : intern float value
let float_str: ObjPtr = builtin_fn!("__str__", |machine, _| {
let obj_ptr: ObjPtr = machine.stack_pop()?; thread_local! {
let obj: &(dyn Obj + 'static) = &*obj_ptr; static VTABLE: VTable = vtable! {
if let Some(float) = obj.as_any().downcast_ref::<FloatObj>() { "__bool__" => builtin_fn!("__bool__", |machine, _| {
let string = StrObj::new(float.value.to_string()); let obj_ptr: ObjPtr = machine.stack_pop()?;
machine.stack_push(string)?; let obj: &(dyn Obj + 'static) = &*obj_ptr;
Ok(BuiltinExit::Return) if let Some(float) = obj.as_any().downcast_ref::<FloatObj>() {
} else { let bool_ = BoolObj::new(float.value != 0.0);
Err(RuntimeError::WrongValue("float".to_string())) machine.stack_push(bool_)?;
} Ok(BuiltinExit::Return)
// Create the builtin float string } else {
}); Err(RuntimeError::WrongValue("float".to_string()))
}
}),
"__str__" => builtin_fn!("__str__", |machine, _| {
let obj_ptr: ObjPtr = machine.stack_pop()?;
let obj: &(dyn Obj + 'static) = &*obj_ptr;
if let Some(float) = obj.as_any().downcast_ref::<FloatObj>() {
let string = StrObj::new(float.value.to_string());
machine.stack_push(string)?;
Ok(BuiltinExit::Return)
} else {
Err(RuntimeError::WrongValue("float".to_string()))
}
}),
};
};
Gc::new(FloatObj { Gc::new(FloatObj {
value, value,
vtable: vtable! { vtable: VTABLE.with(Clone::clone),
"__str__" => float_str,
},
}) })
} }

View File

@@ -1,4 +1,4 @@
use crate::obj::{Obj, VTable}; use crate::obj::prelude::*;
use crate::syn::span::Span; use crate::syn::span::Span;
use crate::vm::{error::*, machine::Machine}; use crate::vm::{error::*, machine::Machine};
use gc::{Finalize, Gc, Trace}; use gc::{Finalize, Gc, Trace};
@@ -13,10 +13,36 @@ pub struct IntObj {
impl IntObj { impl IntObj {
pub fn new(value: Int) -> Gc<Self> { pub fn new(value: Int) -> Gc<Self> {
thread_local! {
static VTABLE: VTable = vtable! {
"__bool__" => builtin_fn!("__bool__", |machine, _| {
let obj_ptr: ObjPtr = machine.stack_pop()?;
let obj: &(dyn Obj + 'static) = &*obj_ptr;
if let Some(int) = obj.as_any().downcast_ref::<IntObj>() {
let bool_ = BoolObj::new(int.value != 0);
machine.stack_push(bool_)?;
Ok(BuiltinExit::Return)
} else {
Err(RuntimeError::WrongValue("int".to_string()))
}
}),
"__str__" => builtin_fn!("__str__", |machine, _| {
let obj_ptr: ObjPtr = machine.stack_pop()?;
let obj: &(dyn Obj + 'static) = &*obj_ptr;
if let Some(int) = obj.as_any().downcast_ref::<IntObj>() {
let string = StrObj::new(int.value.to_string());
machine.stack_push(string)?;
Ok(BuiltinExit::Return)
} else {
Err(RuntimeError::WrongValue("int".to_string()))
}
}),
};
};
// TODO : intern int value // TODO : intern int value
Gc::new(IntObj { Gc::new(IntObj {
value, value,
vtable: vtable! {}, vtable: VTABLE.with(Clone::clone),
}) })
} }

View File

@@ -4,7 +4,7 @@ macro_rules! vtable {
$crate::obj::VTable::from( $crate::obj::VTable::from(
[ [
$( $(
($key.to_string(), $value) ($key.to_string(), $value as ObjPtr)
),* ),*
] ]
) )

View File

@@ -1,4 +1,4 @@
use crate::obj::{builtin::BuiltinExit, Obj, ObjPtr, VTable}; use crate::obj::prelude::*;
use crate::syn::span::Span; use crate::syn::span::Span;
use crate::vm::{error::*, machine::Machine}; use crate::vm::{error::*, machine::Machine};
use gc::{Finalize, Gc, Trace}; use gc::{Finalize, Gc, Trace};
@@ -13,21 +13,28 @@ pub struct StrObj {
impl StrObj { impl StrObj {
pub fn new(value: String) -> Gc<Self> { pub fn new(value: String) -> Gc<Self> {
let str_str: ObjPtr = builtin_fn!("__str__", |machine, _| { thread_local! {
let obj_ptr: ObjPtr = machine.stack_pop()?; static VTABLE: VTable = vtable! {
let obj: &(dyn Obj + 'static) = &*obj_ptr; "__str__" => builtin_fn!("__str__", |_, _| {
if obj.as_any().is::<StrObj>() { // return self, don't do anything
machine.stack_push(obj_ptr)?; Ok(BuiltinExit::Return)
Ok(BuiltinExit::Return) }),
} else { "__bool__" => builtin_fn!("__bool__", |machine, _| {
Err(RuntimeError::WrongValue("float".to_string())) let obj_ptr: ObjPtr = machine.stack_pop()?;
} let obj: &(dyn Obj + 'static) = &*obj_ptr;
}); if let Some(string) = obj.as_any().downcast_ref::<StrObj>() {
let bool_ = BoolObj::new(string.value.len() != 0);
machine.stack_push(bool_)?;
Ok(BuiltinExit::Return)
} else {
Err(RuntimeError::WrongValue("int".to_string()))
}
}),
};
}
Gc::new(StrObj { Gc::new(StrObj {
value, value,
vtable: vtable! { vtable: VTABLE.with(Clone::clone),
"__str__" => str_str,
},
}) })
} }

View File

@@ -3,6 +3,7 @@ use crate::vm::{
error::RuntimeError, error::RuntimeError,
machine::{Machine, MachineBuilder}, machine::{Machine, MachineBuilder},
}; };
use gc::Gc;
impl MachineBuilder { impl MachineBuilder {
/// Registers all builtins for calling on the machine. /// Registers all builtins for calling on the machine.
@@ -32,31 +33,50 @@ impl MachineBuilder {
Ok(BuiltinExit::Return) Ok(BuiltinExit::Return)
}); });
/*
// //
// if // if
// //
self.register_builtin_fun("if", |machine, _| { self.register_builtin_fun("if", |machine, reentry| {
let if_false = machine.stack_pop()?; const CALL_BOOL: usize = 1;
let if_true = machine.stack_pop()?; const RETURN: usize = 2;
let condition = machine.stack_pop()?;
let value = if condition.is_truthy() { if reentry == 0 {
if_true // get __bool__ attr from condition stack member
} else { let bool_fun = machine
if_false .stack_peek()
}; .ok_or_else(|| RuntimeError::StackUnderflow)?
.get("__bool__")
.ok_or_else(|| RuntimeError::UnsetWord("__bool__".to_string()))?;
if let Value::Quote(quote) = value { // call it
Ok(BuiltinExit::Call(quote)) let call_site = machine.call_stack().last().unwrap().call_site().cloned();
bool_fun.call(call_site, machine)?;
Ok(BuiltinExit::Resume(CALL_BOOL))
} else if reentry == CALL_BOOL {
let obj_ptr = machine.stack_pop()?;
let if_false = machine.stack_pop()?;
let if_true = machine.stack_pop()?;
// make sure we gotta bool
if let Some(condition) = obj_ptr.as_any().downcast_ref::<BoolObj>() {
let call_obj = if condition.value() { if_true } else { if_false };
// TODO - maybe it's possible to determine where we're at in
// the source code, but it probably requires saving a state
// that I'm not willing to commit to yet
call_obj.call(None, machine)?;
Ok(BuiltinExit::Resume(RETURN))
} else {
panic!(
"expected BoolObj from __bool__ function but got {:?} instead",
obj_ptr
)
}
} else if reentry == RETURN {
// finish the function call
Ok(BuiltinExit::Return)
} else { } else {
return Err(RuntimeError::CannotCall( unreachable!()
"if statement requires quote value".to_string(),
)
.into());
} }
}); });
*/
// //
// print // print
@@ -145,5 +165,28 @@ impl MachineBuilder {
Ok(BuiltinExit::Return) Ok(BuiltinExit::Return)
}); });
*/ */
//
// getattr
//
self.register_builtin_fun("getattr", |machine, _| {
let obj = machine.stack_pop()?;
let attr = machine.stack_pop()?;
if let Some(string) = attr.as_any().downcast_ref::<StrObj>() {
let value = Gc::clone(
obj.vtable()
.get(string.value())
.ok_or_else(|| RuntimeError::UnsetWord(string.value().clone()))?,
);
machine.stack_push(value)?;
} else {
panic!("expected StrObj for attr in getattr call");
}
Ok(BuiltinExit::Return)
});
//
// setattr
//
} }
} }

View File

@@ -237,10 +237,6 @@ impl Machine {
let exit = fun.call(self, reentry)?; let exit = fun.call(self, reentry)?;
match exit { match exit {
BuiltinExit::Call(quote) => {
self.call_stack.pop();
self.call_quote(quote);
}
BuiltinExit::Resume(reentry) => { BuiltinExit::Resume(reentry) => {
if let Frame::Native(frame) = &mut self.call_stack[current_frame] { if let Frame::Native(frame) = &mut self.call_stack[current_frame] {
frame.reentry = reentry; frame.reentry = reentry;