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:
@@ -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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub(crate) fn println(vm: &mut Vm, state: FunctionState) -> FunctionResult {
|
||||
match state {
|
||||
FunctionState::Begin => {
|
||||
let obj = vm.peek();
|
||||
pub(crate) fn println(vm: &mut Vm) -> ObjP {
|
||||
let obj = vm.frame_stack()[0].clone();
|
||||
let method = obj
|
||||
.borrow()
|
||||
.get_vtable_attr(obj.clone(), "to_str")
|
||||
.expect("no to_str");
|
||||
vm.push(method.clone());
|
||||
method.borrow().call(vm, 0);
|
||||
FunctionResult::Yield(0)
|
||||
}
|
||||
FunctionState::Resume(0) => {
|
||||
println!("{}", vm.frame_stack().last().unwrap().borrow());
|
||||
Nil::create().into()
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let result = vm.call(method, &[]);
|
||||
println!("{}", result.borrow());
|
||||
Nil::create()
|
||||
}
|
||||
|
||||
pub(crate) fn print(vm: &mut Vm, state: FunctionState) -> FunctionResult {
|
||||
match state {
|
||||
FunctionState::Begin => {
|
||||
let obj = vm.peek();
|
||||
pub(crate) fn print(vm: &mut Vm) -> ObjP {
|
||||
let obj = vm.frame_stack()[0].clone();
|
||||
let method = obj
|
||||
.borrow()
|
||||
.get_vtable_attr(obj.clone(), "to_str")
|
||||
.expect("no to_str");
|
||||
vm.push(method.clone());
|
||||
method.borrow().call(vm, 0);
|
||||
FunctionResult::Yield(0)
|
||||
}
|
||||
FunctionState::Resume(0) => {
|
||||
print!("{}", vm.frame_stack().last().unwrap().borrow());
|
||||
Nil::create().into()
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let result = vm.call(method, &[]);
|
||||
print!("{}", result.borrow());
|
||||
Nil::create()
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ fn main() {
|
||||
// VM needs the module to be on top of the stack when it executes
|
||||
vm.push(module.clone());
|
||||
vm.enter_module(module);
|
||||
vm.run();
|
||||
vm.resume();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
127
src/obj.rs
127
src/obj.rs
@@ -48,7 +48,7 @@ pub mod prelude {
|
||||
pub use crate::obj::{Nil, Obj};
|
||||
|
||||
// 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
|
||||
@@ -308,107 +308,77 @@ impl Obj {
|
||||
// Common functions
|
||||
//
|
||||
|
||||
pub(crate) fn to_str(vm: &mut Vm, state: FunctionState) -> FunctionResult {
|
||||
match state {
|
||||
FunctionState::Begin => {
|
||||
pub(crate) fn to_str(vm: &mut Vm) -> ObjP {
|
||||
let this = vm.frame_stack()[0].clone();
|
||||
let method = this
|
||||
.borrow()
|
||||
.get_vtable_attr(this.clone(), "to_repr")
|
||||
.clone()
|
||||
.expect("no to_repr");
|
||||
vm.push(method.clone());
|
||||
method.borrow().call(vm, 0);
|
||||
FunctionResult::Yield(0)
|
||||
}
|
||||
FunctionState::Resume(0) => FunctionResult::Return,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
vm.call(method, &[])
|
||||
}
|
||||
|
||||
pub(crate) fn to_repr(vm: &mut Vm, _state: FunctionState) -> FunctionResult {
|
||||
Str::create(format!("{:?}", vm.frame_stack()[0].borrow())).into()
|
||||
pub(crate) fn to_repr(vm: &mut Vm) -> ObjP {
|
||||
Str::create(format!("{:?}", vm.frame_stack()[0].borrow()))
|
||||
}
|
||||
|
||||
pub(crate) fn to_bool(vm: &mut Vm, _state: FunctionState) -> FunctionResult {
|
||||
Bool::create(vm.frame_stack()[0].borrow().is_truthy()).into()
|
||||
pub(crate) fn to_bool(vm: &mut Vm) -> ObjP {
|
||||
Bool::create(vm.frame_stack()[0].borrow().is_truthy())
|
||||
}
|
||||
|
||||
//
|
||||
// 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 rhs = vm.frame_stack()[1].borrow();
|
||||
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 rhs = vm.frame_stack()[1].borrow();
|
||||
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 {
|
||||
match state {
|
||||
FunctionState::Begin => {
|
||||
// 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
|
||||
pub(crate) fn ne(vm: &mut Vm) -> ObjP {
|
||||
let this = vm.frame_stack()[0].clone();
|
||||
let method = this
|
||||
.borrow()
|
||||
.get_vtable_attr(lhs.clone(), "__eq__")
|
||||
.get_vtable_attr(this.clone(), "__eq__")
|
||||
.expect("no __eq__");
|
||||
|
||||
let rhs = vm.frame_stack()[1].clone();
|
||||
vm.push(rhs);
|
||||
method.borrow().call(vm, 1);
|
||||
FunctionResult::Yield(0)
|
||||
}
|
||||
FunctionState::Resume(0) => {
|
||||
let result = !vm.peek().borrow().is_truthy();
|
||||
Bool::create(result).into()
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
let result = vm.call(method, &[rhs]);
|
||||
let result_borrowed = result.borrow();
|
||||
Bool::create(!result_borrowed.is_truthy())
|
||||
}
|
||||
|
||||
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 rhs = vm.frame_stack()[1].borrow();
|
||||
let equals = lhs.equals(&*rhs);
|
||||
Bool::create(equals).into()
|
||||
Bool::create(equals)
|
||||
}
|
||||
|
||||
pub(crate) fn not(vm: &mut Vm, state: FunctionState) -> FunctionResult {
|
||||
match state {
|
||||
FunctionState::Begin => {
|
||||
pub(crate) fn not(vm: &mut Vm) -> ObjP {
|
||||
let obj = vm.peek();
|
||||
let method = obj
|
||||
.borrow()
|
||||
.get_vtable_attr(obj.clone(), "to_bool")
|
||||
.expect("no to_bool");
|
||||
vm.push(method.clone());
|
||||
method.borrow().call(vm, 0);
|
||||
FunctionResult::Yield(0)
|
||||
}
|
||||
FunctionState::Resume(0) => {
|
||||
let value = vm.peek().borrow().is_truthy();
|
||||
Bool::create(!value).into()
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let result = vm.call(method, &[]);
|
||||
let result_borrowed = result.borrow();
|
||||
Bool::create(!result_borrowed.is_truthy())
|
||||
}
|
||||
|
||||
//
|
||||
// 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;
|
||||
// TODO Obj::not_implemented_un - throw an exception of some kind for not
|
||||
// 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
|
||||
// implemented/not available errors on unary operators
|
||||
// BLOCKED-ON: exceptions
|
||||
@@ -432,29 +402,19 @@ impl Obj {
|
||||
// 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();
|
||||
|
||||
match state {
|
||||
FunctionState::Begin => {
|
||||
let argc = vm.frame_stack().len() - 1;
|
||||
|
||||
let mut obj = Obj::new();
|
||||
obj.set_attr("__ty__", ty.clone());
|
||||
obj.instantiate();
|
||||
|
||||
let obj_ptr = make_ptr(obj);
|
||||
let init = obj_ptr
|
||||
.borrow()
|
||||
.get_vtable_attr(obj_ptr.clone(), "__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 argc != arity as usize {
|
||||
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");
|
||||
}
|
||||
|
||||
vm.push(obj_ptr);
|
||||
vm.push(init.clone());
|
||||
// duplicate arguments that were pushed to the frame
|
||||
for i in 0..argc {
|
||||
let arg = vm.frame_stack()[i + 1].clone();
|
||||
vm.push(arg);
|
||||
let argv = Vec::from(&vm.frame_stack()[1..]);
|
||||
vm.call(init, &argv);
|
||||
obj_ptr
|
||||
}
|
||||
|
||||
init.borrow().call(vm, argc as Argc);
|
||||
|
||||
FunctionResult::Yield(0)
|
||||
}
|
||||
FunctionState::Resume(0) => {
|
||||
vm.pop();
|
||||
FunctionResult::Return
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult {
|
||||
pub(crate) fn init(_vm: &mut Vm) -> ObjP {
|
||||
// no-op
|
||||
Nil::create().into()
|
||||
Nil::create()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,12 +483,12 @@ impl Object for Nil {
|
||||
//
|
||||
|
||||
impl Nil {
|
||||
pub(crate) fn do_call(_vm: &mut Vm, _state: FunctionState) -> FunctionResult {
|
||||
FunctionResult::ReturnPush(Nil::create())
|
||||
pub(crate) fn do_call(_vm: &mut Vm) -> ObjP {
|
||||
Nil::create()
|
||||
}
|
||||
|
||||
pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult {
|
||||
FunctionResult::ReturnPush(Nil::create())
|
||||
pub(crate) fn init(_vm: &mut Vm) -> ObjP {
|
||||
Nil::create()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,19 +58,19 @@ impl Object for Bool {
|
||||
impl 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,
|
||||
// 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);
|
||||
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);
|
||||
Float::create(bool_value as i64 as f64).into()
|
||||
Float::create(bool_value as i64 as f64)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ impl Object for Float {
|
||||
|
||||
macro_rules! float_bin_op_math {
|
||||
($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 rhs = vm.frame_stack()[1].clone();
|
||||
|
||||
@@ -86,14 +86,14 @@ macro_rules! float_bin_op_math {
|
||||
rhs.borrow().ty_name()
|
||||
)
|
||||
};
|
||||
result.into()
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! float_bin_op_logical {
|
||||
($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 rhs = vm.frame_stack()[1].clone();
|
||||
|
||||
@@ -111,7 +111,7 @@ macro_rules! float_bin_op_logical {
|
||||
rhs.borrow().ty_name()
|
||||
)
|
||||
};
|
||||
result.into()
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,19 +119,19 @@ macro_rules! float_bin_op_logical {
|
||||
impl 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,
|
||||
// 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);
|
||||
Int::create(float_value as i64).into()
|
||||
Int::create(float_value as i64)
|
||||
}
|
||||
|
||||
pub(crate) fn to_float(_vm: &mut Vm, _state: FunctionState) -> FunctionResult {
|
||||
FunctionResult::Return
|
||||
pub(crate) fn to_float(vm: &mut Vm) -> ObjP {
|
||||
vm.peek()
|
||||
}
|
||||
|
||||
float_bin_op_math!(add, +);
|
||||
@@ -147,15 +147,15 @@ impl Float {
|
||||
float_bin_op_logical!(lt, <);
|
||||
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 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 value = with_obj_downcast(lhs, Float::float_value);
|
||||
Float::create(-value).into()
|
||||
Float::create(-value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,51 +7,11 @@ use crate::obj::macros::*;
|
||||
use crate::obj::prelude::*;
|
||||
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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
pub type BuiltinFunctionPtr = fn(vm: &mut Vm, function_state: FunctionState) -> FunctionResult;
|
||||
pub type BuiltinFunctionPtr = fn(vm: &mut Vm) -> ObjP;
|
||||
|
||||
#[derive(Trace, Finalize)]
|
||||
pub struct BuiltinFunction {
|
||||
@@ -94,7 +54,7 @@ impl Debug for BuiltinFunction {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
fmt,
|
||||
"<BuiltinFunction {}/{} at 0x{:x}>",
|
||||
"<BuiltinFunction {}/{} at {:#x}>",
|
||||
self.name(),
|
||||
self.arity().unwrap(),
|
||||
self.function as *const BuiltinFunctionPtr as usize
|
||||
@@ -110,7 +70,7 @@ impl Object for BuiltinFunction {
|
||||
fn call(&self, vm: &mut Vm, argc: Argc) {
|
||||
let new_frame = Frame::new(
|
||||
Rc::clone(&self.name),
|
||||
Function::Builtin(self.function, FunctionState::Begin),
|
||||
Function::Builtin(self.function),
|
||||
vm.stack().len() - (argc as usize),
|
||||
);
|
||||
vm.push_frame(new_frame);
|
||||
@@ -268,7 +228,7 @@ impl Debug for Method {
|
||||
};
|
||||
write!(
|
||||
fmt,
|
||||
"<Method {}.{}/{} at {}>",
|
||||
"<Method {}.{}/{} at {:#x}>",
|
||||
self.self_binding().borrow().ty_name(),
|
||||
function_name,
|
||||
self.function.borrow().arity().unwrap(),
|
||||
|
||||
@@ -63,7 +63,7 @@ impl Object for Int {
|
||||
|
||||
macro_rules! int_bin_op_math {
|
||||
($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 rhs = vm.frame_stack()[1].clone();
|
||||
|
||||
@@ -81,14 +81,14 @@ macro_rules! int_bin_op_math {
|
||||
rhs.borrow().ty_name()
|
||||
)
|
||||
};
|
||||
result.into()
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! int_bin_op_logical {
|
||||
($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 rhs = vm.frame_stack()[1].clone();
|
||||
|
||||
@@ -106,33 +106,33 @@ macro_rules! int_bin_op_logical {
|
||||
rhs.borrow().ty_name()
|
||||
)
|
||||
};
|
||||
result.into()
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Int {
|
||||
pub(crate) fn to_int(_vm: &mut Vm, _state: FunctionState) -> FunctionResult {
|
||||
FunctionResult::Return
|
||||
pub(crate) fn to_int(vm: &mut Vm) -> ObjP {
|
||||
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);
|
||||
Float::create(int_value as f64).into()
|
||||
Float::create(int_value as f64)
|
||||
}
|
||||
|
||||
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,
|
||||
// instantiation is done in the `__call__` function.
|
||||
FunctionResult::ReturnPush(Nil::create())
|
||||
Nil::create()
|
||||
}
|
||||
|
||||
int_bin_op_math!(add, +);
|
||||
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
|
||||
let lhs = vm.frame_stack()[0].clone();
|
||||
let rhs = vm.frame_stack()[1].clone();
|
||||
@@ -155,7 +155,7 @@ impl Int {
|
||||
rhs.borrow().ty_name()
|
||||
)
|
||||
};
|
||||
result.into()
|
||||
result
|
||||
}
|
||||
|
||||
// TODO Int::div - handle divide by zero
|
||||
@@ -171,15 +171,15 @@ impl Int {
|
||||
int_bin_op_logical!(lt, <);
|
||||
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 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 value = with_obj_downcast(lhs, Int::int_value);
|
||||
Int::create(-value).into()
|
||||
Int::create(-value)
|
||||
}
|
||||
}
|
||||
|
||||
111
src/obj/list.rs
111
src/obj/list.rs
@@ -61,77 +61,44 @@ impl Object for List {
|
||||
// List function implementations
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
impl List {
|
||||
pub(crate) fn to_repr(vm: &mut Vm, state: FunctionState) -> FunctionResult {
|
||||
// This function is a bit more complicated than the rest because it needs to effectively
|
||||
// loop over the elements and call them, without using a loop. Thus, we use a sort-of state
|
||||
// machine with the function state.
|
||||
//
|
||||
// When we begin, we check if the list is empty. If that's the case, then we just return
|
||||
// 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("[]"));
|
||||
pub(crate) fn to_repr(vm: &mut Vm) -> ObjP {
|
||||
let this = vm.frame_stack()[0].clone();
|
||||
let this_borrowed = this.borrow();
|
||||
let list_obj = this_borrowed.as_any().downcast_ref::<List>().unwrap();
|
||||
if list_obj.list().len() == 0 {
|
||||
return Str::create("[]");
|
||||
}
|
||||
|
||||
let string = Str::create("[");
|
||||
vm.push(string);
|
||||
let index = Int::create(0);
|
||||
vm.push(index);
|
||||
let mut repr = "[".to_string();
|
||||
|
||||
let item = this.list()[0].clone();
|
||||
let method = item
|
||||
// first item
|
||||
{
|
||||
let first = list_obj.list()[0].clone();
|
||||
let method = first
|
||||
.borrow()
|
||||
.get_vtable_attr(item.clone(), "to_repr")
|
||||
.get_vtable_attr(first.clone(), "to_repr")
|
||||
.expect("no to_repr");
|
||||
vm.push(method.clone());
|
||||
method.borrow().call(vm, 0);
|
||||
FunctionResult::Yield(0)
|
||||
let result = vm.call(method, &[]);
|
||||
repr += &format!("{}", result.borrow());
|
||||
}
|
||||
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() {
|
||||
// if this is the last item in the list, then we're done
|
||||
let new_str = format!("{}{}]", build_str.borrow(), repr_str.borrow());
|
||||
Str::create(new_str).into()
|
||||
} 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
|
||||
// remaining items
|
||||
for obj in &list_obj.list()[1..] {
|
||||
repr += ", ";
|
||||
let method = obj
|
||||
.borrow()
|
||||
.get_vtable_attr(item.clone(), "to_repr")
|
||||
.get_vtable_attr(obj.clone(), "to_repr")
|
||||
.expect("no to_repr");
|
||||
vm.push(method.clone());
|
||||
method.borrow().call(vm, 0);
|
||||
FunctionResult::Yield(0)
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let result = vm.call(method, &[]);
|
||||
repr += &format!("{}", result.borrow());
|
||||
}
|
||||
|
||||
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
|
||||
let this = vm.frame_stack()[0].clone();
|
||||
let list_items = with_obj_downcast(this, |list: &List| list.list().clone());
|
||||
@@ -140,13 +107,13 @@ impl 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,
|
||||
// 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 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 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 arg = vm.frame_stack()[1].clone();
|
||||
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 last = if let Some(last) =
|
||||
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")
|
||||
};
|
||||
|
||||
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 arg = vm.frame_stack()[1].clone();
|
||||
|
||||
@@ -224,6 +191,6 @@ impl List {
|
||||
list.list_mut().extend(list_extension);
|
||||
});
|
||||
|
||||
Nil::create().into()
|
||||
Nil::create()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,14 +52,8 @@ macro_rules! impl_create {
|
||||
|
||||
macro_rules! impl_do_call {
|
||||
($name:ident) => {
|
||||
pub(crate) fn do_call(
|
||||
vm: &mut $crate::vm::Vm,
|
||||
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();
|
||||
pub(crate) fn do_call(vm: &mut $crate::vm::Vm) -> ObjP {
|
||||
let arg = vm.frame_stack().last().unwrap().clone();
|
||||
let method = if let Some(method) =
|
||||
arg.borrow().get_vtable_attr(arg.clone(), stringify!($name))
|
||||
{
|
||||
@@ -73,17 +67,8 @@ macro_rules! impl_do_call {
|
||||
arg.borrow().ty_name()
|
||||
);
|
||||
};
|
||||
vm.push(method.clone());
|
||||
method.borrow().call(vm, 0);
|
||||
|
||||
// resume execution
|
||||
$crate::obj::function::FunctionResult::Yield(0)
|
||||
}
|
||||
$crate::obj::function::FunctionState::Resume(0) => {
|
||||
$crate::obj::function::FunctionResult::Return
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
vm.call(method, &[])
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -63,59 +63,59 @@ impl Object for Str {
|
||||
impl 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,
|
||||
// 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
|
||||
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, _> =
|
||||
with_obj_downcast(vm.frame_stack()[0].clone(), |str_inst: &Str| {
|
||||
str_inst.str_value().parse()
|
||||
});
|
||||
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
|
||||
// BLOCKED-ON - exceptions
|
||||
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, _> =
|
||||
with_obj_downcast(vm.frame_stack()[0].clone(), |str_inst: &Str| {
|
||||
str_inst.str_value().parse()
|
||||
});
|
||||
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
|
||||
// BLOCKED-ON - exceptions
|
||||
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 list_items = with_obj_downcast(this, |str: &Str| {
|
||||
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| {
|
||||
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 rhs = vm.frame_stack()[1].clone();
|
||||
if !obj_is_inst::<Str>(&rhs) {
|
||||
@@ -128,10 +128,10 @@ impl Str {
|
||||
}
|
||||
|
||||
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 rhs = vm.frame_stack()[1].clone();
|
||||
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 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 index_obj = vm.frame_stack()[1].clone();
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ impl Object for Ty {
|
||||
.vtable
|
||||
.get("__call__")
|
||||
.expect("Why does a type not have a __call__ member?");
|
||||
|
||||
function.borrow().call(vm, argc + 1);
|
||||
}
|
||||
|
||||
|
||||
49
src/vm.rs
49
src/vm.rs
@@ -71,7 +71,7 @@ pub struct Chunk {
|
||||
pub(crate) enum Function {
|
||||
Chunk(Rc<Chunk>),
|
||||
Module(Rc<Chunk>),
|
||||
Builtin(BuiltinFunctionPtr, FunctionState),
|
||||
Builtin(BuiltinFunctionPtr),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -158,7 +158,7 @@ impl<'c> Vm<'c> {
|
||||
pub fn chunk(&self) -> Option<&Chunk> {
|
||||
match &self.frame().function {
|
||||
Function::Chunk(chunk) | Function::Module(chunk) => Some(chunk),
|
||||
Function::Builtin(_, _) => None,
|
||||
Function::Builtin(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,35 +278,12 @@ impl<'c> Vm<'c> {
|
||||
ip += 1;
|
||||
op
|
||||
}
|
||||
Function::Builtin(function, state) => {
|
||||
Function::Builtin(function) => {
|
||||
// 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, *state);
|
||||
match result {
|
||||
FunctionResult::ReturnPush(value) => {
|
||||
// push value to the stack and let the VM handle return protocols
|
||||
self.push(value);
|
||||
let result = (function)(self);
|
||||
self.push(result);
|
||||
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);
|
||||
op
|
||||
@@ -327,7 +304,17 @@ impl<'c> Vm<'c> {
|
||||
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 {
|
||||
let op = self.dispatch();
|
||||
|
||||
@@ -450,6 +437,10 @@ impl<'c> Vm<'c> {
|
||||
// also pop the function object off of the stack
|
||||
self.stack.pop();
|
||||
self.push(return_value);
|
||||
|
||||
if matches!(self.frame().function, Function::Builtin(_)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Op::CloseOver { depth, slot } => {
|
||||
// since we're closing over a value, and functions ultimately come from
|
||||
|
||||
Reference in New Issue
Block a user