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