diff --git a/term/src/_generic.rs b/term/src/_generic.rs index 991c4062..834abe58 100644 --- a/term/src/_generic.rs +++ b/term/src/_generic.rs @@ -2,244 +2,14 @@ use std::borrow::Borrow; use std::fmt::Debug; use sophia_api::ns::rdf; -use sophia_api::term::{ - BnodeId, FromTerm, IriRef, LanguageTag, Term, TermKind, TryFromTerm, VarName, -}; +use sophia_api::term::{IriRef, LanguageTag, Term, TermKind, TryFromTerm}; use sophia_api::MownStr; lazy_static::lazy_static! { static ref RDF_LANG_STRING: IriRef> = rdf::langString.iri().unwrap().map_unchecked(Box::from); } -/// A generic implementation of [`Term`]. -/// -/// Note that, although it is possible to use `GenericTerm`, -/// it is recommended to use [`sophia_api::term::SimpleTerm`] instead. -#[derive(Clone, Debug)] -pub enum GenericTerm> { - /// A straighforward implementation of [`Term`] as an enum. - /// An [RDF IRI](https://www.w3.org/TR/rdf11-concepts/#section-IRIs) - Iri(IriRef), - /// An RDF [blank node](https://www.w3.org/TR/rdf11-concepts/#section-blank-nodes) - BlankNode(BnodeId), - /// An RDF [literal](https://www.w3.org/TR/rdf11-concepts/#section-Graph-Literal) - Literal(GenericLiteral), - /// An RDF-star [quoted triple](https://www.w3.org/2021/12/rdf-star.html#dfn-quoted) - Triple(Box<[Self; 3]>), - /// A SPARQL or Notation3 variable - Variable(VarName), -} - -impl + Debug> Term for GenericTerm { - type BorrowTerm<'x> = &'x Self where Self: 'x; - - fn kind(&self) -> sophia_api::term::TermKind { - match self { - GenericTerm::Iri(_) => TermKind::Iri, - GenericTerm::BlankNode(_) => TermKind::BlankNode, - GenericTerm::Literal(_) => TermKind::Literal, - GenericTerm::Triple(_) => TermKind::Triple, - GenericTerm::Variable(_) => TermKind::Variable, - } - } - - fn borrow_term(&self) -> Self::BorrowTerm<'_> { - self - } - - fn is_iri(&self) -> bool { - matches!(self, GenericTerm::Iri(..)) - } - - fn is_blank_node(&self) -> bool { - matches!(self, GenericTerm::BlankNode(..)) - } - - fn is_literal(&self) -> bool { - matches!(self, GenericTerm::Literal(..)) - } - - fn is_variable(&self) -> bool { - matches!(self, GenericTerm::Variable(..)) - } - - fn is_atom(&self) -> bool { - !matches!(self, GenericTerm::Triple(..)) - } - - fn is_triple(&self) -> bool { - matches!(self, GenericTerm::Triple(..)) - } - - fn iri(&self) -> Option> { - if let GenericTerm::Iri(iri) = self { - Some(iri.as_ref().map_unchecked(MownStr::from_str)) - } else { - None - } - } - - fn bnode_id(&self) -> Option> { - if let GenericTerm::BlankNode(id) = self { - Some(id.as_ref().map_unchecked(MownStr::from_str)) - } else { - None - } - } - - fn lexical_form(&self) -> Option { - if let GenericTerm::Literal(lit) = self { - lit.lexical_form() - } else { - None - } - } - - fn datatype(&self) -> Option> { - if let GenericTerm::Literal(lit) = self { - lit.datatype() - } else { - None - } - } - - fn language_tag(&self) -> Option> { - if let GenericTerm::Literal(lit) = self { - lit.language_tag() - } else { - None - } - } - - fn variable(&self) -> Option> { - if let GenericTerm::Variable(name) = self { - Some(name.as_ref().map_unchecked(MownStr::from_str)) - } else { - None - } - } - - fn triple(&self) -> Option<[Self::BorrowTerm<'_>; 3]> { - if let GenericTerm::Triple(spo) = self { - Some([ - spo[0].borrow_term(), - spo[1].borrow_term(), - spo[2].borrow_term(), - ]) - } else { - None - } - } - - fn to_triple(self) -> Option<[Self; 3]> - where - Self: Sized, - { - if let GenericTerm::Triple(spo) = self { - Some(*spo) - } else { - None - } - } -} - -impl + Debug, T2: Term> PartialEq for GenericTerm { - fn eq(&self, other: &T2) -> bool { - Term::eq(self, other.borrow_term()) - } -} - -impl + Debug> Eq for GenericTerm {} - -impl + Debug> std::hash::Hash for GenericTerm { - fn hash(&self, state: &mut H) { - Term::hash(self, state) - } -} - -impl + Debug, T2: Term> PartialOrd for GenericTerm { - fn partial_cmp(&self, other: &T2) -> Option { - Some(Term::cmp(self, other.borrow_term())) - } -} - -impl + Debug> Ord for GenericTerm { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - Term::cmp(self, other.borrow_term()) - } -} - -impl + for<'x> From<&'x str>> FromTerm for GenericTerm { - fn from_term(term: U) -> Self { - match term.kind() { - TermKind::Iri => { - // the following is safe because we checked term.kind() - let iri = unsafe { term.iri().unwrap_unchecked() }; - GenericTerm::Iri(iri.map_unchecked(|txt| T::from(&txt))) - } - TermKind::Literal => { - // the following is safe because we checked term.kind() - let lit = unsafe { GenericLiteral::try_from_term(term).unwrap_unchecked() }; - GenericTerm::Literal(lit) - } - TermKind::BlankNode => { - // the following is safe because we checked term.kind() - let id = unsafe { term.bnode_id().unwrap_unchecked() }; - GenericTerm::BlankNode(id.map_unchecked(|txt| T::from(&txt))) - } - TermKind::Triple => { - // the following is safe because we checked term.kind() - let spo = unsafe { term.triple().unwrap_unchecked() }; - GenericTerm::Triple(Box::new(spo.map(Self::from_term))) - } - TermKind::Variable => { - // the following is safe because we checked term.kind() - let name = unsafe { term.variable().unwrap_unchecked() }; - GenericTerm::Variable(name.map_unchecked(|txt| T::from(&txt))) - } - } - } -} - -impl> From> for GenericTerm { - fn from(value: IriRef) -> Self { - GenericTerm::Iri(value) - } -} - -impl> From> for GenericTerm { - fn from(value: BnodeId) -> Self { - GenericTerm::BlankNode(value) - } -} - -impl> From<(T, IriRef)> for GenericTerm { - fn from(value: (T, IriRef)) -> Self { - GenericTerm::Literal(GenericLiteral::Typed(value.0, value.1)) - } -} - -impl> From<(T, LanguageTag)> for GenericTerm { - fn from(value: (T, LanguageTag)) -> Self { - GenericTerm::Literal(GenericLiteral::LanguageString(value.0, value.1)) - } -} - -impl> From> for GenericTerm { - fn from(value: VarName) -> Self { - GenericTerm::Variable(value) - } -} - -impl> From; 3]>> for GenericTerm { - fn from(value: Box<[GenericTerm; 3]>) -> Self { - GenericTerm::Triple(value) - } -} - -// - -/// This type is mostly required as one of the variants of [`GenericTerm`]. +/// This type is used in the `Literal` variant of [`ArcTerm`](crate::ArcTerm) and [`RcTerm`](crate::RcTerm). /// /// It can however be used as a specialized [`Term`] implementation. #[derive(Clone, Debug)] @@ -288,15 +58,15 @@ impl + Debug> Term for GenericLiteral { self } - fn lexical_form(&self) -> Option { + fn lexical_form(&self) -> Option { Some(MownStr::from_str(self.get_lexical_form())) } - fn datatype(&self) -> Option> { + fn datatype(&self) -> Option> { Some(self.get_datatype().map_unchecked(MownStr::from_str)) } - fn language_tag(&self) -> Option> { + fn language_tag(&self) -> Option> { self.get_language_tag() .map(|tag| tag.map_unchecked(MownStr::from_str)) } @@ -376,10 +146,7 @@ impl std::error::Error for GenericLiteralError {} #[cfg(test)] mod test { use super::*; - use sophia_api::{ - ns::xsd, - term::{assert_consistent_term_impl, SimpleTerm}, - }; + use sophia_api::{ns::xsd, term::assert_consistent_term_impl}; #[test] fn generic_literal_typed() { @@ -405,63 +172,4 @@ mod test { fn generic_literal_from_iri_errs() { assert!(GenericLiteral::::try_from_term(rdf::type_).is_err()) } - - #[test] - fn generic_term_iri() { - let gt: GenericTerm = rdf::type_.into_term(); - assert_consistent_term_impl(>); - assert_eq!(gt.kind(), TermKind::Iri); - assert!(Term::eq(>.iri().unwrap(), rdf::type_)); - } - - #[test] - fn generic_term_bnode() { - let bn = BnodeId::new_unchecked("x"); - let gt: GenericTerm = bn.into_term(); - assert_consistent_term_impl(>); - assert_eq!(gt.kind(), TermKind::BlankNode); - assert_eq!(>.bnode_id().unwrap(), "x"); - } - - #[test] - fn generic_term_typed_literal() { - let gt: GenericTerm = 42.into_term(); - assert_consistent_term_impl(>); - assert_eq!(gt.kind(), TermKind::Literal); - assert_eq!(gt.lexical_form().unwrap(), "42"); - assert!(Term::eq(>.datatype().unwrap(), xsd::integer)); - } - - #[test] - fn generic_term_language_string() { - let en = LanguageTag::new_unchecked("en"); - let gt: GenericTerm = ("hello" * en).into_term(); - assert_consistent_term_impl(>); - assert_eq!(gt.kind(), TermKind::Literal); - assert_eq!(gt.lexical_form().unwrap(), "hello"); - assert!(Term::eq(>.datatype().unwrap(), rdf::langString)); - assert_eq!(gt.language_tag().unwrap(), en); - } - - #[test] - fn generic_term_triple() { - let spo = [rdf::type_, rdf::type_, rdf::Property].map(Term::into_term); - let qt = SimpleTerm::Triple(Box::new(spo)); - let gt: GenericTerm = qt.into_term(); - assert_consistent_term_impl(>); - assert_eq!(gt.kind(), TermKind::Triple); - let spo = gt.triple().unwrap(); - assert!(Term::eq(spo[0], rdf::type_)); - assert!(Term::eq(spo[1], rdf::type_)); - assert!(Term::eq(spo[2], rdf::Property)); - } - - #[test] - fn generic_term_variable() { - let v = VarName::new_unchecked("x"); - let gt: GenericTerm = v.into_term(); - assert_consistent_term_impl(>); - assert_eq!(gt.kind(), TermKind::Variable); - assert_eq!(>.variable().unwrap(), "x"); - } } diff --git a/term/src/_macro.rs b/term/src/_macro.rs new file mode 100644 index 00000000..93786e43 --- /dev/null +++ b/term/src/_macro.rs @@ -0,0 +1,431 @@ +//! A [stash]($type_name) is a collection of strings that can be reused +//! to avoid allocating identical string multiple times. + +macro_rules! gen_term { + ($type_name: ident, $wrapper: path, $mod_name: ident) => { + mod $mod_name { + use super::*; + use sophia_api::term::{ + BnodeId, FromTerm, IriRef, LanguageTag, Term, TermKind, TryFromTerm, VarName, + }; + use sophia_api::MownStr; + use $wrapper as W; + + #[doc = "An implementation of [`Term`] using "] + #[doc = stringify!($wrapper)] + #[doc = " under the hood."] + #[derive(Clone, Debug)] + pub enum $type_name { + /// A straighforward implementation of [`Term`] as an enum. + /// An [RDF IRI](https://www.w3.org/TR/rdf11-concepts/#section-IRIs) + Iri(IriRef>), + /// An RDF [blank node](https://www.w3.org/TR/rdf11-concepts/#section-blank-nodes) + BlankNode(BnodeId>), + /// An RDF [literal](https://www.w3.org/TR/rdf11-concepts/#section-Graph-Literal) + Literal($crate::GenericLiteral>), + /// An RDF-star [quoted triple](https://www.w3.org/2021/12/rdf-star.html#dfn-quoted) + Triple(W<[Self; 3]>), + /// A SPARQL or Notation3 variable + Variable(VarName>), + } + + impl Term for $type_name { + type BorrowTerm<'x> = &'x Self where Self: 'x; + + fn kind(&self) -> sophia_api::term::TermKind { + match self { + $type_name::Iri(_) => TermKind::Iri, + $type_name::BlankNode(_) => TermKind::BlankNode, + $type_name::Literal(_) => TermKind::Literal, + $type_name::Triple(_) => TermKind::Triple, + $type_name::Variable(_) => TermKind::Variable, + } + } + + fn borrow_term(&self) -> Self::BorrowTerm<'_> { + self + } + + fn is_iri(&self) -> bool { + matches!(self, $type_name::Iri(..)) + } + + fn is_blank_node(&self) -> bool { + matches!(self, $type_name::BlankNode(..)) + } + + fn is_literal(&self) -> bool { + matches!(self, $type_name::Literal(..)) + } + + fn is_variable(&self) -> bool { + matches!(self, $type_name::Variable(..)) + } + + fn is_atom(&self) -> bool { + !matches!(self, $type_name::Triple(..)) + } + + fn is_triple(&self) -> bool { + matches!(self, $type_name::Triple(..)) + } + + fn iri(&self) -> Option> { + if let $type_name::Iri(iri) = self { + Some(iri.as_ref().map_unchecked(MownStr::from_str)) + } else { + None + } + } + + fn bnode_id(&self) -> Option> { + if let $type_name::BlankNode(id) = self { + Some(id.as_ref().map_unchecked(MownStr::from_str)) + } else { + None + } + } + + fn lexical_form(&self) -> Option { + if let $type_name::Literal(lit) = self { + lit.lexical_form() + } else { + None + } + } + + fn datatype(&self) -> Option> { + if let $type_name::Literal(lit) = self { + lit.datatype() + } else { + None + } + } + + fn language_tag(&self) -> Option> { + if let $type_name::Literal(lit) = self { + lit.language_tag() + } else { + None + } + } + + fn variable(&self) -> Option> { + if let $type_name::Variable(name) = self { + Some(name.as_ref().map_unchecked(MownStr::from_str)) + } else { + None + } + } + + fn triple(&self) -> Option<[Self::BorrowTerm<'_>; 3]> { + if let $type_name::Triple(spo) = self { + Some([ + spo[0].borrow_term(), + spo[1].borrow_term(), + spo[2].borrow_term(), + ]) + } else { + None + } + } + + fn to_triple(self) -> Option<[Self; 3]> + where + Self: Sized, + { + if let $type_name::Triple(spo) = self { + Some(spo.as_ref().clone()) + } else { + None + } + } + } + + impl PartialEq for $type_name { + fn eq(&self, other: &T) -> bool { + Term::eq(self, other.borrow_term()) + } + } + + impl Eq for $type_name {} + + impl std::hash::Hash for $type_name { + fn hash(&self, state: &mut H) { + Term::hash(self, state) + } + } + + impl PartialOrd for $type_name { + fn partial_cmp(&self, other: &T) -> Option { + Some(Term::cmp(self, other.borrow_term())) + } + } + + impl Ord for $type_name { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + Term::cmp(self, other.borrow_term()) + } + } + + impl FromTerm for $type_name { + fn from_term(term: U) -> Self { + match term.kind() { + TermKind::Iri => { + // the following is safe because we checked term.kind() + let iri = unsafe { term.iri().unwrap_unchecked() }; + $type_name::Iri(iri.as_ref().map_unchecked(Into::into)) + } + TermKind::Literal => { + // the following is safe because we checked term.kind() + let lit = + unsafe { GenericLiteral::try_from_term(term).unwrap_unchecked() }; + $type_name::Literal(lit) + } + TermKind::BlankNode => { + // the following is safe because we checked term.kind() + let id = unsafe { term.bnode_id().unwrap_unchecked() }; + $type_name::BlankNode(id.as_ref().map_unchecked(Into::into)) + } + TermKind::Triple => { + // the following is safe because we checked term.kind() + let spo = unsafe { term.triple().unwrap_unchecked() }; + $type_name::Triple(W::new(spo.map(Self::from_term))) + } + TermKind::Variable => { + // the following is safe because we checked term.kind() + let name = unsafe { term.variable().unwrap_unchecked() }; + $type_name::Variable(name.as_ref().map_unchecked(Into::into)) + } + } + } + } + + impl From>> for $type_name { + fn from(value: IriRef>) -> Self { + $type_name::Iri(value.map_unchecked(Into::into)) + } + } + + impl From>> for $type_name { + fn from(value: BnodeId>) -> Self { + $type_name::BlankNode(value) + } + } + + impl From<(W, IriRef>)> for $type_name { + fn from(value: (W, IriRef>)) -> Self { + $type_name::Literal(GenericLiteral::Typed(value.0, value.1)) + } + } + + impl From<(W, LanguageTag>)> for $type_name { + fn from(value: (W, LanguageTag>)) -> Self { + $type_name::Literal(GenericLiteral::LanguageString(value.0, value.1)) + } + } + + impl From>> for $type_name { + fn from(value: VarName>) -> Self { + $type_name::Variable(value) + } + } + + impl From> for $type_name { + fn from(value: W<[$type_name; 3]>) -> Self { + $type_name::Triple(value) + } + } + + #[cfg(test)] + mod test { + use super::*; + use sophia_api::{ + ns::{rdf, xsd}, + term::{assert_consistent_term_impl, SimpleTerm}, + }; + + #[test] + fn gen_term_iri() { + let gt: $type_name = rdf::type_.into_term(); + assert_consistent_term_impl(>); + assert_eq!(gt.kind(), TermKind::Iri); + assert!(Term::eq(>.iri().unwrap(), rdf::type_)); + } + + #[test] + fn gen_term_bnode() { + let bn = BnodeId::new_unchecked("x"); + let gt: $type_name = bn.into_term(); + assert_consistent_term_impl(>); + assert_eq!(gt.kind(), TermKind::BlankNode); + assert_eq!(>.bnode_id().unwrap(), "x"); + } + + #[test] + fn gen_term_typed_literal() { + let gt: $type_name = 42.into_term(); + assert_consistent_term_impl(>); + assert_eq!(gt.kind(), TermKind::Literal); + assert_eq!(gt.lexical_form().unwrap(), "42"); + assert!(Term::eq(>.datatype().unwrap(), xsd::integer)); + } + + #[test] + fn gen_term_language_string() { + let en = LanguageTag::new_unchecked("en"); + let gt: $type_name = ("hello" * en).into_term(); + assert_consistent_term_impl(>); + assert_eq!(gt.kind(), TermKind::Literal); + assert_eq!(gt.lexical_form().unwrap(), "hello"); + assert!(Term::eq(>.datatype().unwrap(), rdf::langString)); + assert_eq!(gt.language_tag().unwrap(), en); + } + + #[test] + fn gen_term_triple() { + let spo = [rdf::type_, rdf::type_, rdf::Property].map(Term::into_term); + let qt = SimpleTerm::Triple(Box::new(spo)); + let gt: $type_name = qt.into_term(); + assert_consistent_term_impl(>); + assert_eq!(gt.kind(), TermKind::Triple); + let spo = gt.triple().unwrap(); + assert!(Term::eq(spo[0], rdf::type_)); + assert!(Term::eq(spo[1], rdf::type_)); + assert!(Term::eq(spo[2], rdf::Property)); + } + + #[test] + fn gen_term_variable() { + let v = VarName::new_unchecked("x"); + let gt: $type_name = v.into_term(); + assert_consistent_term_impl(>); + assert_eq!(gt.kind(), TermKind::Variable); + assert_eq!(>.variable().unwrap(), "x"); + } + } + } + pub use $mod_name::$type_name; + }; +} + +macro_rules! gen_stash { + ($type_name: ident, $term_type: ident, $wrapper: path, $mod_name: ident) => { + mod $mod_name { + use std::{borrow::Borrow, collections::BTreeSet}; + use $wrapper as W; + + use sophia_api::term::{BnodeId, IriRef, LanguageTag, SimpleTerm, Term, VarName}; + + use crate::{$term_type, GenericLiteral}; + + #[doc = "A stash is a collection of strings that can be reused for generating [`"] + #[doc = stringify!($term_type)] + #[doc = "`] without reallocating identical strings multiple times, "] + #[doc = "using the `copy_term` method."] + #[derive(Clone, Debug)] + pub struct $type_name { + store: BTreeSet>, + } + + impl $type_name { + /// Create a new empty stash + pub fn new() -> Self { + Default::default() + } + + /// Retrieve a value from the stash, if present + pub fn get(&self, probe: &str) -> Option<&W> { + self.store.get(probe) + } + + /// Retrieve a value from the stash, inserting it if not present + pub fn get_or_insert(&mut self, probe: &str) -> &W { + if !self.store.contains(probe) { + let ins = W::from(probe); + self.store.insert(ins); + } + let ret = self.store.get(probe); + debug_assert!(ret.is_some()); + // this is safe because we just inserted this element + unsafe { ret.unwrap_unchecked() } + } + + /// How many values are stored in this stash + pub fn len(&self) -> usize { + self.store.len() + } + + /// Is this stash empty? + pub fn is_empty(&self) -> bool { + self.store.is_empty() + } + } + + impl Default for $type_name { + fn default() -> Self { + Self { + store: BTreeSet::default(), + } + } + } + + impl $type_name { + /// Copy any [`Borrow`] into an `T` backed on this stash. + pub fn copy_str>(&mut self, iri: U) -> W { + self.get_or_insert(iri.borrow()).clone() + } + + /// Copy any [`IriRef`] into an [`IriRef>`] backed on this stash. + pub fn copy_iri>(&mut self, iri: IriRef) -> IriRef> { + IriRef::new_unchecked(self.copy_str(iri)) + } + + /// Copy any [`BnodeId`] into an [`BnodeId>`] backed on this stash. + pub fn copy_bnode_id>( + &mut self, + bnid: BnodeId, + ) -> BnodeId> { + BnodeId::new_unchecked(self.copy_str(bnid)) + } + + /// Copy any [`LanguageTag`] into an [`LanguageTag>`] backed on this stash. + pub fn copy_language_tag>( + &mut self, + tag: LanguageTag, + ) -> LanguageTag> { + LanguageTag::new_unchecked(self.copy_str(tag)) + } + + /// Copy any [`VarName`] into an [`VarName>`] backed on this stash. + pub fn copy_var_name>(&mut self, vn: VarName) -> VarName> { + VarName::new_unchecked(self.copy_str(vn)) + } + + #[doc = "Copy any [`Term`] into an [`"] + #[doc = stringify!($term_type)] + #[doc = "`] backed on this stash."] + pub fn copy_term(&mut self, t: U) -> $term_type { + use SimpleTerm::*; + match t.as_simple() { + Iri(iri) => $term_type::Iri(self.copy_iri(iri)), + BlankNode(bnid) => $term_type::BlankNode(self.copy_bnode_id(bnid)), + LiteralDatatype(lex, dt) => $term_type::Literal(GenericLiteral::Typed( + self.copy_str(lex), + self.copy_iri(dt), + )), + LiteralLanguage(lex, tag) => { + $term_type::Literal(GenericLiteral::LanguageString( + self.copy_str(lex), + self.copy_language_tag(tag), + )) + } + Triple(tr) => $term_type::Triple(W::new(tr.map(|t| self.copy_term(t)))), + Variable(vn) => $term_type::Variable(self.copy_var_name(vn)), + } + } + } + } + pub use $mod_name::$type_name; + }; +} diff --git a/term/src/_stash.rs b/term/src/_stash.rs deleted file mode 100644 index 121daf5d..00000000 --- a/term/src/_stash.rs +++ /dev/null @@ -1,116 +0,0 @@ -//! A [stash](GenericStash) is a collection of strings that can be reused -//! to avoid allocating identical string multiple times. - -use std::{borrow::Borrow, collections::BTreeSet}; - -use sophia_api::term::{BnodeId, IriRef, LanguageTag, SimpleTerm, Term, VarName}; - -use crate::{GenericLiteral, GenericTerm}; - -/// A [stash](GenericStash) is a collection of strings that can be reused -/// to avoid allocating identical string multiple times. -#[derive(Clone, Debug)] -pub struct GenericStash { - store: BTreeSet, -} - -impl GenericStash -where - T: Ord, -{ - /// Create a new empty stash - pub fn new() -> Self { - Default::default() - } - - /// Retrieve a value from the stash, if present - pub fn get(&self, probe: &Q) -> Option<&T> - where - T: Borrow, - Q: Ord + ?Sized, - { - self.store.get(probe) - } - - /// Retrieve a value from the stash, inserting it if not present - pub fn get_or_insert(&mut self, probe: &Q) -> &T - where - T: Borrow + for<'x> From<&'x Q>, - Q: Ord + ?Sized, - { - if !self.store.contains(probe) { - let ins = T::from(probe); - self.store.insert(ins); - } - let ret = self.store.get(probe); - debug_assert!(ret.is_some()); - // this is safe because we just inserted this element - unsafe { ret.unwrap_unchecked() } - } - - /// How many values are stored in this stash - pub fn len(&self) -> usize { - self.store.len() - } - - /// Is this stash empty? - pub fn is_empty(&self) -> bool { - self.store.is_empty() - } -} - -impl Default for GenericStash { - fn default() -> Self { - Self { - store: BTreeSet::default(), - } - } -} - -impl GenericStash -where - T: Ord + for<'x> From<&'x str> + Borrow + Clone, -{ - /// Copy any [`Borrow`] into an `T` backed on this stash. - pub fn copy_str>(&mut self, iri: U) -> T { - self.get_or_insert(iri.borrow()).clone() - } - - /// Copy any [`IriRef`] into an [`IriRef`] backed on this stash. - pub fn copy_iri>(&mut self, iri: IriRef) -> IriRef { - IriRef::new_unchecked(self.copy_str(iri)) - } - - /// Copy any [`BnodeId`] into an [`BnodeId`] backed on this stash. - pub fn copy_bnode_id>(&mut self, bnid: BnodeId) -> BnodeId { - BnodeId::new_unchecked(self.copy_str(bnid)) - } - - /// Copy any [`LanguageTag`] into an [`LanguageTag`] backed on this stash. - pub fn copy_language_tag>(&mut self, tag: LanguageTag) -> LanguageTag { - LanguageTag::new_unchecked(self.copy_str(tag)) - } - - /// Copy any [`VarName`] into an [`VarName`] backed on this stash. - pub fn copy_var_name>(&mut self, vn: VarName) -> VarName { - VarName::new_unchecked(self.copy_str(vn)) - } - - /// Copy any [`Term`] into an [`GenericTerm`] backed on this stash. - pub fn copy_term(&mut self, t: U) -> GenericTerm { - use SimpleTerm::*; - match t.as_simple() { - Iri(iri) => GenericTerm::Iri(self.copy_iri(iri)), - BlankNode(bnid) => GenericTerm::BlankNode(self.copy_bnode_id(bnid)), - LiteralDatatype(lex, dt) => { - GenericTerm::Literal(GenericLiteral::Typed(self.copy_str(lex), self.copy_iri(dt))) - } - LiteralLanguage(lex, tag) => GenericTerm::Literal(GenericLiteral::LanguageString( - self.copy_str(lex), - self.copy_language_tag(tag), - )), - Triple(tr) => GenericTerm::Triple(Box::new(tr.map(|t| self.copy_term(t)))), - Variable(vn) => GenericTerm::Variable(self.copy_var_name(vn)), - } - } -} diff --git a/term/src/lib.rs b/term/src/lib.rs index 4e82aa2a..11bd0c2d 100644 --- a/term/src/lib.rs +++ b/term/src/lib.rs @@ -1,34 +1,22 @@ -//! I define implementations of [`sophia_api::term::Term`] -//! as well as associated types. +//! I define implementations of [`sophia_api::term::Term`]: +//! * [`ArcTerm`] using [`Arc`](std::sync::Arc) as the underlying text, +//! making it cheap to clone and thread-safe; +//! see also [`ArcStrStash`]. +//! * [`RcTerm`] using [`Rc`](std::rc::Rc) as the underlying text, +//! making it cheap to clone; +//! see also [`RcStrStash`]. #![deny(missing_docs)] -use std::{rc::Rc, sync::Arc}; mod _generic; pub use _generic::*; -mod _stash; -pub use _stash::*; +#[macro_use] +mod _macro; -/// A [`Term`](sophia_api::term::Term) implementation -/// using [`Arc`] as the underlying text, -/// making it cheap to clone and thread-safe. -/// -/// See also [`ArcStrStash`]. -pub type ArcTerm = GenericTerm>; +gen_term!(ArcTerm, std::sync::Arc, arc_term); +gen_term!(RcTerm, std::rc::Rc, rc_term); -/// A [`Term`](sophia_api::term::Term) implementation -/// using [`Rc`] as the underlying text, -/// making it cheap to clone. -/// -/// See also [`RcStrStash`]. -pub type RcTerm = GenericTerm>; - -/// A stash for generating [`ArcTerm`]s (with [`copy_term`](GenericStash::copy_term)) -/// or any [`Arc`]. -pub type ArcStrStash = GenericStash>; - -/// A stash for generating [`RcTerm`]s (with [`copy_term`](GenericStash::copy_term)) -/// or any [`Rc`]. -pub type RcStrStash = GenericStash>; +gen_stash!(ArcStrStash, ArcTerm, std::sync::Arc, arc_stash); +gen_stash!(RcStrStash, RcTerm, std::rc::Rc, rc_stash); #[cfg(test)] mod test {