1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! A selection of useful traits that exceed the scope of OBO syntax.

use std::cmp::Ordering;
use std::collections::HashMap;
use std::collections::HashSet;
use std::ptr::NonNull;

use crate::ast::*;
use crate::error::CardinalityError;
use crate::visit::VisitMut;

mod treat_xrefs;
pub(crate) use self::treat_xrefs::*;

/// The cardinality constraint for a given clause type.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Cardinality {
    ZeroOrOne,
    One,
    NotOne,
    Any,
}

impl Cardinality {
    pub fn is_match(&self, n: usize) -> bool {
        match self {
            Cardinality::ZeroOrOne => n < 2,
            Cardinality::One => n == 1,
            Cardinality::NotOne => n != 1,
            Cardinality::Any => true,
        }
    }

    pub fn to_error<S: Into<String>>(&self, n: usize, tag: S) -> Option<CardinalityError> {
        use self::CardinalityError::*;
        let name = tag.into();
        match self {
            Cardinality::ZeroOrOne if n > 1 => Some(DuplicateClauses { name }),
            Cardinality::One if n == 0 => Some(MissingClause { name }),
            Cardinality::One if n > 1 => Some(DuplicateClauses { name }),
            Cardinality::NotOne if n == 1 => Some(SingleClause { name }),
            _ => None,
        }
    }
}

/// A trait for structs that can be sorted in an order specified in the OBO spec.
pub trait Orderable {
    /// Sort the elements of the collection in the right serialization order.
    ///
    /// # See Also
    /// - The [Serializer conventions](https://owlcollab.github.io/oboformat/doc/GO.format.obo-1_4.html#S.3.5)
    ///   section of the OBO Flat File format guide.
    fn sort(&mut self);

    /// Check if the collection is sorted in the right serialization order.
    fn is_sorted(&self) -> bool;
}

/// Common attributes and operations for all frames.
pub trait OboFrame {
    type Clause: OboClause;

    /// Get a vector of references to the clauses of a frame.
    ///
    /// # Note
    /// While currently returning a `Box<Iterator>`, this method will be changed
    /// to return an associated type when [RFC1598] is implemented and available
    /// in *stable* Rust.
    ///
    /// [RFC1598]: https://github.com/rust-lang/rfcs/blob/master/text/1598-generic_associated_types.md
    fn clauses_ref(&self) -> Vec<&Self::Clause>;

    /// Check the frame only contains clauses with the right cardinality.
    ///
    /// # Note
    /// The current implementation does not check for missing clauses: good
    /// ergonomics are to be found to provide a collection of required clauses
    /// in a generic manner.
    fn cardinality_check(&self) -> Result<(), CardinalityError> {
        use std::collections::HashMap;
        use std::mem::discriminant;

        // Group clauses by variant kind
        let mut clause_index: HashMap<_, Vec<&Self::Clause>> = HashMap::new();
        for clause in self.clauses_ref() {
            clause_index.entry(clause.tag()).or_default().push(clause);
        }

        // Check each variant kind
        for (tag, clauses) in clause_index {
            let cardinality = clauses[0].cardinality();
            if let Some(err) = cardinality.to_error(clauses.len(), tag) {
                return Err(err);
            }
        }

        Ok(())
    }
}

/// Common attributes and operations for all clauses.
pub trait OboClause {
    /// Get the raw string corresponding to the tag of a clause.
    ///
    /// # Example
    /// ```rust
    /// # extern crate fastobo;
    /// # use fastobo::ast::*;
    /// # use fastobo::semantics::OboClause;
    /// let clause = HeaderClause::SavedBy("Martin Larralde".into());
    /// assert_eq!(clause.tag(), "saved-by");
    /// ```
    fn tag(&self) -> &str;

    /// Get the cardinality expected for a clause variant.
    ///
    /// While most clauses can appear any number of time in a frame, some
    /// have a constraint on how many time they can appear: for instance,
    /// a `namespace` clause must appear exactly once in every entity frame,
    /// and an `intersection_of` clause cannot appear only once.
    ///
    /// # Example
    /// ```rust
    /// # extern crate fastobo;
    /// # use fastobo::ast::*;
    /// # use fastobo::semantics::OboClause;
    /// # use fastobo::semantics::Cardinality;
    /// let clause = HeaderClause::SavedBy("Martin Larralde".into());
    /// assert_eq!(clause.cardinality(), Cardinality::ZeroOrOne);
    /// ```
    fn cardinality(&self) -> Cardinality;
}

/// A trait for structs that have an identifier.
pub trait Identified {
    /// Get a reference to the identifier of the entity.
    fn as_id(&self) -> &Ident;

    /// Get a mutable reference to the identifier of the entity.
    fn as_id_mut(&mut self) -> &mut Ident;
}