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
|
// 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!(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(())
|
||||||
}
|
}
|
||||||
|
|||||||
127
src/obj.rs
127
src/obj.rs
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
111
src/obj/list.rs
111
src/obj/list.rs
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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!(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
49
src/vm.rs
49
src/vm.rs
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user