use std::fmt::Debug;
use arc_gc::{arc::GCArc, traceable::GCTraceable};
use crate::lambda::runnable::RuntimeError;
use super::object::{OnionObject, OnionObjectCell, OnionStaticObject};
#[derive(Clone)]
pub struct OnionTuple {
pub elements: Vec<OnionObjectCell>,
}
impl GCTraceable for OnionTuple {
fn visit(&self) {
for element in &self.elements {
element.visit();
}
}
}
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 }
}
pub fn new_static(elements: Vec<&OnionStaticObject>) -> OnionStaticObject {
OnionStaticObject::new(OnionObject::Tuple(OnionTuple {
elements: elements.into_iter().map(|e| e.weak().clone()).collect(),
}))
}
pub fn new_static_no_ref(elements: Vec<OnionStaticObject>) -> OnionStaticObject {
OnionStaticObject::new(OnionObject::Tuple(OnionTuple {
elements: elements.into_iter().map(|e| e.weak().clone()).collect(),
}))
}
pub fn upgrade(&self) -> Option<Vec<GCArc<OnionObjectCell>>> {
if self.elements.is_empty() {
return None;
}
let mut arcs = Vec::new();
for element in &self.elements {
match element.upgrade() {
Some(arc) => arcs.extend(arc),
None => {}
}
}
Some(arcs)
}
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 Ok(OnionStaticObject::new(OnionObject::Undefined(Some(
"Index out of bounds".to_string(),
))));
}
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 {
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 &mut self.elements {
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.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 {
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) {
if a.equals(b)? {
return Ok(false);
}
}
Ok(true)
}
_ => Ok(false),
}
}
}
impl OnionTuple {
pub fn clone_and_named_assignment(
&self,
other: &OnionTuple,
) -> Result<OnionStaticObject, RuntimeError> {
let mut new_elements = self.elements.clone();
let mut assigned = vec![false; new_elements.len()];
for other_element in &other.elements {
match &*other_element.try_borrow()? {
OnionObject::Named(named) => {
let key = named.key.as_ref();
let mut found = false;
for (i, element) in new_elements.iter_mut().enumerate() {
let should_replace = {
match &*element.try_borrow()? {
OnionObject::Named(existing_named) => {
existing_named.key.equals(key)?
}
_ => false,
}
};
if should_replace {
*element = other_element.clone();
assigned[i] = true;
found = true;
break;
}
}
if !found {
new_elements.push(other_element.clone());
assigned.push(true);
}
}
_ => {}
}
}
for other_element in &other.elements {
match &*other_element.try_borrow()? {
OnionObject::Named(_) => {}
_ => {
let mut found = false;
for (i, assigned_flag) in assigned.iter_mut().enumerate() {
if !*assigned_flag {
let should_replace_with_clone = {
match &mut *new_elements[i].try_borrow_mut()? {
OnionObject::Named(v) => {
*v.get_value_mut() = other_element.clone();
false
}
_ => {
true
}
}
};
if should_replace_with_clone {
new_elements[i] = other_element.clone();
}
*assigned_flag = true;
found = true;
break;
}
}
if !found {
new_elements.push(other_element.clone());
assigned.push(true);
}
}
}
}
Ok(OnionStaticObject::new(OnionObject::Tuple(OnionTuple {
elements: new_elements,
})))
}
}