2024-09-30 16:33:58 -07:00
|
|
|
use std::fmt::{self, Debug, Display};
|
|
|
|
|
|
|
|
|
|
use gc::{Finalize, Trace};
|
|
|
|
|
|
|
|
|
|
use crate::obj::macros::*;
|
|
|
|
|
use crate::obj::prelude::*;
|
|
|
|
|
use crate::obj::BaseObj;
|
|
|
|
|
use crate::vm::Vm;
|
|
|
|
|
|
|
|
|
|
#[derive(Trace, Finalize)]
|
|
|
|
|
pub struct List {
|
|
|
|
|
base: BaseObj,
|
|
|
|
|
list: Vec<ObjP>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl List {
|
|
|
|
|
pub fn new(list: Vec<ObjP>) -> Self {
|
|
|
|
|
List {
|
|
|
|
|
base: Default::default(),
|
|
|
|
|
list,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn list(&self) -> &Vec<ObjP> {
|
|
|
|
|
&self.list
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn list_mut(&mut self) -> &mut Vec<ObjP> {
|
|
|
|
|
&mut self.list
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl_create!(list: Vec<ObjP>);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Display for List {
|
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
Debug::fmt(self, fmt)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Debug for List {
|
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
// NOTE : this function should not actually be called by the runtime, since we should be
|
|
|
|
|
// calling `to_repr` on all children in the list, which requires VM context.
|
|
|
|
|
write!(fmt, "[")?;
|
|
|
|
|
for i in 0..self.list.len() - 1 {
|
|
|
|
|
write!(fmt, "{:?}, ", self.list[i].borrow())?;
|
|
|
|
|
}
|
|
|
|
|
if let Some(last) = self.list.last() {
|
|
|
|
|
write!(fmt, "{}]", last.borrow())
|
|
|
|
|
} else {
|
|
|
|
|
write!(fmt, "]")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Object for List {
|
|
|
|
|
fn equals(&self, other: &dyn Object) -> bool {
|
|
|
|
|
if let Some(other) = other.as_any().downcast_ref::<List>() {
|
|
|
|
|
self.list.len() == other.list.len()
|
|
|
|
|
&& self
|
|
|
|
|
.list
|
|
|
|
|
.iter()
|
|
|
|
|
.zip(other.list.iter())
|
|
|
|
|
.all(|(me, you)| me.borrow().equals(&*you.borrow()))
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl_base_obj!(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("[]"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let string = Str::create("[");
|
|
|
|
|
vm.push(string);
|
|
|
|
|
let index = Int::create(0);
|
|
|
|
|
vm.push(index);
|
|
|
|
|
|
|
|
|
|
let item = this.list()[0].clone();
|
|
|
|
|
let method = item
|
|
|
|
|
.borrow()
|
|
|
|
|
.get_vtable_attr(item.clone(), "to_repr")
|
|
|
|
|
.expect("no to_repr");
|
|
|
|
|
vm.push(method.clone());
|
|
|
|
|
method.borrow().call(vm, 0);
|
|
|
|
|
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() {
|
|
|
|
|
// if this is the last item in the list, then we're done
|
|
|
|
|
let new_str = format!("{}{}]", build_str.borrow(), repr_str.borrow());
|
|
|
|
|
FunctionResult::ReturnPush(Str::create(new_str))
|
|
|
|
|
} 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()
|
|
|
|
|
.get_vtable_attr(item.clone(), "to_repr")
|
|
|
|
|
.expect("no to_repr");
|
|
|
|
|
vm.push(method.clone());
|
|
|
|
|
method.borrow().call(vm, 0);
|
|
|
|
|
FunctionResult::Yield(0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => unreachable!(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl_do_call!(to_list);
|
|
|
|
|
|
|
|
|
|
pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult {
|
|
|
|
|
// 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())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn index(vm: &mut Vm, _state: FunctionState) -> FunctionResult {
|
|
|
|
|
let this = vm.frame_stack()[0].clone();
|
|
|
|
|
let index_obj = vm.frame_stack()[1].clone();
|
|
|
|
|
|
|
|
|
|
let index = if let Some(index_obj) = index_obj.borrow().as_any().downcast_ref::<Int>() {
|
|
|
|
|
index_obj.int_value()
|
|
|
|
|
} else {
|
|
|
|
|
// TODO List::index - throw an exception when the index object is not an integer
|
|
|
|
|
// BLOCKED-ON: exceptions
|
|
|
|
|
todo!("throw an exception when the index object is not an integer")
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let item = with_obj_downcast(this, |list: &List| {
|
|
|
|
|
let mut index = index;
|
|
|
|
|
|
|
|
|
|
// backtrack index lookup
|
|
|
|
|
if index < 0 {
|
|
|
|
|
index = list.list().len() as i64 + index;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if index < 0 || index as usize >= list.list().len() {
|
|
|
|
|
// TODO List::index - throw an exception when the index is out of range
|
|
|
|
|
// BLOCKED-ON: exceptions
|
|
|
|
|
todo!("throw an exception when the list index is out of range")
|
|
|
|
|
} else {
|
|
|
|
|
list.list()[index as usize].clone()
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
FunctionResult::ReturnPush(item)
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-30 17:46:03 -07:00
|
|
|
pub(crate) fn len(vm: &mut Vm, _state: FunctionState) -> FunctionResult {
|
|
|
|
|
let this = vm.frame_stack()[0].clone();
|
|
|
|
|
let len = with_obj_downcast(this, |list: &List| list.list().len());
|
|
|
|
|
Int::create(len as i64).into()
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-30 16:33:58 -07:00
|
|
|
pub(crate) fn push(vm: &mut Vm, _state: FunctionState) -> FunctionResult {
|
|
|
|
|
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));
|
|
|
|
|
FunctionResult::ReturnPush(Nil::create())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn pop(vm: &mut Vm, _state: FunctionState) -> FunctionResult {
|
|
|
|
|
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())
|
|
|
|
|
{
|
|
|
|
|
last
|
|
|
|
|
} else {
|
|
|
|
|
// TODO List::pop - throw an exception when the list object is empty
|
|
|
|
|
// BLOCKED-ON: exceptions
|
|
|
|
|
todo!("throw an exception when the list is empty and there is nothing to pop")
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
FunctionResult::ReturnPush(last)
|
|
|
|
|
}
|
|
|
|
|
}
|