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!
1 [ "OK" println! ] [ "fail - 1 is truthy" println! ] if!
1.1 [ "OK" println! ] [ "fail - 1.1 is truthy" println! ] if!
0.1 [ "OK" println! ] [ "fail - 0.1 is truthy" println! ] if!
-0.1 [ "OK" println! ] [ "fail - -0.1 is truthy" println! ] if!
-0.0 [ "fail - -0.0 is not truthy" println! ] [ "OK" println! ] if!
0.0 [ "fail - 0.0 is not truthy" println! ] [ "OK" println! ] if!
[ "fail - 0 is not truthy" println! ] [ "OK" println! ] 0 if!
[ "OK" println! ] [ "fail - 1 is truthy" println! ] 1 if!
[ "OK" println! ] [ "fail - 1.1 is truthy" println! ] 1.1 if!
[ "OK" println! ] [ "fail - 0.1 is truthy" println! ] 0.1 if!
[ "OK" println! ] [ "fail - -0.1 is truthy" println! ] -0.1 if!
[ "fail - -0.0 is not truthy" println! ] [ "OK" println! ] -0.0 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::vm::{error::*, machine::Machine};
use gc::{Finalize, Gc, Trace};
@@ -14,9 +14,27 @@ pub struct BoolObj {
impl BoolObj {
pub fn new(value: Bool) -> Gc<Self> {
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] = [
Gc::new(BoolObj { value: false, vtable: vtable! {} }),
Gc::new(BoolObj { value: true, vtable: vtable! {} }),
Gc::new(BoolObj { value: false, vtable: VTABLE.with(Clone::clone), }),
Gc::new(BoolObj { value: true, vtable: VTABLE.with(Clone::clone), }),
];
}
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::vm::{error::Result, machine::Machine};
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.
#[derive(Debug, Clone, Copy)]
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
/// the call stack, updating the reentry for this function call to the
/// 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::vm::{error::*, machine::Machine};
use gc::{Finalize, Gc, Trace};
@@ -14,7 +14,21 @@ pub struct FloatObj {
impl FloatObj {
pub fn new(value: Float) -> Gc<Self> {
// TODO : intern float value
let float_str: ObjPtr = builtin_fn!("__str__", |machine, _| {
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(float) = obj.as_any().downcast_ref::<FloatObj>() {
let bool_ = BoolObj::new(float.value != 0.0);
machine.stack_push(bool_)?;
Ok(BuiltinExit::Return)
} 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>() {
@@ -24,13 +38,12 @@ impl FloatObj {
} else {
Err(RuntimeError::WrongValue("float".to_string()))
}
// Create the builtin float string
});
}),
};
};
Gc::new(FloatObj {
value,
vtable: vtable! {
"__str__" => float_str,
},
vtable: VTABLE.with(Clone::clone),
})
}

View File

@@ -1,4 +1,4 @@
use crate::obj::{Obj, VTable};
use crate::obj::prelude::*;
use crate::syn::span::Span;
use crate::vm::{error::*, machine::Machine};
use gc::{Finalize, Gc, Trace};
@@ -13,10 +13,36 @@ pub struct IntObj {
impl IntObj {
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
Gc::new(IntObj {
value,
vtable: vtable! {},
vtable: VTABLE.with(Clone::clone),
})
}

View File

@@ -4,7 +4,7 @@ macro_rules! vtable {
$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::vm::{error::*, machine::Machine};
use gc::{Finalize, Gc, Trace};
@@ -13,21 +13,28 @@ pub struct StrObj {
impl StrObj {
pub fn new(value: String) -> Gc<Self> {
let str_str: ObjPtr = builtin_fn!("__str__", |machine, _| {
thread_local! {
static VTABLE: VTable = vtable! {
"__str__" => builtin_fn!("__str__", |_, _| {
// return self, don't do anything
Ok(BuiltinExit::Return)
}),
"__bool__" => builtin_fn!("__bool__", |machine, _| {
let obj_ptr: ObjPtr = machine.stack_pop()?;
let obj: &(dyn Obj + 'static) = &*obj_ptr;
if obj.as_any().is::<StrObj>() {
machine.stack_push(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("float".to_string()))
Err(RuntimeError::WrongValue("int".to_string()))
}
}),
};
}
});
Gc::new(StrObj {
value,
vtable: vtable! {
"__str__" => str_str,
},
vtable: VTABLE.with(Clone::clone),
})
}

View File

@@ -3,6 +3,7 @@ use crate::vm::{
error::RuntimeError,
machine::{Machine, MachineBuilder},
};
use gc::Gc;
impl MachineBuilder {
/// Registers all builtins for calling on the machine.
@@ -32,31 +33,50 @@ impl MachineBuilder {
Ok(BuiltinExit::Return)
});
/*
//
// if
//
self.register_builtin_fun("if", |machine, _| {
self.register_builtin_fun("if", |machine, reentry| {
const CALL_BOOL: usize = 1;
const RETURN: usize = 2;
if reentry == 0 {
// get __bool__ attr from condition stack member
let bool_fun = machine
.stack_peek()
.ok_or_else(|| RuntimeError::StackUnderflow)?
.get("__bool__")
.ok_or_else(|| RuntimeError::UnsetWord("__bool__".to_string()))?;
// call it
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()?;
let condition = machine.stack_pop()?;
let value = if condition.is_truthy() {
if_true
// 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 {
if_false
};
if let Value::Quote(quote) = value {
Ok(BuiltinExit::Call(quote))
} else {
return Err(RuntimeError::CannotCall(
"if statement requires quote value".to_string(),
panic!(
"expected BoolObj from __bool__ function but got {:?} instead",
obj_ptr
)
.into());
}
} else if reentry == RETURN {
// finish the function call
Ok(BuiltinExit::Return)
} else {
unreachable!()
}
});
*/
//
// print
@@ -145,5 +165,28 @@ impl MachineBuilder {
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)?;
match exit {
BuiltinExit::Call(quote) => {
self.call_stack.pop();
self.call_quote(quote);
}
BuiltinExit::Resume(reentry) => {
if let Frame::Native(frame) = &mut self.call_stack[current_frame] {
frame.reentry = reentry;