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}