rain-lang 0.0.1

An implementation of an RVSDG in Rust with a concept of lifetimes
Documentation
/*!
A region, containing scoped nodes, parameters and outputs.
*/
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::ops::Deref;
use std::cmp::{PartialOrd, Ordering};
use std::sync::{Arc, Weak};
use std::convert::Infallible;
use std::hash::{Hash, Hasher};
use crate::value::{
    ValId, ValueEnum, ValueData, ValueDesc, Value,
    error::RegionAlreadyFused
};

/// A region into which nodes are scoped
#[derive(Debug, Clone)]
pub struct Region(Arc<RegionData>);

impl Region {
    /// Create a new region
    pub fn new() -> Region { Region::new_in(WeakRegion::default()) }
    /// Create a new region with a given parent
    pub fn new_in(parent: WeakRegion) -> Region {
        let depth = parent.upgrade().map(|parent| parent.depth).unwrap_or(0) + 1;
        let result = Region(Arc::new(RegionData {
            parent,
            depth,
            params: RwLock::new(Parameters::new()),
            _private: ()
        }));
        let weak = result.downgrade();
        result.params.write().this = weak;
        result
    }
    /// Downgrade a region to a weak reference
    #[inline] pub fn downgrade(&self) -> WeakRegion { WeakRegion(Arc::downgrade(&self.0)) }
}

impl PartialEq for Region {
    fn eq(&self, other: &Region) -> bool { Arc::ptr_eq(&self.0, &other.0) }
}

impl Eq for Region {}

impl Hash for Region {
    #[inline] fn hash<H: Hasher>(&self, hasher: &mut H) {
        Arc::as_ptr(&self.0).hash(hasher)
    }
}

impl PartialOrd for Region {
    fn partial_cmp(&self, other: &Region) -> Option<Ordering> {
        if self == other { return Some(Ordering::Equal) }
        let ordering = self.depth.cmp(&other.depth);
        let (deeper, shallower) = match ordering {
            Ordering::Equal => { return None },
            Ordering::Greater => (self, other),
            Ordering::Less => (other, self)
        };
        let mut parent = deeper.parent.upgrade()?;
        while parent.depth > shallower.depth {
            parent = parent.parent.upgrade()?;
        }
        if &parent == shallower {
            Some(ordering.reverse())
        } else {
            None
        }
    }
}

impl PartialEq<WeakRegion> for Region {
    fn eq(&self, other: &WeakRegion) -> bool {
        if let Some(other) = other.upgrade() { other.eq(self) } else { false }
    }
}

impl PartialOrd<WeakRegion> for Region {
    fn partial_cmp(&self, other: &WeakRegion) -> Option<Ordering> {
        if other.is_null() { Some(Ordering::Less) }
        else { self.partial_cmp(&other.upgrade()?) }
    }
}

impl PartialEq<Region> for WeakRegion {
    fn eq(&self, other: &Region) -> bool { other.eq(self) }
}

impl PartialOrd<Region> for WeakRegion {
    fn partial_cmp(&self, other: &Region) -> Option<Ordering> {
        other.partial_cmp(self).map(Ordering::reverse)
    }
}

impl Deref for Region {
    type Target = RegionData;
    #[inline] fn deref(&self) -> &RegionData { self.0.deref() }
}

/// A weak handle on a region
#[derive(Debug, Clone, Default)]
pub struct WeakRegion(pub Weak<RegionData>);

impl WeakRegion {
    /// Attempt to upgrade a region to a strong reference
    #[inline] pub fn upgrade(&self) -> Option<Region> { self.0.upgrade().map(Region) }
    /// Check whether a weak region is null
    #[inline] pub fn is_null(&self) -> bool { self == &WeakRegion::default() }
    /// Get the outer region of nested regions, if either is
    #[inline] pub fn outer<'a>(&'a self, other: &'a WeakRegion) -> Option<&'a WeakRegion> {
        self.partial_cmp(other).map(|ord| match ord { Ordering::Greater => self, _ => other })
    }
    /// Get the inner region of nested regions, if either is
    #[inline] pub fn inner<'a>(&'a self, other: &'a WeakRegion) -> Option<&'a WeakRegion> {
        self.partial_cmp(other).map(|ord| match ord { Ordering::Less => self, _ => other })
    }
    /// Get the innermost region of a list of nested regions, assuming all are comparable.
    /// Return an error if not.
    #[inline] pub fn innermost<'a, I>(&'a self, mut regions: I)
    -> Result<&'a WeakRegion, (&'a WeakRegion, &'a WeakRegion)>
    where I: Iterator<Item=&'a WeakRegion> {
        let mut res = self;
        while let Some(region) = regions.next() {
            match res.partial_cmp(region) {
                None => return Err((res, region)),
                Some(Ordering::Less) => res = region,
                _ => {}
            }
        }
        Ok(res)
    }
}

impl PartialEq for WeakRegion {
    fn eq(&self, other: &WeakRegion) -> bool { Weak::ptr_eq(&self.0, &other.0) }
}

impl Eq for WeakRegion {}

impl Hash for WeakRegion {
    #[inline] fn hash<H: Hasher>(&self, hasher: &mut H) {
        self.0.as_ptr().hash(hasher)
    }
}

impl PartialOrd for WeakRegion {
    fn partial_cmp(&self, other: &WeakRegion) -> Option<Ordering> {
        if self == other {
            Some(Ordering::Equal)
        } else if self.is_null() {
            Some(Ordering::Greater)
        } else if other.is_null() {
            Some(Ordering::Less)
        } else {
            let left = self.upgrade()?;
            let right = other.upgrade()?;
            let ordering = left.partial_cmp(&right);
            debug_assert_ne!(ordering, Some(Ordering::Equal));
            ordering
        }
    }
}

/// The data defining a region
#[derive(Debug)]
pub struct RegionData {
    /// The parent scope of this region
    pub parent: WeakRegion,
    /// The depth of this region
    pub depth: usize,
    /// The parameters of a region
    pub params: RwLock<Parameters>,
    /// Disallow directly creating `RegionData`
    _private: ()
}

impl RegionData {
    /// Get the parameters of a region
    pub fn params(&self) -> RwLockReadGuard<Parameters> { self.params.read() }
    /// Mutably get the parameters of a region
    pub fn params_mut(&self) -> RwLockWriteGuard<Parameters> { self.params.write() }
    /// Add a parameter to this region, get the parameter back along with its index
    pub fn add_param(&self, desc: ParameterDesc) -> (usize, ValId) {
        let mut params = self.params_mut();
        let ix = params.add_param(desc).expect("Unfused region");
        let param = params.arr()[ix].clone();
        (ix, param)
    }
    /// Add a parameter to this region with a given type, get the parameter back
    pub fn add_with_ty(&self, ty: ValId) -> (usize, ValId) {
        let mut params = self.params_mut();
        let ix = params.add_with_ty(ty).expect("Unfused region");
        let param = params.arr()[ix].clone();
        (ix, param)
    }
    /// Get the this pointer of this region
    pub fn this(&self) -> WeakRegion { self.params().this() }
}

/// The roots of a region
#[derive(Debug)]
pub struct Parameters {
    /// The actual parameters
    arr: Vec<ValId>,
    /// A self-pointer to the region
    this: WeakRegion,
    /// Whether this node is fused
    fused: bool
}

impl Parameters {
    /// Create a new, empty set of region roots
    fn new() -> Parameters {
        Parameters { arr: Vec::new(), this: WeakRegion::default(), fused: false }
    }
    /// Add a new parameter to the region. Get the associated index
    pub fn add_param(&mut self, desc: ParameterDesc) -> Result<usize, RegionAlreadyFused> {
        if self.fused { return Err(RegionAlreadyFused) } //TODO
        let ix = self.arr.len();
        let param = Parameter {
            ty: desc.ty,
            region: self.this.clone(),
            ix
        };
        let node = ValId::from(param);
        self.arr.push(node);
        Ok(ix)
    }
    /// Add a new parameter with a given type
    pub fn add_with_ty(&mut self, ty: ValId) -> Result<usize, RegionAlreadyFused> {
        self.add_param(ParameterDesc { ty })
    }
    /// Get the parameter array
    pub fn arr(&self) -> &[ValId] { &self.arr }
    /// Get the this pointer of this region
    pub fn this(&self) -> WeakRegion { self.this.clone() }
    /// Check whether this region is fused
    pub fn fused(&self) -> bool { self.fused }
    /// Fuse this region.
    pub fn fuse(&mut self) { self.fused = true }
}

/// A descriptor for a region parameter
#[derive(Debug, Clone, PartialEq)]
pub struct ParameterDesc {
    /// The type of this parameter
    pub ty: ValId
}

/// A parameter for a region
#[derive(Debug, Clone, Hash)]
pub struct Parameter {
    /// The type of this parameter
    pub ty: ValId,
    /// The region this parameter is associated to
    region: WeakRegion,
    /// The parameter index of this parameter
    ix: usize
}

impl From<Parameter> for ValueEnum {
    fn from(param: Parameter) -> ValueEnum { ValueEnum::Parameter(param) }
}
impl From<Parameter> for ValueData {
    fn from(param: Parameter) -> ValueData {
        let region = param.region.clone();
        ValueData::with_region(ValueEnum::Parameter(param), region)
    }
}
impl From<Parameter> for ValId {
    fn from(param: Parameter) -> ValId {
        ValId::try_new(ValueData::from(param)).expect("Impossible")
    }
}

impl ValueDesc for Parameter {
    type Err = Infallible;
}

impl Value for Parameter {}

impl Parameter {
    /// Get the parameter index of this parameter
    pub fn ix(&self) -> usize { self.ix }
    /// Get the region of this parameter
    pub fn region(&self) -> &WeakRegion { &self.region }
}

impl PartialEq for Parameter {
    fn eq(&self, other: &Parameter) -> bool {
        self.region == other.region && self.ix == other.ix
    }
}

impl Eq for Parameter {}

#[cfg(test)]
pub mod tests {
    use super::*;
    use crate::value::primitive::{Unit, logical::{Unary, Bool}};
    use crate::value::expr::SexprArgs;
    use smallvec::smallvec;
    use std::convert::TryInto;

    #[test]
    fn nested_region_construction() {
        let region = Region::new();
        let nested_region = Region::new_in(region.downgrade());
        let other_region = Region::new();
        let null = WeakRegion::default();
        assert_eq!(nested_region.parent, region.downgrade());
        assert_eq!(nested_region.this(), nested_region.downgrade());
        assert_eq!(region.this(), region.downgrade());
        assert_eq!(region.parent, WeakRegion::default());
        assert_eq!(region.depth, 1);
        assert_eq!(other_region.parent, WeakRegion::default());
        assert_eq!(other_region.depth, 1);
        assert_eq!(nested_region.depth, 2);
        assert_eq!(region, region);
        assert_eq!(nested_region, nested_region);
        assert_eq!(other_region, other_region);
        assert_ne!(region, other_region);
        assert_ne!(region, nested_region);
        assert_ne!(other_region, nested_region);
        assert_eq!(region.partial_cmp(&nested_region), Some(Ordering::Greater));
        assert_eq!(region.partial_cmp(&null), Some(Ordering::Less));
        assert_eq!(region.partial_cmp(&other_region), None);
        assert_eq!(nested_region.partial_cmp(&region), Some(Ordering::Less));
        assert_eq!(nested_region.partial_cmp(&other_region), None);
    }

    #[test]
    fn parameters_are_added_to_region() {
        let region = Region::new();
        let unit_ty = ValId::from(Unit);
        let bool_ty = ValId::from(Bool);
        let mut params = region.params_mut();
        assert_eq!(params.arr().len(), 0);
        let tys = [unit_ty.clone(), bool_ty.clone(), unit_ty.clone()];
        for (i, ty) in tys.iter().enumerate() {
            assert_eq!(i, params.add_with_ty(ty.clone()).expect("Unfused region"))
        }
        assert_eq!(params.arr().len(), tys.len());
        let weak = region.downgrade();
        for (i, a) in params.arr().iter().enumerate() {
            let data = a.data();
            match &data.value {
                ValueEnum::Parameter(p) => {
                    assert_eq!(p.ix, i);
                    assert_eq!(p.region, weak);
                    assert_eq!(p.ty, tys[i])
                },
                v => panic!("Bad parameter value {} @ params[{}]", v, i)
            }
        }

        let not_args = SexprArgs(
            smallvec![params.arr()[1].clone(), Unary::Not.into()]
        );
        let not: ValId = not_args.try_into().expect("This is a valid expression");
        {
            let not = not.data();
            assert_eq!(
                not.region, weak,
                "Wrong region for (not #parameter): is null = {}",
                not.region == WeakRegion::default()
            );
        }
    }
}