use std::{collections::VecDeque, fmt::Debug};
use arc_gc::{
arc::{GCArc, GCArcWeak},
traceable::GCTraceable,
};
use crate::lambda::runnable::RuntimeError;
use super::object::{OnionObject, OnionObjectCell, OnionStaticObject};
#[derive(Clone)]
pub struct OnionTuple {
pub elements: Box<Vec<OnionObjectCell>>,
}
impl GCTraceable<OnionObjectCell> for OnionTuple {
fn collect(&self, queue: &mut VecDeque<GCArcWeak<OnionObjectCell>>) {
for element in self.elements.as_ref() {
element.collect(queue);
}
}
}
impl Debug for OnionTuple {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.elements.len() {
0 => write!(f, "()"),
1 => write!(f, "({:?},)", self.elements[0]),
_ => {
let elements: Vec<String> =
self.elements.iter().map(|e| format!("{:?}", e)).collect();
write!(f, "({})", elements.join(", "))
}
}
}
}
#[macro_export]
macro_rules! onion_tuple {
($($x:expr),*) => {
OnionTuple::new_static(vec![$($x),*])
};
() => {
};
}
impl OnionTuple {
pub fn new(elements: Vec<OnionObjectCell>) -> Self {
OnionTuple {
elements: elements.into(),
}
}
pub fn new_static(elements: Vec<&OnionStaticObject>) -> OnionStaticObject {
OnionStaticObject::new(OnionObject::Tuple(OnionTuple {
elements: elements
.into_iter()
.map(|e| e.weak().clone().to_cell())
.collect::<Vec<_>>()
.into(),
}))
}
pub fn new_static_no_ref(elements: Vec<OnionStaticObject>) -> OnionStaticObject {
OnionStaticObject::new(OnionObject::Tuple(OnionTuple {
elements: elements
.into_iter()
.map(|e| e.weak().clone().to_cell())
.collect::<Vec<_>>()
.into(),
}))
}
pub fn upgrade(&self, collected: &mut Vec<GCArc<OnionObjectCell>>) {
self.elements.iter().for_each(|e| e.upgrade(collected));
}
pub fn len(&self) -> Result<OnionStaticObject, RuntimeError> {
Ok(OnionStaticObject::new(OnionObject::Integer(
self.elements.len() as i64,
)))
}
pub fn at(&self, index: i64) -> Result<OnionStaticObject, RuntimeError> {
if index < 0 || index >= self.elements.len() as i64 {
return Err(RuntimeError::InvalidOperation(format!(
"Index out of bounds: {}",
index
)));
}
Ok(OnionStaticObject::new(
self.elements[index as usize].try_borrow()?.clone(),
))
}
pub fn with_index<F, R>(&self, index: i64, f: &F) -> Result<R, RuntimeError>
where
F: Fn(&OnionObject) -> Result<R, RuntimeError>,
{
if index < 0 || index >= self.elements.len() as i64 {
return Err(RuntimeError::InvalidOperation(format!(
"Index out of bounds: {}",
index
)));
}
let borrowed = self.elements[index as usize].try_borrow()?;
f(&*borrowed)
}
pub fn with_attribute<F, R>(&self, key: &OnionObject, f: &F) -> Result<R, RuntimeError>
where
F: Fn(&OnionObject) -> Result<R, RuntimeError>,
{
for element in self.elements.as_ref() {
match &*element.try_borrow()? {
OnionObject::Named(named) => {
if named.key.try_borrow()?.equals(key)? {
return f(&*named.value.try_borrow()?);
}
}
OnionObject::Pair(pair) => {
if pair.key.try_borrow()?.equals(key)? {
return f(&*pair.value.try_borrow()?);
}
}
_ => {}
}
}
Err(RuntimeError::InvalidOperation(format!(
"Attribute {:?} not found in tuple",
key
)))
}
pub fn with_attribute_mut<F, R>(&mut self, key: &OnionObject, f: F) -> Result<R, RuntimeError>
where
F: Fn(&mut OnionObject) -> Result<R, RuntimeError>,
{
for element in self.elements.as_mut() {
match &*element.try_borrow()? {
OnionObject::Named(named) => {
if named.key.try_borrow()?.equals(key)? {
return f(&mut *named.value.try_borrow_mut().map_err(|_| {
RuntimeError::InvalidOperation(format!(
"Failed to borrow value for key {:?}",
key
))
})?);
}
}
OnionObject::Pair(pair) => {
if pair.key.try_borrow()?.equals(key)? {
return f(&mut *pair.value.try_borrow_mut().map_err(|_| {
RuntimeError::InvalidOperation(format!(
"Failed to borrow value for key {:?}",
key
))
})?);
}
}
_ => {}
}
}
Err(RuntimeError::InvalidOperation(format!(
"Attribute {:?} not found in tuple",
key
)))
}
pub fn binary_add(&self, other: &OnionObject) -> Result<OnionStaticObject, RuntimeError> {
match other {
OnionObject::Tuple(other_tuple) => {
let mut new_elements = self.elements.clone();
new_elements.extend(other_tuple.elements.as_ref().clone());
Ok(OnionStaticObject::new(OnionObject::Tuple(OnionTuple {
elements: new_elements,
})))
}
_ => Ok(OnionStaticObject::new(OnionObject::Undefined(Some(
format!("Cannot add tuple with {:?}", other),
)))),
}
}
pub fn contains(&self, other: &OnionObject) -> Result<bool, RuntimeError> {
for element in self.elements.as_ref() {
if element.try_borrow()?.equals(other)? {
return Ok(true);
}
}
Ok(false)
}
}
impl OnionTuple {
pub fn equals(&self, other: &OnionObject) -> Result<bool, RuntimeError> {
match other {
OnionObject::Tuple(other_tuple) => {
if self.elements.len() != other_tuple.elements.len() {
return Ok(false);
}
for (a, b) in self.elements.iter().zip(other_tuple.elements.as_ref()) {
if a.equals(b)? {
return Ok(false);
}
}
Ok(true)
}
_ => Ok(false),
}
}
}
impl OnionTuple {
pub fn push(&mut self, element: OnionObjectCell) {
self.elements.push(element);
}
pub fn pop(&mut self) -> Option<OnionStaticObject> {
if self.elements.is_empty() {
None
} else {
let last_element = self.elements.last().cloned().map(|e| e.stabilize());
self.elements.pop();
last_element
}
}
pub fn is_empty(&self) -> bool {
self.elements.is_empty()
}
pub fn clear(&mut self) {
self.elements.clear();
}
pub fn insert(&mut self, index: usize, element: OnionObjectCell) -> Result<(), RuntimeError> {
if index > self.elements.len() {
return Err(RuntimeError::InvalidOperation(format!(
"Index out of bounds: {}",
index
)));
}
self.elements.insert(index, element);
Ok(())
}
pub fn remove(&mut self, index: usize) -> Result<OnionStaticObject, RuntimeError> {
let element_to_remove = self
.elements
.get(index)
.ok_or_else(|| {
RuntimeError::InvalidOperation(format!("Index out of bounds: {}", index))
})?
.clone()
.stabilize();
self.elements.remove(index);
Ok(element_to_remove)
}
}