Change up how function calls are handled

This is a big one.

For a while, builtin functions were a bit cumbersome and not easily
re-entrant. If you needed to call a function from within a builtin
function, the only method of doing so was to take a `FunctionState`
parameter, which would either be "Begin", meaning the function was being
called for the first time, or "Resume", meaning the function was being
re-entered. This meant that if we wanted to call another function within
this function, we'd have to set up a whole `match` statement to figure
out whether we were re-entering the function or starting out. It was a
mess and not very ergonomic, and most importantly, made it very
difficult to implement hashmaps.

Now, builtin functions are handled a little more elegantly. A native
function is pushed to the stack, where it is detected in the
`Vm::dispatch()` function. It is then called, like normal. If the
builtin function then needs to call *another* function, it will push
that function to the stack and call it, and then call `Vm::resume()` to
resume VM execution. `Vm::dispatch()` is then called again, this time
with the current function on top of the stack. If it's another builtin
function, the above is repeated. If it's a user-defined function, then
bytecode is executed in the main `loop` inside of resume. Ultimately, we
are able to compose builtin functions like we would any other internal
function to the program. Overall this should speed things up a little,
make them a whole lot easier to read, and make them a million times
easier to compose with other builtin parts of Rust.

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2024-10-11 16:22:19 -07:00
parent 3fd0ba3a0b
commit c6e242c39d
12 changed files with 226 additions and 405 deletions

View File

@@ -22,56 +22,28 @@ pub fn init_global_builtins() {
} }
} }
// TODO builtins.rs - need a good macro to help reduce this repetition.
// The main problem is that "macros cannot expand to match arms".
// Thus, if we try to do something like this:
//
// ( ($n:expr, $block:block),* ) => { FunctionState::Resume($n) => $block }
//
// Is not allowed.
//
// This would probably be doable in a procedural macro.
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Global functions // Global functions
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
pub(crate) fn println(vm: &mut Vm, state: FunctionState) -> FunctionResult { pub(crate) fn println(vm: &mut Vm) -> ObjP {
match state { let obj = vm.frame_stack()[0].clone();
FunctionState::Begin => {
let obj = vm.peek();
let method = obj let method = obj
.borrow() .borrow()
.get_vtable_attr(obj.clone(), "to_str") .get_vtable_attr(obj.clone(), "to_str")
.expect("no to_str"); .expect("no to_str");
vm.push(method.clone()); let result = vm.call(method, &[]);
method.borrow().call(vm, 0); println!("{}", result.borrow());
FunctionResult::Yield(0) Nil::create()
}
FunctionState::Resume(0) => {
println!("{}", vm.frame_stack().last().unwrap().borrow());
Nil::create().into()
}
_ => unreachable!(),
}
} }
pub(crate) fn print(vm: &mut Vm, state: FunctionState) -> FunctionResult { pub(crate) fn print(vm: &mut Vm) -> ObjP {
match state { let obj = vm.frame_stack()[0].clone();
FunctionState::Begin => {
let obj = vm.peek();
let method = obj let method = obj
.borrow() .borrow()
.get_vtable_attr(obj.clone(), "to_str") .get_vtable_attr(obj.clone(), "to_str")
.expect("no to_str"); .expect("no to_str");
vm.push(method.clone()); let result = vm.call(method, &[]);
method.borrow().call(vm, 0); print!("{}", result.borrow());
FunctionResult::Yield(0) Nil::create()
}
FunctionState::Resume(0) => {
print!("{}", vm.frame_stack().last().unwrap().borrow());
Nil::create().into()
}
_ => unreachable!(),
}
} }

View File

@@ -56,7 +56,7 @@ fn main() {
// VM needs the module to be on top of the stack when it executes // VM needs the module to be on top of the stack when it executes
vm.push(module.clone()); vm.push(module.clone());
vm.enter_module(module); vm.enter_module(module);
vm.run(); vm.resume();
Ok(()) Ok(())
} }

View File

@@ -48,7 +48,7 @@ pub mod prelude {
pub use crate::obj::{Nil, Obj}; pub use crate::obj::{Nil, Obj};
// Other auxiliary types and functions // Other auxiliary types and functions
pub use crate::obj::function::{BuiltinFunctionPtr, FunctionResult, FunctionState}; pub use crate::obj::function::BuiltinFunctionPtr;
} }
// TODO obj::with_obj_downcast - optimize downcasts of "known" types with an unchecked downcast // TODO obj::with_obj_downcast - optimize downcasts of "known" types with an unchecked downcast
@@ -308,107 +308,77 @@ impl Obj {
// Common functions // Common functions
// //
pub(crate) fn to_str(vm: &mut Vm, state: FunctionState) -> FunctionResult { pub(crate) fn to_str(vm: &mut Vm) -> ObjP {
match state {
FunctionState::Begin => {
let this = vm.frame_stack()[0].clone(); let this = vm.frame_stack()[0].clone();
let method = this let method = this
.borrow() .borrow()
.get_vtable_attr(this.clone(), "to_repr") .get_vtable_attr(this.clone(), "to_repr")
.clone() .clone()
.expect("no to_repr"); .expect("no to_repr");
vm.push(method.clone()); vm.call(method, &[])
method.borrow().call(vm, 0);
FunctionResult::Yield(0)
}
FunctionState::Resume(0) => FunctionResult::Return,
_ => unreachable!(),
}
} }
pub(crate) fn to_repr(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn to_repr(vm: &mut Vm) -> ObjP {
Str::create(format!("{:?}", vm.frame_stack()[0].borrow())).into() Str::create(format!("{:?}", vm.frame_stack()[0].borrow()))
} }
pub(crate) fn to_bool(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn to_bool(vm: &mut Vm) -> ObjP {
Bool::create(vm.frame_stack()[0].borrow().is_truthy()).into() Bool::create(vm.frame_stack()[0].borrow().is_truthy())
} }
// //
// Operators // Operators
// //
pub(crate) fn and(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn and(vm: &mut Vm) -> ObjP {
let lhs = vm.frame_stack()[0].borrow(); let lhs = vm.frame_stack()[0].borrow();
let rhs = vm.frame_stack()[1].borrow(); let rhs = vm.frame_stack()[1].borrow();
let result = lhs.is_truthy() && rhs.is_truthy(); let result = lhs.is_truthy() && rhs.is_truthy();
Bool::create(result).into() Bool::create(result)
} }
pub(crate) fn or(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn or(vm: &mut Vm) -> ObjP {
let lhs = vm.frame_stack()[0].borrow(); let lhs = vm.frame_stack()[0].borrow();
let rhs = vm.frame_stack()[1].borrow(); let rhs = vm.frame_stack()[1].borrow();
let result = lhs.is_truthy() || rhs.is_truthy(); let result = lhs.is_truthy() || rhs.is_truthy();
Bool::create(result).into() Bool::create(result)
} }
pub(crate) fn ne(vm: &mut Vm, state: FunctionState) -> FunctionResult { pub(crate) fn ne(vm: &mut Vm) -> ObjP {
match state { let this = vm.frame_stack()[0].clone();
FunctionState::Begin => { let method = this
// we actually want to be calling the lhs's eq function and then negate it
let lhs = vm.frame_stack()[0].clone();
let method = lhs
.borrow() .borrow()
.get_vtable_attr(lhs.clone(), "__eq__") .get_vtable_attr(this.clone(), "__eq__")
.expect("no __eq__"); .expect("no __eq__");
let rhs = vm.frame_stack()[1].clone(); let rhs = vm.frame_stack()[1].clone();
vm.push(rhs); let result = vm.call(method, &[rhs]);
method.borrow().call(vm, 1); let result_borrowed = result.borrow();
FunctionResult::Yield(0) Bool::create(!result_borrowed.is_truthy())
}
FunctionState::Resume(0) => {
let result = !vm.peek().borrow().is_truthy();
Bool::create(result).into()
}
_ => {
unreachable!()
}
}
} }
pub(crate) fn eq(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn eq(vm: &mut Vm) -> ObjP {
let lhs = vm.frame_stack()[0].borrow(); let lhs = vm.frame_stack()[0].borrow();
let rhs = vm.frame_stack()[1].borrow(); let rhs = vm.frame_stack()[1].borrow();
let equals = lhs.equals(&*rhs); let equals = lhs.equals(&*rhs);
Bool::create(equals).into() Bool::create(equals)
} }
pub(crate) fn not(vm: &mut Vm, state: FunctionState) -> FunctionResult { pub(crate) fn not(vm: &mut Vm) -> ObjP {
match state {
FunctionState::Begin => {
let obj = vm.peek(); let obj = vm.peek();
let method = obj let method = obj
.borrow() .borrow()
.get_vtable_attr(obj.clone(), "to_bool") .get_vtable_attr(obj.clone(), "to_bool")
.expect("no to_bool"); .expect("no to_bool");
vm.push(method.clone()); let result = vm.call(method, &[]);
method.borrow().call(vm, 0); let result_borrowed = result.borrow();
FunctionResult::Yield(0) Bool::create(!result_borrowed.is_truthy())
}
FunctionState::Resume(0) => {
let value = vm.peek().borrow().is_truthy();
Bool::create(!value).into()
}
_ => unreachable!(),
}
} }
// //
// Not implemented // Not implemented
// //
pub(crate) fn not_implemented_un(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn not_implemented_un(vm: &mut Vm) -> ObjP {
let fname = &vm.frame().name; let fname = &vm.frame().name;
// TODO Obj::not_implemented_un - throw an exception of some kind for not // TODO Obj::not_implemented_un - throw an exception of some kind for not
// implemented/not available errors on unary operators // implemented/not available errors on unary operators
@@ -420,7 +390,7 @@ impl Obj {
) )
} }
pub(crate) fn not_implemented_bin(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn not_implemented_bin(vm: &mut Vm) -> ObjP {
// TODO Obj::not_implemented_un - throw an exception of some kind for not // TODO Obj::not_implemented_un - throw an exception of some kind for not
// implemented/not available errors on unary operators // implemented/not available errors on unary operators
// BLOCKED-ON: exceptions // BLOCKED-ON: exceptions
@@ -432,29 +402,19 @@ impl Obj {
// Do call // Do call
// //
pub(crate) fn do_call(vm: &mut Vm, state: FunctionState) -> FunctionResult { pub(crate) fn do_call(vm: &mut Vm) -> ObjP {
let ty = vm.frame_stack()[0].clone(); let ty = vm.frame_stack()[0].clone();
match state {
FunctionState::Begin => {
let argc = vm.frame_stack().len() - 1; let argc = vm.frame_stack().len() - 1;
let mut obj = Obj::new(); let mut obj = Obj::new();
obj.set_attr("__ty__", ty.clone()); obj.set_attr("__ty__", ty.clone());
obj.instantiate(); obj.instantiate();
let obj_ptr = make_ptr(obj); let obj_ptr = make_ptr(obj);
let init = obj_ptr let init = obj_ptr
.borrow() .borrow()
.get_vtable_attr(obj_ptr.clone(), "__init__") .get_vtable_attr(obj_ptr.clone(), "__init__")
.expect("no __init__"); .expect("no __init__");
// check arity against __init__ function
// TODO Obj::do_call - need to throw an exception when __init__ arity does not
// match what was passed to __call__
// Also need to throw an exception when __init__ is not a
// function or doesn't have an arity
// BLOCKED-ON: exceptions
if let Some(arity) = init.borrow().arity() { if let Some(arity) = init.borrow().arity() {
if argc != arity as usize { if argc != arity as usize {
todo!( todo!(
@@ -466,29 +426,14 @@ impl Obj {
todo!("TODO - throw an exception when __init__ member does not have an arity or is not a function"); todo!("TODO - throw an exception when __init__ member does not have an arity or is not a function");
} }
vm.push(obj_ptr); let argv = Vec::from(&vm.frame_stack()[1..]);
vm.push(init.clone()); vm.call(init, &argv);
// duplicate arguments that were pushed to the frame obj_ptr
for i in 0..argc {
let arg = vm.frame_stack()[i + 1].clone();
vm.push(arg);
} }
init.borrow().call(vm, argc as Argc); pub(crate) fn init(_vm: &mut Vm) -> ObjP {
FunctionResult::Yield(0)
}
FunctionState::Resume(0) => {
vm.pop();
FunctionResult::Return
}
_ => unreachable!(),
}
}
pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult {
// no-op // no-op
Nil::create().into() Nil::create()
} }
} }
@@ -538,12 +483,12 @@ impl Object for Nil {
// //
impl Nil { impl Nil {
pub(crate) fn do_call(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn do_call(_vm: &mut Vm) -> ObjP {
FunctionResult::ReturnPush(Nil::create()) Nil::create()
} }
pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn init(_vm: &mut Vm) -> ObjP {
FunctionResult::ReturnPush(Nil::create()) Nil::create()
} }
} }

View File

@@ -58,19 +58,19 @@ impl Object for Bool {
impl Bool { impl Bool {
impl_do_call!(to_bool); impl_do_call!(to_bool);
pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn init(_vm: &mut Vm) -> ObjP {
// This is a no-op. We don't want the user-exposed `__init__` function to do anything, // This is a no-op. We don't want the user-exposed `__init__` function to do anything,
// instantiation is done in the `__call__` function. // instantiation is done in the `__call__` function.
FunctionResult::ReturnPush(Nil::create()) Nil::create()
} }
pub(crate) fn to_int(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn to_int(vm: &mut Vm) -> ObjP {
let bool_value = with_obj_downcast(vm.frame_stack()[0].clone(), Bool::bool_value); let bool_value = with_obj_downcast(vm.frame_stack()[0].clone(), Bool::bool_value);
Int::create(bool_value as i64).into() Int::create(bool_value as i64)
} }
pub(crate) fn to_float(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn to_float(vm: &mut Vm) -> ObjP {
let bool_value = with_obj_downcast(vm.frame_stack()[0].clone(), Bool::bool_value); let bool_value = with_obj_downcast(vm.frame_stack()[0].clone(), Bool::bool_value);
Float::create(bool_value as i64 as f64).into() Float::create(bool_value as i64 as f64)
} }
} }

View File

@@ -68,7 +68,7 @@ impl Object for Float {
macro_rules! float_bin_op_math { macro_rules! float_bin_op_math {
($function:ident, $op:tt) => { ($function:ident, $op:tt) => {
pub(crate) fn $function(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn $function(vm: &mut Vm) -> ObjP {
let lhs = vm.frame_stack()[0].clone(); let lhs = vm.frame_stack()[0].clone();
let rhs = vm.frame_stack()[1].clone(); let rhs = vm.frame_stack()[1].clone();
@@ -86,14 +86,14 @@ macro_rules! float_bin_op_math {
rhs.borrow().ty_name() rhs.borrow().ty_name()
) )
}; };
result.into() result
} }
} }
} }
macro_rules! float_bin_op_logical { macro_rules! float_bin_op_logical {
($function:ident, $op:tt) => { ($function:ident, $op:tt) => {
pub(crate) fn $function(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn $function(vm: &mut Vm) -> ObjP {
let lhs = vm.frame_stack()[0].clone(); let lhs = vm.frame_stack()[0].clone();
let rhs = vm.frame_stack()[1].clone(); let rhs = vm.frame_stack()[1].clone();
@@ -111,7 +111,7 @@ macro_rules! float_bin_op_logical {
rhs.borrow().ty_name() rhs.borrow().ty_name()
) )
}; };
result.into() result
} }
} }
} }
@@ -119,19 +119,19 @@ macro_rules! float_bin_op_logical {
impl Float { impl Float {
impl_do_call!(to_float); impl_do_call!(to_float);
pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn init(_vm: &mut Vm) -> ObjP {
// This is a no-op. We don't want the user-exposed `__init__` function to do anything, // This is a no-op. We don't want the user-exposed `__init__` function to do anything,
// instantiation is done in the `__call__` function. // instantiation is done in the `__call__` function.
FunctionResult::ReturnPush(Nil::create()) Nil::create()
} }
pub(crate) fn to_int(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn to_int(vm: &mut Vm) -> ObjP {
let float_value = with_obj_downcast(vm.frame_stack()[0].clone(), Float::float_value); let float_value = with_obj_downcast(vm.frame_stack()[0].clone(), Float::float_value);
Int::create(float_value as i64).into() Int::create(float_value as i64)
} }
pub(crate) fn to_float(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn to_float(vm: &mut Vm) -> ObjP {
FunctionResult::Return vm.peek()
} }
float_bin_op_math!(add, +); float_bin_op_math!(add, +);
@@ -147,15 +147,15 @@ impl Float {
float_bin_op_logical!(lt, <); float_bin_op_logical!(lt, <);
float_bin_op_logical!(le, <=); float_bin_op_logical!(le, <=);
pub(crate) fn pos(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn pos(vm: &mut Vm) -> ObjP {
let lhs = vm.frame_stack()[0].clone(); let lhs = vm.frame_stack()[0].clone();
let value = with_obj_downcast(lhs, Float::float_value); let value = with_obj_downcast(lhs, Float::float_value);
Float::create(value.abs()).into() Float::create(value.abs())
} }
pub(crate) fn neg(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn neg(vm: &mut Vm) -> ObjP {
let lhs = vm.frame_stack()[0].clone(); let lhs = vm.frame_stack()[0].clone();
let value = with_obj_downcast(lhs, Float::float_value); let value = with_obj_downcast(lhs, Float::float_value);
Float::create(-value).into() Float::create(-value)
} }
} }

View File

@@ -7,51 +7,11 @@ use crate::obj::macros::*;
use crate::obj::prelude::*; use crate::obj::prelude::*;
use crate::vm::{Argc, Chunk, Frame, Function, Vm}; use crate::vm::{Argc, Chunk, Frame, Function, Vm};
////////////////////////////////////////////////////////////////////////////////
// FunctionResult, FunctionState
////////////////////////////////////////////////////////////////////////////////
/// A result that instructs the VM what to do after a function finishes its execution.
#[derive(Debug)]
pub enum FunctionResult {
/// Take this value and push it on the stack as its return value.
ReturnPush(ObjP),
/// Return value has already been pushed to the stack.
Return,
/// Yield control to the VM with a state marker, and resume execution.
///
/// This means that a new stack frame should have been pushed to the VM and resume execution
/// starting from there.
Yield(usize),
}
impl From<ObjP> for FunctionResult {
fn from(other: ObjP) -> Self {
FunctionResult::ReturnPush(other)
}
}
/// A function's resume state.
///
/// When a builtin function is called, it may need to set up a stack frame for a new function,
/// yielding its control back to the VM. If it does this, then presumably, that function would like
/// to resume its execution where it left off. There's not really a way to capture a native
/// function's execution state in Rust (without tons of unsafe and not-really-worth-it black
/// magic), so instead a builtin function will accept a `FunctionState` value to give it a hint of
/// where it left off.
#[derive(Debug, Clone, Copy)]
pub enum FunctionState {
Begin,
Resume(usize),
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// BuiltinFunction // BuiltinFunction
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
pub type BuiltinFunctionPtr = fn(vm: &mut Vm, function_state: FunctionState) -> FunctionResult; pub type BuiltinFunctionPtr = fn(vm: &mut Vm) -> ObjP;
#[derive(Trace, Finalize)] #[derive(Trace, Finalize)]
pub struct BuiltinFunction { pub struct BuiltinFunction {
@@ -94,7 +54,7 @@ impl Debug for BuiltinFunction {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!( write!(
fmt, fmt,
"<BuiltinFunction {}/{} at 0x{:x}>", "<BuiltinFunction {}/{} at {:#x}>",
self.name(), self.name(),
self.arity().unwrap(), self.arity().unwrap(),
self.function as *const BuiltinFunctionPtr as usize self.function as *const BuiltinFunctionPtr as usize
@@ -110,7 +70,7 @@ impl Object for BuiltinFunction {
fn call(&self, vm: &mut Vm, argc: Argc) { fn call(&self, vm: &mut Vm, argc: Argc) {
let new_frame = Frame::new( let new_frame = Frame::new(
Rc::clone(&self.name), Rc::clone(&self.name),
Function::Builtin(self.function, FunctionState::Begin), Function::Builtin(self.function),
vm.stack().len() - (argc as usize), vm.stack().len() - (argc as usize),
); );
vm.push_frame(new_frame); vm.push_frame(new_frame);
@@ -268,7 +228,7 @@ impl Debug for Method {
}; };
write!( write!(
fmt, fmt,
"<Method {}.{}/{} at {}>", "<Method {}.{}/{} at {:#x}>",
self.self_binding().borrow().ty_name(), self.self_binding().borrow().ty_name(),
function_name, function_name,
self.function.borrow().arity().unwrap(), self.function.borrow().arity().unwrap(),

View File

@@ -63,7 +63,7 @@ impl Object for Int {
macro_rules! int_bin_op_math { macro_rules! int_bin_op_math {
($function:ident, $op:tt) => { ($function:ident, $op:tt) => {
pub(crate) fn $function(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn $function(vm: &mut Vm) -> ObjP {
let lhs = vm.frame_stack()[0].clone(); let lhs = vm.frame_stack()[0].clone();
let rhs = vm.frame_stack()[1].clone(); let rhs = vm.frame_stack()[1].clone();
@@ -81,14 +81,14 @@ macro_rules! int_bin_op_math {
rhs.borrow().ty_name() rhs.borrow().ty_name()
) )
}; };
result.into() result
} }
} }
} }
macro_rules! int_bin_op_logical { macro_rules! int_bin_op_logical {
($function:ident, $op:tt) => { ($function:ident, $op:tt) => {
pub(crate) fn $function(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn $function(vm: &mut Vm) -> ObjP {
let lhs = vm.frame_stack()[0].clone(); let lhs = vm.frame_stack()[0].clone();
let rhs = vm.frame_stack()[1].clone(); let rhs = vm.frame_stack()[1].clone();
@@ -106,33 +106,33 @@ macro_rules! int_bin_op_logical {
rhs.borrow().ty_name() rhs.borrow().ty_name()
) )
}; };
result.into() result
} }
} }
} }
impl Int { impl Int {
pub(crate) fn to_int(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn to_int(vm: &mut Vm) -> ObjP {
FunctionResult::Return vm.peek()
} }
pub(crate) fn to_float(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn to_float(vm: &mut Vm) -> ObjP {
let int_value = with_obj_downcast(vm.frame_stack()[0].clone(), Int::int_value); let int_value = with_obj_downcast(vm.frame_stack()[0].clone(), Int::int_value);
Float::create(int_value as f64).into() Float::create(int_value as f64)
} }
impl_do_call!(to_int); impl_do_call!(to_int);
pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn init(_vm: &mut Vm) -> ObjP {
// This is a no-op. We don't want the user-exposed `__init__` function to do anything, // This is a no-op. We don't want the user-exposed `__init__` function to do anything,
// instantiation is done in the `__call__` function. // instantiation is done in the `__call__` function.
FunctionResult::ReturnPush(Nil::create()) Nil::create()
} }
int_bin_op_math!(add, +); int_bin_op_math!(add, +);
int_bin_op_math!(sub, -); int_bin_op_math!(sub, -);
pub(crate) fn mul(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn mul(vm: &mut Vm) -> ObjP {
// can't bin_op_math this one because it needs the string case // can't bin_op_math this one because it needs the string case
let lhs = vm.frame_stack()[0].clone(); let lhs = vm.frame_stack()[0].clone();
let rhs = vm.frame_stack()[1].clone(); let rhs = vm.frame_stack()[1].clone();
@@ -155,7 +155,7 @@ impl Int {
rhs.borrow().ty_name() rhs.borrow().ty_name()
) )
}; };
result.into() result
} }
// TODO Int::div - handle divide by zero // TODO Int::div - handle divide by zero
@@ -171,15 +171,15 @@ impl Int {
int_bin_op_logical!(lt, <); int_bin_op_logical!(lt, <);
int_bin_op_logical!(le, <=); int_bin_op_logical!(le, <=);
pub(crate) fn pos(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn pos(vm: &mut Vm) -> ObjP {
let lhs = vm.frame_stack()[0].clone(); let lhs = vm.frame_stack()[0].clone();
let value = with_obj_downcast(lhs, Int::int_value); let value = with_obj_downcast(lhs, Int::int_value);
Int::create(value.abs()).into() Int::create(value.abs())
} }
pub(crate) fn neg(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn neg(vm: &mut Vm) -> ObjP {
let lhs = vm.frame_stack()[0].clone(); let lhs = vm.frame_stack()[0].clone();
let value = with_obj_downcast(lhs, Int::int_value); let value = with_obj_downcast(lhs, Int::int_value);
Int::create(-value).into() Int::create(-value)
} }
} }

View File

@@ -61,77 +61,44 @@ impl Object for List {
// List function implementations // List function implementations
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
impl List { impl List {
pub(crate) fn to_repr(vm: &mut Vm, state: FunctionState) -> FunctionResult { pub(crate) fn to_repr(vm: &mut Vm) -> ObjP {
// This function is a bit more complicated than the rest because it needs to effectively let this = vm.frame_stack()[0].clone();
// loop over the elements and call them, without using a loop. Thus, we use a sort-of state let this_borrowed = this.borrow();
// machine with the function state. let list_obj = this_borrowed.as_any().downcast_ref::<List>().unwrap();
// if list_obj.list().len() == 0 {
// When we begin, we check if the list is empty. If that's the case, then we just return return Str::create("[]");
// the "empty list" string. Otherwise, we push two "locals", the string we're building, and
// the current list index, to the stack. Then, we call `to_repr` on the first item in the
// list, and yield execution to the VM with state 0.
//
// When function resumes, we get the return value off the top of the stack, append it to
// the current string, increment the index, and continue, until the string is fully built.
// This function needs to keep track of the string that we're building, plus the current
// index, on the VM stack.
let this_ptr = vm.frame_stack()[0].clone();
let this = this_ptr.borrow();
let this = this.as_any().downcast_ref::<List>().unwrap();
match state {
FunctionState::Begin => {
// empty list, exit early
if this.list().len() == 0 {
return FunctionResult::ReturnPush(Str::create("[]"));
} }
let string = Str::create("["); let mut repr = "[".to_string();
vm.push(string);
let index = Int::create(0);
vm.push(index);
let item = this.list()[0].clone(); // first item
let method = item {
let first = list_obj.list()[0].clone();
let method = first
.borrow() .borrow()
.get_vtable_attr(item.clone(), "to_repr") .get_vtable_attr(first.clone(), "to_repr")
.expect("no to_repr"); .expect("no to_repr");
vm.push(method.clone()); let result = vm.call(method, &[]);
method.borrow().call(vm, 0); repr += &format!("{}", result.borrow());
FunctionResult::Yield(0)
} }
FunctionState::Resume(0) => {
let build_str = vm.frame_stack()[1].clone();
// putting the "1 +" in front so we don't forget that it's there
let index =
1 + with_obj_downcast(vm.frame_stack()[2].clone(), Int::int_value) as usize;
let repr_str = vm.pop();
if index == this.list().len() { // remaining items
// if this is the last item in the list, then we're done for obj in &list_obj.list()[1..] {
let new_str = format!("{}{}]", build_str.borrow(), repr_str.borrow()); repr += ", ";
Str::create(new_str).into() let method = obj
} else {
// otherwise, continue building the string and calling to_repr
let new_str = format!("{}{}, ", build_str.borrow(), repr_str.borrow());
vm.frame_stack_mut()[1] = Str::create(new_str);
vm.frame_stack_mut()[2] = Int::create(index as i64);
let item = this.list()[index].clone();
let method = item
.borrow() .borrow()
.get_vtable_attr(item.clone(), "to_repr") .get_vtable_attr(obj.clone(), "to_repr")
.expect("no to_repr"); .expect("no to_repr");
vm.push(method.clone()); let result = vm.call(method, &[]);
method.borrow().call(vm, 0); repr += &format!("{}", result.borrow());
FunctionResult::Yield(0)
}
}
_ => unreachable!(),
}
} }
pub(crate) fn to_list(vm: &mut Vm, _state: FunctionState) -> FunctionResult { repr += "]";
return Str::create(repr);
}
pub(crate) fn to_list(vm: &mut Vm) -> ObjP {
// create a clone of this list // create a clone of this list
let this = vm.frame_stack()[0].clone(); let this = vm.frame_stack()[0].clone();
let list_items = with_obj_downcast(this, |list: &List| list.list().clone()); let list_items = with_obj_downcast(this, |list: &List| list.list().clone());
@@ -140,13 +107,13 @@ impl List {
impl_do_call!(to_list); impl_do_call!(to_list);
pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn init(_vm: &mut Vm) -> ObjP {
// This is a no-op. We don't want the user-exposed `__init__` function to do anything, // This is a no-op. We don't want the user-exposed `__init__` function to do anything,
// instantiation is done in the `__call__` function. // instantiation is done in the `__call__` function.
FunctionResult::ReturnPush(Nil::create()) Nil::create()
} }
pub(crate) fn index(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn index(vm: &mut Vm) -> ObjP {
let this = vm.frame_stack()[0].clone(); let this = vm.frame_stack()[0].clone();
let index_obj = vm.frame_stack()[1].clone(); let index_obj = vm.frame_stack()[1].clone();
@@ -175,23 +142,23 @@ impl List {
} }
}); });
item.into() item
} }
pub(crate) fn len(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn len(vm: &mut Vm) -> ObjP {
let this = vm.frame_stack()[0].clone(); let this = vm.frame_stack()[0].clone();
let len = with_obj_downcast(this, |list: &List| list.list().len()); let len = with_obj_downcast(this, |list: &List| list.list().len());
Int::create(len as i64).into() Int::create(len as i64)
} }
pub(crate) fn push(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn push(vm: &mut Vm) -> ObjP {
let this = vm.frame_stack()[0].clone(); let this = vm.frame_stack()[0].clone();
let arg = vm.frame_stack()[1].clone(); let arg = vm.frame_stack()[1].clone();
with_obj_downcast_mut(this, |list: &mut List| list.list_mut().push(arg)); with_obj_downcast_mut(this, |list: &mut List| list.list_mut().push(arg));
Nil::create().into() Nil::create()
} }
pub(crate) fn pop(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn pop(vm: &mut Vm) -> ObjP {
let this = vm.frame_stack()[0].clone(); let this = vm.frame_stack()[0].clone();
let last = if let Some(last) = let last = if let Some(last) =
with_obj_downcast_mut(this, |list: &mut List| list.list_mut().pop()) with_obj_downcast_mut(this, |list: &mut List| list.list_mut().pop())
@@ -203,10 +170,10 @@ impl List {
todo!("throw an exception when the list is empty and there is nothing to pop") todo!("throw an exception when the list is empty and there is nothing to pop")
}; };
last.into() last
} }
pub(crate) fn extend(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn extend(vm: &mut Vm) -> ObjP {
let this = vm.frame_stack()[0].clone(); let this = vm.frame_stack()[0].clone();
let arg = vm.frame_stack()[1].clone(); let arg = vm.frame_stack()[1].clone();
@@ -224,6 +191,6 @@ impl List {
list.list_mut().extend(list_extension); list.list_mut().extend(list_extension);
}); });
Nil::create().into() Nil::create()
} }
} }

View File

@@ -52,14 +52,8 @@ macro_rules! impl_create {
macro_rules! impl_do_call { macro_rules! impl_do_call {
($name:ident) => { ($name:ident) => {
pub(crate) fn do_call( pub(crate) fn do_call(vm: &mut $crate::vm::Vm) -> ObjP {
vm: &mut $crate::vm::Vm, let arg = vm.frame_stack().last().unwrap().clone();
state: $crate::obj::function::FunctionState,
) -> $crate::obj::function::FunctionResult {
match state {
$crate::obj::function::FunctionState::Begin => {
// get the top item off the stack and call to_float on it
let arg = vm.peek();
let method = if let Some(method) = let method = if let Some(method) =
arg.borrow().get_vtable_attr(arg.clone(), stringify!($name)) arg.borrow().get_vtable_attr(arg.clone(), stringify!($name))
{ {
@@ -73,17 +67,8 @@ macro_rules! impl_do_call {
arg.borrow().ty_name() arg.borrow().ty_name()
); );
}; };
vm.push(method.clone());
method.borrow().call(vm, 0);
// resume execution vm.call(method, &[])
$crate::obj::function::FunctionResult::Yield(0)
}
$crate::obj::function::FunctionState::Resume(0) => {
$crate::obj::function::FunctionResult::Return
}
_ => unreachable!(),
}
} }
}; };
} }

View File

@@ -63,59 +63,59 @@ impl Object for Str {
impl Str { impl Str {
impl_do_call!(to_str); impl_do_call!(to_str);
pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn init(_vm: &mut Vm) -> ObjP {
// This is a no-op. We don't want the user-exposed `__init__` function to do anything, // This is a no-op. We don't want the user-exposed `__init__` function to do anything,
// instantiation is done in the `__call__` function. // instantiation is done in the `__call__` function.
FunctionResult::ReturnPush(Nil::create()) Nil::create()
} }
pub(crate) fn to_str(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn to_str(vm: &mut Vm) -> ObjP {
// top item of the stack should just be ourselves, so return immediately // top item of the stack should just be ourselves, so return immediately
FunctionResult::Return vm.peek()
} }
pub(crate) fn to_int(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn to_int(vm: &mut Vm) -> ObjP {
let parsed: Result<i64, _> = let parsed: Result<i64, _> =
with_obj_downcast(vm.frame_stack()[0].clone(), |str_inst: &Str| { with_obj_downcast(vm.frame_stack()[0].clone(), |str_inst: &Str| {
str_inst.str_value().parse() str_inst.str_value().parse()
}); });
match parsed { match parsed {
Ok(int) => Int::create(int).into(), Ok(int) => Int::create(int),
// TODO Str::to_int - throw an exception when we fail to parse an integer // TODO Str::to_int - throw an exception when we fail to parse an integer
// BLOCKED-ON - exceptions // BLOCKED-ON - exceptions
Err(e) => todo!("error parsing string to an integer: {}", e), Err(e) => todo!("error parsing string to an integer: {}", e),
} }
} }
pub(crate) fn to_float(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn to_float(vm: &mut Vm) -> ObjP {
let parsed: Result<f64, _> = let parsed: Result<f64, _> =
with_obj_downcast(vm.frame_stack()[0].clone(), |str_inst: &Str| { with_obj_downcast(vm.frame_stack()[0].clone(), |str_inst: &Str| {
str_inst.str_value().parse() str_inst.str_value().parse()
}); });
match parsed { match parsed {
Ok(float) => Float::create(float).into(), Ok(float) => Float::create(float),
// TODO Str::to_int - throw an exception when we fail to parse an integer // TODO Str::to_int - throw an exception when we fail to parse an integer
// BLOCKED-ON - exceptions // BLOCKED-ON - exceptions
Err(e) => todo!("error parsing string to a float: {}", e), Err(e) => todo!("error parsing string to a float: {}", e),
} }
} }
pub(crate) fn to_list(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn to_list(vm: &mut Vm) -> ObjP {
let this = vm.frame_stack()[0].clone(); let this = vm.frame_stack()[0].clone();
let list_items = with_obj_downcast(this, |str: &Str| { let list_items = with_obj_downcast(this, |str: &Str| {
str.str_value().chars().map(|c| Str::create(c)).collect() str.str_value().chars().map(|c| Str::create(c)).collect()
}); });
List::create(list_items).into() List::create(list_items)
} }
pub(crate) fn len(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn len(vm: &mut Vm) -> ObjP {
let len = with_obj_downcast(vm.frame_stack()[0].clone(), |str_inst: &Str| { let len = with_obj_downcast(vm.frame_stack()[0].clone(), |str_inst: &Str| {
str_inst.str_value().len() as i64 str_inst.str_value().len() as i64
}); });
Int::create(len).into() Int::create(len)
} }
pub(crate) fn add(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn add(vm: &mut Vm) -> ObjP {
let lhs = vm.frame_stack()[0].clone(); let lhs = vm.frame_stack()[0].clone();
let rhs = vm.frame_stack()[1].clone(); let rhs = vm.frame_stack()[1].clone();
if !obj_is_inst::<Str>(&rhs) { if !obj_is_inst::<Str>(&rhs) {
@@ -128,10 +128,10 @@ impl Str {
} }
let new = format!("{}{}", lhs.borrow(), rhs.borrow()); let new = format!("{}{}", lhs.borrow(), rhs.borrow());
Str::create(new).into() Str::create(new)
} }
pub(crate) fn mul(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn mul(vm: &mut Vm) -> ObjP {
let lhs = vm.frame_stack()[0].clone(); let lhs = vm.frame_stack()[0].clone();
let rhs = vm.frame_stack()[1].clone(); let rhs = vm.frame_stack()[1].clone();
let repeat_count = if let Some(int_inst) = rhs.borrow().as_any().downcast_ref::<Int>() { let repeat_count = if let Some(int_inst) = rhs.borrow().as_any().downcast_ref::<Int>() {
@@ -146,10 +146,10 @@ impl Str {
}; };
let repeat_count = repeat_count.max(0) as usize; let repeat_count = repeat_count.max(0) as usize;
let new = format!("{}", lhs.borrow()).repeat(repeat_count); let new = format!("{}", lhs.borrow()).repeat(repeat_count);
Str::create(new).into() Str::create(new)
} }
pub(crate) fn index(vm: &mut Vm, _state: FunctionState) -> FunctionResult { pub(crate) fn index(vm: &mut Vm) -> ObjP {
let this = vm.frame_stack()[0].clone(); let this = vm.frame_stack()[0].clone();
let index_obj = vm.frame_stack()[1].clone(); let index_obj = vm.frame_stack()[1].clone();

View File

@@ -91,6 +91,7 @@ impl Object for Ty {
.vtable .vtable
.get("__call__") .get("__call__")
.expect("Why does a type not have a __call__ member?"); .expect("Why does a type not have a __call__ member?");
function.borrow().call(vm, argc + 1); function.borrow().call(vm, argc + 1);
} }

View File

@@ -71,7 +71,7 @@ pub struct Chunk {
pub(crate) enum Function { pub(crate) enum Function {
Chunk(Rc<Chunk>), Chunk(Rc<Chunk>),
Module(Rc<Chunk>), Module(Rc<Chunk>),
Builtin(BuiltinFunctionPtr, FunctionState), Builtin(BuiltinFunctionPtr),
} }
#[derive(Debug)] #[derive(Debug)]
@@ -158,7 +158,7 @@ impl<'c> Vm<'c> {
pub fn chunk(&self) -> Option<&Chunk> { pub fn chunk(&self) -> Option<&Chunk> {
match &self.frame().function { match &self.frame().function {
Function::Chunk(chunk) | Function::Module(chunk) => Some(chunk), Function::Chunk(chunk) | Function::Module(chunk) => Some(chunk),
Function::Builtin(_, _) => None, Function::Builtin(_) => None,
} }
} }
@@ -278,35 +278,12 @@ impl<'c> Vm<'c> {
ip += 1; ip += 1;
op op
} }
Function::Builtin(function, state) => { Function::Builtin(function) => {
// keep track of where the current frame index is in case we need to yield // keep track of where the current frame index is in case we need to yield
let frame_index = self.frames.len() - 1; let result = (function)(self);
let result = (function)(self, *state); self.push(result);
match result {
FunctionResult::ReturnPush(value) => {
// push value to the stack and let the VM handle return protocols
self.push(value);
Op::Return Op::Return
} }
// value is already on top of the stack, let the VM handle return protocols
FunctionResult::Return => Op::Return,
// new stack frame has been pushed, yield control while keeping track of the
// old state
FunctionResult::Yield(resume_state) => {
// update the current state
if let Function::Builtin(_function, ref mut state) =
&mut self.frames[frame_index].function
{
*state = FunctionState::Resume(resume_state)
} else {
panic!("function stack got really messed up - function stack was changed under us");
}
// inject a no-op so the VM can load a new instruction or dispatch a new
// instruction.
Op::Nop
}
}
}
}; };
self.set_ip(ip); self.set_ip(ip);
op op
@@ -327,7 +304,17 @@ impl<'c> Vm<'c> {
self.stack.push(value); self.stack.push(value);
} }
pub fn run(&mut self) { pub fn call(&mut self, fun: ObjP, argv: &[ObjP]) -> ObjP {
self.push(fun.clone());
for arg in argv {
self.push(arg.clone());
}
fun.borrow().call(self, argv.len() as Argc);
self.resume();
self.pop()
}
pub fn resume(&mut self) {
loop { loop {
let op = self.dispatch(); let op = self.dispatch();
@@ -450,6 +437,10 @@ impl<'c> Vm<'c> {
// also pop the function object off of the stack // also pop the function object off of the stack
self.stack.pop(); self.stack.pop();
self.push(return_value); self.push(return_value);
if matches!(self.frame().function, Function::Builtin(_)) {
return;
}
} }
Op::CloseOver { depth, slot } => { Op::CloseOver { depth, slot } => {
// since we're closing over a value, and functions ultimately come from // since we're closing over a value, and functions ultimately come from