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}