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, } impl List { pub fn new(list: Vec) -> Self { List { base: Default::default(), list, } } pub fn list(&self) -> &Vec { &self.list } pub fn list_mut(&mut self) -> &mut Vec { &mut self.list } impl_create!(list: Vec); } 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::() { 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::().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::() { 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) } 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() } 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) } }