sophia_api/
term.rs

1//! I define how RDF terms
2//! (such as [IRIs](https://www.w3.org/TR/rdf11-concepts/#section-IRIs),
3//! [blank nodes](https://www.w3.org/TR/rdf11-concepts/#section-blank-nodes)
4//! and [literals](https://www.w3.org/TR/rdf11-concepts/#section-Graph-Literal))
5//! are represented in Sophia.
6//!
7//! I provide the main trait [`Term`],
8//! and a number of auxiliary types and traits, such as [`TermKind`], [`FromTerm`]...
9use crate::triple::Triple;
10use mownstr::MownStr;
11use std::cmp::{Ord, Ordering};
12use std::hash::Hash;
13
14mod _cmp;
15pub use _cmp::*;
16mod _graph_name;
17pub use _graph_name::*;
18mod _native_iri;
19mod _native_literal;
20mod _simple;
21pub use _simple::*;
22
23pub mod bnode_id;
24pub mod language_tag;
25pub mod matcher;
26pub mod var_name;
27
28/// This type is aliased from `sophia_iri` for convenience,
29/// as it is required to implement [`Term`].
30pub type IriRef<T> = sophia_iri::IriRef<T>;
31// The following two types are also re-exported for the same reason.
32pub use bnode_id::BnodeId;
33pub use language_tag::LanguageTag;
34pub use var_name::VarName;
35
36lazy_static::lazy_static! {
37    static ref RDF_LANG_STRING: Box<str> = crate::ns::rdf::langString.iri().unwrap().unwrap().into();
38}
39
40/// The different kinds of terms that a [`Term`] can represent.
41#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
42pub enum TermKind {
43    /// An [RDF IRI](https://www.w3.org/TR/rdf11-concepts/#section-IRIs)
44    Iri,
45    /// An RDF [literal](https://www.w3.org/TR/rdf11-concepts/#section-Graph-Literal)
46    Literal,
47    /// An RDF [blank node](https://www.w3.org/TR/rdf11-concepts/#section-blank-nodes)
48    BlankNode,
49    /// An RDF-star [quoted triple](https://www.w3.org/2021/12/rdf-star.html#dfn-quoted)
50    Triple,
51    /// A SPARQL or Notation3 variable
52    Variable,
53}
54
55/// A [generalized] RDF term.
56///
57/// # Implementation
58///
59/// The only method without a default implementation is [`kind`](Term::kind),
60/// which indicates what kind of RDF term a given [`Term`] represents.
61///
62/// However, while all other methods have a default implementtation (returning `None`),
63/// those corresponding to the supported kinds MUST be overridden accordingly,
64/// otherwise they will panic.
65/// See below for an explanation of this design choice.
66///
67/// In order to test that all the methods are implemented consistently,
68/// consider using the macro [`assert_consistent_term_impl`].
69/// The macro should be invoked on various instances of the type,
70/// at least one for each [kind](Term::kind) that the type supports.
71///
72/// # Design rationale
73///
74/// The methods defined by this trait are not independent:
75/// depending on the value returned by [`kind`](Term::kind),
76/// other methods are expected to return `Some(...)` or `None` accordingly.
77///
78/// An alternative solution would have been for the variants of [`TermKind`]
79/// to *contain* the corresponding values.
80/// This would arguably have been more idiomatic for users,
81/// and less error-prone for implementors of this trait.
82///
83/// However, this would have caused performance issues in some cases,
84/// because the [`MownStr`] returned by, e.g.,
85/// [`iri`](Term::iri) or [`lexical_form`](Term::lexical_form),
86/// can be allocated *on demand* by some implementations.
87///
88/// [generalized]: crate#generalized-vs-strict-rdf-model
89pub trait Term: std::fmt::Debug {
90    /// A type of [`Term`] that can be borrowed from this type
91    /// (i.e. that can be obtained from a simple reference to this type).
92    /// It is used in particular for accessing constituents of quoted tripes ([`Term::triple`])
93    /// or for sharing this term with a function that expects `T: Term` (rather than `&T`)
94    /// using [`Term::borrow_term`].
95    ///
96    /// In "standard" cases, this type is either `&Self` or `Self`
97    /// (for types implementing [`Copy`]).
98    ///
99    /// # Note to implementors
100    /// * When in doubt, set `BorrowTerm<'x>` to `&'x Self`.
101    /// * If your type implements [`Copy`],
102    ///   consider setting it to `Self`.
103    /// * If your type is a wrapper `W(T)` where `T: Term`,
104    ///   consider setting it to `W(T::BorrowTerm<'x>)`.
105    /// * If none of the options above are possible, your type is probably not a good fit for implementing [`Term`].
106    type BorrowTerm<'x>: Term + Copy
107    where
108        Self: 'x;
109
110    /// Return the kind of RDF term that this [`Term`] represents.
111    fn kind(&self) -> TermKind;
112
113    /// Return true if this [`Term`] is an IRI,
114    /// i.e. if [`kind`](Term::kind) returns [`TermKind::Iri`].
115    #[inline]
116    fn is_iri(&self) -> bool {
117        self.kind() == TermKind::Iri
118    }
119
120    /// Return true if this [`Term`] is a blank node,
121    /// i.e. if [`kind`](Term::kind) returns [`TermKind::BlankNode`].
122    #[inline]
123    fn is_blank_node(&self) -> bool {
124        self.kind() == TermKind::BlankNode
125    }
126
127    /// Return true if this [`Term`] is a literal,
128    /// i.e. if [`kind`](Term::kind) returns [`TermKind::Literal`].
129    #[inline]
130    fn is_literal(&self) -> bool {
131        self.kind() == TermKind::Literal
132    }
133
134    /// Return true if this [`Term`] is a variable,
135    /// i.e. if [`kind`](Term::kind) returns [`TermKind::Variable`].
136    #[inline]
137    fn is_variable(&self) -> bool {
138        self.kind() == TermKind::Variable
139    }
140
141    /// Return true if this [`Term`] is an atomic term,
142    /// i.e. an [IRI](Term::is_iri),
143    /// a [blank node](Term::is_blank_node),
144    /// a [literal](Term::is_literal)
145    /// or a [variable](Term::is_variable).
146    #[inline]
147    fn is_atom(&self) -> bool {
148        use TermKind::*;
149        match self.kind() {
150            Iri | BlankNode | Literal | Variable => true,
151            Triple => false,
152        }
153    }
154
155    /// Return true if this [`Term`] is an RDF-star quoted triple,
156    /// i.e. if [`kind`](Term::kind) returns [`TermKind::Triple`].
157    #[inline]
158    fn is_triple(&self) -> bool {
159        self.kind() == TermKind::Triple
160    }
161
162    /// If [`kind`](Term::kind) returns [`TermKind::Iri`],
163    /// return this IRI.
164    /// Otherwise return `None`.
165    ///
166    /// # Note to implementors
167    /// The default implementation assumes that [`Term::is_iri`] always return false.
168    /// If that is not the case, this method must be explicit implemented.
169    #[inline]
170    fn iri(&self) -> Option<IriRef<MownStr>> {
171        self.is_iri()
172            .then(|| unimplemented!("Default implementation should have been overridden"))
173    }
174
175    /// If [`kind`](Term::kind) returns [`TermKind::BlankNode`],
176    /// return the locally unique label of this blank node.
177    /// Otherwise return `None`.
178    ///
179    /// # Note to implementors
180    /// The default implementation assumes that [`Term::is_blank_node`] always return false.
181    /// If that is not the case, this method must be explicit implemented.
182    #[inline]
183    fn bnode_id(&self) -> Option<BnodeId<MownStr>> {
184        self.is_blank_node()
185            .then(|| unimplemented!("Default implementation should have been overridden"))
186    }
187
188    /// If [`kind`](Term::kind) returns [`TermKind::Literal`],
189    /// return the lexical form of this literal.
190    /// Otherwise return `None`.
191    ///
192    /// # Note to implementors
193    /// The default implementation assumes that [`Term::is_literal`] always return false.
194    /// If that is not the case, this method must be explicit implemented.
195    #[inline]
196    fn lexical_form(&self) -> Option<MownStr> {
197        self.is_literal()
198            .then(|| unimplemented!("Default implementation should have been overridden"))
199    }
200
201    /// If [`kind`](Term::kind) returns [`TermKind::Literal`],
202    /// return the datatype IRI of this literal.
203    /// Otherwise return `None`.
204    ///
205    /// NB: if this literal is a language-tagged string,
206    /// then this method MUST return `http://www.w3.org/1999/02/22-rdf-syntax-ns#langString`.
207    ///
208    /// # Note to implementors
209    /// The default implementation assumes that [`Term::is_literal`] always return false.
210    /// If that is not the case, this method must be explicit implemented.
211    #[inline]
212    fn datatype(&self) -> Option<IriRef<MownStr>> {
213        self.is_literal()
214            .then(|| unimplemented!("Default implementation should have been overridden"))
215    }
216
217    /// If [`kind`](Term::kind) returns [`TermKind::Literal`],
218    /// and if this literal is a language-tagged string,
219    /// return its language tag.
220    /// Otherwise return `None`.
221    ///
222    /// # Note to implementors
223    /// The default implementation assumes that [`Term::is_literal`] always return false.
224    /// If that is not the case, this method must be explicit implemented.
225    #[inline]
226    fn language_tag(&self) -> Option<LanguageTag<MownStr>> {
227        self.is_literal()
228            .then(|| unimplemented!("Default implementation should have been overridden"))
229    }
230
231    /// If [`kind`](Term::kind) returns [`TermKind::Variable`],
232    /// return the name of this variable.
233    /// Otherwise return `None`.
234    ///
235    /// # Note to implementors
236    /// The default implementation assumes that [`Term::is_variable`] always return false.
237    /// If that is not the case, this method must be explicit implemented.
238    #[inline]
239    fn variable(&self) -> Option<VarName<MownStr>> {
240        self.is_variable()
241            .then(|| unimplemented!("Default implementation should have been overridden"))
242    }
243
244    /// If [`kind`](Term::kind) returns [`TermKind::Triple`],
245    /// return this triple.
246    /// Otherwise return `None`.
247    ///
248    /// # Note to implementors
249    /// The default implementation assumes that [`Term::is_triple`] always return false.
250    /// If that is not the case, this method must be explicit implemented.
251    #[inline]
252    fn triple(&self) -> Option<[Self::BorrowTerm<'_>; 3]> {
253        self.is_triple()
254            .then(|| unimplemented!("Default implementation should have been overridden"))
255    }
256
257    /// If [`kind`](Term::kind) returns [`TermKind::Triple`],
258    /// return this triple, consuming this term.
259    /// Otherwise return `None`.
260    ///
261    /// # Note to implementors
262    /// The default implementation assumes that [`Term::is_triple`] always return false.
263    /// If that is not the case, this method must be explicit implemented.
264    #[inline]
265    fn to_triple(self) -> Option<[Self; 3]>
266    where
267        Self: Sized,
268    {
269        self.is_triple()
270            .then(|| unimplemented!("Default implementation should have been overridden"))
271    }
272
273    /// Get something implementing [`Term`] from a simple reference to `self`,
274    /// representing the same RDF term as `self`.
275    ///
276    /// # Wny do functions in Sophia expect `T: Term` and never `&T: Term`?
277    /// To understand the rationale of this design choice,
278    /// consider an imaginary type `Foo`.
279    /// A function `f(x: Foo)` requires users to waive the ownership of the `Foo` value they want to pass to the function.
280    /// This is not always suited to the users needs.
281    /// On the other hand, a function `g(x: &Foo)` not only allows, but *forces* its users to maintain ownership of the `Foo` value they want to pass to the function.
282    /// Again, there are situations where this is not suitable.
283    /// The standard solution to this problem is to use the [`Borrow`](std::borrow) trait:
284    /// a function `h<T>(x: T) where T: Borrow<Foo>` allows `x` to be passed either by *value* (transferring ownership)
285    /// or by reference (simply borrowing the caller's `Foo`).
286    ///
287    /// While this design pattern is usable with a single type (`Foo` in our example above),
288    /// it is not usable with a trait, such as `Term`:
289    /// the following trait bound is not valid in Rust: `T: Borrow<Term>`.
290    /// Yet, we would like any function expecting a terms to be able to either take its ownership or simply borrow it,
291    /// depending on the caller's needs and preferences.
292    ///
293    /// The `borrow_term` methods offer a solution to this problem,
294    /// and therefore the trait bound `T: Term` must be thought of as equivalent to `T: Borrow<Term>`:
295    /// the caller can chose to either waive ownership of its term (by passing it directly)
296    /// or keep it (by passing the result of `borrow_term()` instead).
297    fn borrow_term(&self) -> Self::BorrowTerm<'_>;
298
299    /// Iter over all the constituents of this term.
300    ///
301    /// If this term is [atomic](Term::is_atom), the iterator yields only the term itself.
302    /// If it is a quoted triple, the iterator yields the quoted triple itself,
303    /// and the constituents of its subject, predicate and object.
304    fn constituents<'s>(&'s self) -> Box<dyn Iterator<Item = Self::BorrowTerm<'s>> + 's> {
305        let this_term = std::iter::once(self.borrow_term());
306        match self.triple() {
307            None => Box::new(this_term),
308            Some(triple) => {
309                Box::new(this_term.chain(triple.into_iter().flat_map(Term::to_constituents)))
310            }
311        }
312    }
313
314    /// Iter over all the constituents of this term, consuming it.
315    ///
316    /// See [Term::constituents].
317    fn to_constituents<'a>(self) -> Box<dyn Iterator<Item = Self> + 'a>
318    where
319        Self: Clone + 'a,
320    {
321        if !self.is_triple() {
322            Box::new(std::iter::once(self))
323        } else {
324            Box::new(
325                std::iter::once(self.clone()).chain(
326                    self.to_triple()
327                        .unwrap()
328                        .into_iter()
329                        .flat_map(Term::to_constituents),
330                ),
331            )
332        }
333    }
334
335    /// Iter over all the [atomic] constituents of this term.
336    ///
337    /// If this term is [atomic], the iterator yields only the term itself.
338    /// If it is a quoted triple, the iterator yields the atoms of its subject, predicate and object.
339    ///
340    /// [atomic]: Term::is_atom
341    fn atoms<'s>(&'s self) -> Box<dyn Iterator<Item = Self::BorrowTerm<'s>> + 's> {
342        match self.triple() {
343            None => Box::new(std::iter::once(self.borrow_term())),
344            Some(triple) => Box::new(triple.into_iter().flat_map(Term::to_atoms)),
345        }
346    }
347
348    /// Iter over all the [atomic](Term::is_atom) constituents of this term, consuming it.
349    ///
350    /// See [Term::atoms].
351    fn to_atoms<'a>(self) -> Box<dyn Iterator<Item = Self> + 'a>
352    where
353        Self: Sized + 'a,
354    {
355        if !self.is_triple() {
356            Box::new(std::iter::once(self))
357        } else {
358            Box::new(
359                self.to_triple()
360                    .unwrap()
361                    .into_iter()
362                    .flat_map(Term::to_atoms),
363            )
364        }
365    }
366
367    /// Check whether `self` and `other` represent the same RDF term.
368    fn eq<T: Term>(&self, other: T) -> bool {
369        let k1 = self.kind();
370        let k2 = other.kind();
371        if k1 != k2 {
372            return false;
373        }
374        match k1 {
375            TermKind::Iri => self.iri() == other.iri(),
376            TermKind::BlankNode => self.bnode_id() == other.bnode_id(),
377            TermKind::Literal => {
378                self.lexical_form() == other.lexical_form()
379                    && match (self.language_tag(), other.language_tag()) {
380                        (None, None) => self.datatype() == other.datatype(),
381                        (Some(tag1), Some(tag2)) if tag1 == tag2 => true,
382                        _ => false,
383                    }
384            }
385            TermKind::Triple => self.triple().unwrap().eq(other.triple().unwrap()),
386            TermKind::Variable => self.variable() == other.variable(),
387        }
388    }
389
390    /// Compare two terms:
391    /// * IRIs < literals < blank nodes < quoted triples < variables
392    /// * IRIs, blank nodes and variables are ordered by their value
393    /// * Literals are ordered by their datatype, then their language (if any),
394    ///   then their lexical form
395    /// * Quoted triples are ordered in lexicographical order
396    ///
397    /// NB: literals are ordered by their *lexical* value,
398    /// so for example, `"10"^^xsd:integer` comes *before* `"2"^^xsd:integer`.
399    fn cmp<T>(&self, other: T) -> Ordering
400    where
401        T: Term,
402    {
403        let k1 = self.kind();
404        let k2 = other.kind();
405        k1.cmp(&k2).then_with(|| match k1 {
406            TermKind::Iri => Ord::cmp(&self.iri().unwrap(), &other.iri().unwrap()),
407            TermKind::BlankNode => Ord::cmp(&self.bnode_id().unwrap(), &other.bnode_id().unwrap()),
408            TermKind::Variable => Ord::cmp(&self.variable().unwrap(), &other.variable().unwrap()),
409            TermKind::Literal => {
410                let tag1 = self.language_tag();
411                let tag2 = other.language_tag();
412                if let (Some(tag1), Some(tag2)) = (tag1, tag2) {
413                    tag1.cmp(&tag2).then_with(|| {
414                        self.lexical_form()
415                            .unwrap()
416                            .cmp(&other.lexical_form().unwrap())
417                    })
418                } else {
419                    let dt1 = self.datatype().unwrap();
420                    let dt2 = other.datatype().unwrap();
421                    Ord::cmp(&dt1, &dt2).then_with(|| {
422                        self.lexical_form()
423                            .unwrap()
424                            .cmp(&other.lexical_form().unwrap())
425                    })
426                }
427            }
428            TermKind::Triple => {
429                let spo1 = self.triple().unwrap();
430                let spo2 = other.triple().unwrap();
431                Term::cmp(&spo1[0], spo2[0])
432                    .then_with(|| Term::cmp(&spo1[1], spo2[1]))
433                    .then_with(|| Term::cmp(&spo1[2], spo2[2]))
434            }
435        })
436    }
437
438    /// Compute an implementation-independent hash of this RDF term.
439    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
440        let k = self.kind();
441        k.hash(state);
442        match k {
443            TermKind::Iri => Hash::hash(self.iri().unwrap().as_str(), state),
444            TermKind::BlankNode => Hash::hash(self.bnode_id().unwrap().as_str(), state),
445            TermKind::Literal => {
446                self.lexical_form().unwrap().hash(state);
447                match self.language_tag() {
448                    None => {
449                        Hash::hash(self.datatype().unwrap().as_str(), state);
450                    }
451                    Some(tag) => {
452                        '@'.hash(state);
453                        tag.hash(state);
454                    }
455                }
456            }
457            TermKind::Triple => {
458                let t = self.triple().unwrap();
459                t.s().hash(state);
460                t.p().hash(state);
461                t.o().hash(state);
462            }
463            TermKind::Variable => Hash::hash(self.variable().unwrap().as_str(), state),
464        }
465    }
466
467    /// Convert this term in another type.
468    ///
469    /// This method is to [`FromTerm`] what [`Into::into`] is to [`From`].
470    ///
471    /// NB: if you want to make a *copy* of this term without consuming it,
472    /// you can use `this_term.`[`borrow_term`](Term::borrow_term)`().into_term::<T>()`.
473    #[inline]
474    fn into_term<T: FromTerm>(self) -> T
475    where
476        Self: Sized,
477    {
478        T::from_term(self)
479    }
480
481    /// Try to convert this term into another type.
482    ///
483    /// This method is to [`TryFromTerm`] what [`TryInto::try_into`] is to [`TryFrom`].
484    ///
485    /// NB: if you want to make a *copy* of this term without consuming it,
486    /// you can use `this_term.`[`borrow_term`](Term::borrow_term)`().try_into_term::<T>()`.
487    #[inline]
488    fn try_into_term<T: TryFromTerm>(self) -> Result<T, T::Error>
489    where
490        Self: Sized,
491    {
492        T::try_from_term(self)
493    }
494
495    /// Copies this term into a [`SimpleTerm`],
496    /// borrowing as much as possible from `self`
497    /// (calling [`SimpleTerm::from_term_ref`]).
498    #[inline]
499    fn as_simple(&self) -> SimpleTerm<'_> {
500        SimpleTerm::from_term_ref(self)
501    }
502}
503
504impl<'a, T> Term for &'a T
505where
506    T: Term<BorrowTerm<'a> = &'a T> + ?Sized,
507{
508    type BorrowTerm<'x> = Self where 'a: 'x;
509
510    fn kind(&self) -> TermKind {
511        (*self).kind()
512    }
513    fn is_iri(&self) -> bool {
514        (*self).is_iri()
515    }
516    fn is_blank_node(&self) -> bool {
517        (*self).is_blank_node()
518    }
519    fn is_literal(&self) -> bool {
520        (*self).is_literal()
521    }
522    fn is_variable(&self) -> bool {
523        (*self).is_variable()
524    }
525    fn is_triple(&self) -> bool {
526        (*self).is_triple()
527    }
528    fn iri(&self) -> Option<IriRef<MownStr>> {
529        (*self).iri()
530    }
531    fn bnode_id(&self) -> Option<BnodeId<MownStr>> {
532        (*self).bnode_id()
533    }
534    fn lexical_form(&self) -> Option<MownStr> {
535        (*self).lexical_form()
536    }
537    fn datatype(&self) -> Option<IriRef<MownStr>> {
538        (*self).datatype()
539    }
540    fn language_tag(&self) -> Option<LanguageTag<MownStr>> {
541        (*self).language_tag()
542    }
543    fn variable(&self) -> Option<VarName<MownStr>> {
544        (*self).variable()
545    }
546    fn triple(&self) -> Option<[Self::BorrowTerm<'_>; 3]> {
547        (*self).triple()
548    }
549    fn to_triple(self) -> Option<[Self; 3]> {
550        (*self).triple()
551    }
552    fn borrow_term(&self) -> Self::BorrowTerm<'_> {
553        *self
554    }
555    fn eq<U: Term>(&self, other: U) -> bool {
556        (*self).eq(other)
557    }
558    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
559        (*self).hash(state)
560    }
561}
562
563//
564
565/// A type that can be built from any term.
566///
567/// See also [`TryFromTerm`]
568pub trait FromTerm: Sized {
569    /// Copy `term` into an instance of this type.
570    fn from_term<T: Term>(term: T) -> Self;
571}
572
573/// A type that can be built from some terms.
574///
575/// See also [`FromTerm`]
576pub trait TryFromTerm: Sized {
577    /// The error type produced when failing to copy a given term
578    type Error: std::error::Error + Send + Sync + 'static;
579    /// Try to copy `term` into an instance of this type.
580    fn try_from_term<T: Term>(term: T) -> Result<Self, Self::Error>;
581}
582
583/// Test that the given term is consistent in its implementation of the [`Term`] trait.
584///
585/// NB: it may be necessary to explicitly specify the parameter `T`,
586/// even when the type of `t` is known. E.g.: ``assert_consistent_term_impl::<MyTerm>(&t)``.
587pub fn assert_consistent_term_impl<T>(t: &T)
588where
589    T: Term + Clone,
590{
591    let k = t.kind();
592    if k == TermKind::Iri {
593        assert!(t.is_iri());
594        assert!(t.iri().is_some());
595    } else {
596        assert!(!t.is_iri());
597        assert!(t.iri().is_none());
598    }
599    if k == TermKind::BlankNode {
600        assert!(t.is_blank_node());
601        assert!(t.bnode_id().is_some());
602    } else {
603        assert!(!t.is_blank_node());
604        assert!(t.bnode_id().is_none());
605    }
606    if k == TermKind::Literal {
607        assert!(t.is_literal());
608        assert!(t.lexical_form().is_some());
609        assert!(t.datatype().is_some());
610        if t.datatype() == crate::ns::rdf::langString.iri() {
611            assert!(t.language_tag().is_some());
612        } else {
613            assert!(t.language_tag().is_none());
614        }
615    } else {
616        assert!(!t.is_literal());
617        assert!(t.lexical_form().is_none());
618        assert!(t.datatype().is_none());
619        assert!(t.language_tag().is_none());
620    }
621    if k == TermKind::Variable {
622        assert!(t.is_variable());
623        assert!(t.variable().is_some());
624    } else {
625        assert!(!t.is_variable());
626        assert!(t.variable().is_none());
627    }
628    if k == TermKind::Triple {
629        assert!(t.is_triple());
630        assert!(t.triple().is_some());
631        assert!(t.clone().to_triple().is_some());
632    } else {
633        assert!(!t.is_triple());
634        assert!(t.triple().is_none());
635        assert!(t.clone().to_triple().is_none());
636    }
637    if k != TermKind::Triple {
638        assert!(t.is_atom());
639        assert!(t.constituents().count() == 1);
640        assert!(t.constituents().next().unwrap().eq(t.borrow_term()));
641        assert!(t.clone().to_constituents().count() == 1);
642        assert!(t.clone().to_constituents().next().unwrap().eq(t.clone()));
643        assert!(t.atoms().count() == 1);
644        assert!(t.atoms().next().unwrap().eq(t.borrow_term()));
645        assert!(t.clone().to_atoms().count() == 1);
646        assert!(t.clone().to_atoms().next().unwrap().eq(t.clone()));
647    } else {
648        assert!(!t.is_atom());
649        assert!(t.constituents().count() >= 4);
650        assert!(t.clone().to_constituents().count() >= 4);
651        assert!(t.atoms().count() >= 3);
652        assert!(t.clone().to_atoms().count() >= 3);
653    }
654    t.eq(t.borrow_term());
655}
656
657#[cfg(test)]
658mod check_implementability {
659    use super::*;
660
661    // three different implementations of Term using different strategies for Self::Triple
662
663    #[derive(Clone, Copy, Debug)]
664    struct Term1 {
665        nested: bool,
666    }
667
668    const BN1: Term1 = Term1 { nested: false };
669
670    impl Term for Term1 {
671        type BorrowTerm<'x> = Self;
672
673        fn kind(&self) -> TermKind {
674            match self.nested {
675                false => TermKind::BlankNode,
676                true => TermKind::Triple,
677            }
678        }
679        fn bnode_id(&self) -> Option<BnodeId<MownStr>> {
680            (!self.nested).then(|| BnodeId::new_unchecked("t1".into()))
681        }
682        fn triple(&self) -> Option<[Self::BorrowTerm<'_>; 3]> {
683            self.nested.then_some([BN1, BN1, BN1])
684        }
685        fn borrow_term(&self) -> Self::BorrowTerm<'_> {
686            *self
687        }
688    }
689
690    #[derive(Clone, Copy, Debug)]
691    struct Term2 {
692        nested: bool,
693    }
694
695    const BN2: Term2 = Term2 { nested: false };
696
697    impl Term for Term2 {
698        type BorrowTerm<'x> = &'x Self;
699
700        fn kind(&self) -> TermKind {
701            match self.nested {
702                false => TermKind::BlankNode,
703                true => TermKind::Triple,
704            }
705        }
706        fn bnode_id(&self) -> Option<BnodeId<MownStr>> {
707            (!self.nested).then(|| BnodeId::new_unchecked("t2".into()))
708        }
709        fn triple(&self) -> Option<[Self::BorrowTerm<'_>; 3]> {
710            self.nested.then_some([&BN2, &BN2, &BN2])
711        }
712        fn borrow_term(&self) -> Self::BorrowTerm<'_> {
713            self
714        }
715    }
716
717    #[derive(Clone, Debug)]
718    struct Term3(Option<Box<[Term3; 3]>>);
719
720    impl Term for Term3 {
721        type BorrowTerm<'x> = &'x Self;
722
723        fn kind(&self) -> TermKind {
724            match self.0 {
725                None => TermKind::BlankNode,
726                Some(_) => TermKind::Triple,
727            }
728        }
729        fn bnode_id(&self) -> Option<BnodeId<MownStr>> {
730            match self.0 {
731                None => Some(BnodeId::new_unchecked("t3".into())),
732                Some(_) => None,
733            }
734        }
735        fn triple(&self) -> Option<[Self::BorrowTerm<'_>; 3]> {
736            if let Some(b) = &self.0 {
737                let [s, p, o] = b.as_ref();
738                Some([s, p, o])
739            } else {
740                None
741            }
742        }
743        fn borrow_term(&self) -> Self::BorrowTerm<'_> {
744            self
745        }
746    }
747}
748
749#[cfg(test)]
750/// Simplistic Term parser, useful for writing test cases.
751/// The syntax is a subset of Turtle-star.
752pub(crate) fn ez_term(txt: &str) -> SimpleTerm {
753    use sophia_iri::IriRef;
754    match txt.as_bytes() {
755        [b'<', b'<', .., b'>', b'>'] => {
756            let subterms: Vec<&str> = txt[2..txt.len() - 2].split(' ').collect();
757            assert_eq!(subterms.len(), 3);
758            SimpleTerm::Triple(Box::new([
759                ez_term(subterms[0]),
760                ez_term(subterms[1]),
761                ez_term(subterms[2]),
762            ]))
763        }
764        [b'<', .., b'>'] => IriRef::new_unchecked(&txt[1..txt.len() - 1]).into_term(),
765        [b':', ..] => {
766            let iri = format!("tag:{}", &txt[1..]);
767            SimpleTerm::Iri(IriRef::new_unchecked(iri.into()))
768        }
769        [b'_', b':', ..] => BnodeId::new_unchecked(&txt[2..]).into_term(),
770        [b'\'', .., b'\''] => (&txt[1..txt.len() - 1]).into_term(),
771        [b'\'', .., b'\'', b'@', _, _] => SimpleTerm::LiteralLanguage(
772            (&txt[1..txt.len() - 4]).into(),
773            LanguageTag::new_unchecked(txt[txt.len() - 2..].into()),
774        ),
775        [c, ..] if c.is_ascii_digit() => txt.parse::<i32>().unwrap().into_term(),
776        [b'?', ..] => VarName::new_unchecked(&txt[1..]).into_term(),
777        _ => panic!("Unable to parse term"),
778    }
779}
780
781#[cfg(test)]
782mod test_term_impl {
783    use super::*;
784    use test_case::test_case;
785
786    // order with terms of the same kind
787    #[test_case("<tag:a>", "<tag:b>")]
788    #[test_case("_:u", "_:v")]
789    #[test_case("'a'", "'b'")]
790    #[test_case("10", "2")]
791    #[test_case("'a'@en", "'a'@fr")]
792    #[test_case("?x", "?y")]
793    #[test_case("<<_:s <tag:p> 'o1'>>", "<<_:s <tag:p> 'o2'>>")]
794    #[test_case("<<_:s <tag:p1> 'o2'>>", "<<_:s <tag:p2> 'o1'>>")]
795    #[test_case("<<_:s1 <tag:p2> 'o'>>", "<<_:s2 <tag:p1> 'o'>>")]
796    // order across different literals
797    #[test_case("2", "'10'")]
798    #[test_case("'b'@en", "'a'")]
799    // order across term kinds
800    #[test_case("<tag:a>", "'s'")]
801    #[test_case("<tag:a>", "_:r")]
802    #[test_case("<tag:a>", "<<_:q <tag:q> 'q'>>")]
803    #[test_case("<tag:a>", "?p")]
804    #[test_case("'s'", "_:r")]
805    #[test_case("'s'", "<<_:q <tag:q> 'q'>>")]
806    #[test_case("'s'", "?p")]
807    #[test_case("_:r", "<<_:q <tag:q> 'q'>>")]
808    #[test_case("_:r", "?p")]
809    #[test_case("<<_:q <tag:q> 'q'>>", "?p")]
810    fn cmp_terms(t1: &str, t2: &str) {
811        let t1 = ez_term(t1);
812        let t2 = ez_term(t2);
813        assert_eq!(Term::cmp(&t1, &t1), std::cmp::Ordering::Equal);
814        assert_eq!(Term::cmp(&t2, &t2), std::cmp::Ordering::Equal);
815        assert_eq!(Term::cmp(&t1, &t2), std::cmp::Ordering::Less);
816        assert_eq!(Term::cmp(&t2, &t1), std::cmp::Ordering::Greater);
817    }
818}