use sophia_api::{
MownStr,
quad::Spog,
term::{BaseDirection, BnodeId, IriRef, LanguageTag, Term, TermKind, VarName},
};
use crate::_stash::StringStash;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum IndexedTerm {
Iri(usize),
RdfBuiltin(IriRef<&'static str>),
BlankNode(usize),
TypedLiteral(usize, usize),
LangString(usize, usize, Option<BaseDirection>),
XsdLiteral(usize, IriRef<&'static str>),
BoolLiteral(bool),
TripleTerm,
Reifier,
Rest,
Variable(usize),
}
impl IndexedTerm {
pub fn copy_term<T: Term>(
t: T,
stack: &mut Vec<IndexedTerm>,
buffers: &mut StringStash,
) -> usize {
use sophia_api::term::SimpleTerm::*;
match t.as_simple() {
Iri(iri_ref) => {
let i = buffers.len();
buffers.push().push_str(&iri_ref);
stack.push(IndexedTerm::Iri(i));
}
BlankNode(bnode_id) => {
let i = buffers.len();
buffers.push().push_str(&bnode_id);
stack.push(IndexedTerm::BlankNode(i));
}
LiteralDatatype(lex, dt) => {
let ilex = buffers.len();
buffers.push().push_str(&lex);
buffers.push().push_str(&dt);
stack.push(IndexedTerm::TypedLiteral(ilex, ilex + 1));
}
LiteralLanguage(lex, tag, dir) => {
let ilex = buffers.len();
buffers.push().push_str(&lex);
buffers.push().push_str(&tag);
stack.push(IndexedTerm::LangString(ilex, ilex + 1, dir));
}
Triple(t) => {
Self::copy_term(&t[0], stack, buffers);
Self::copy_term(&t[1], stack, buffers);
Self::copy_term(&t[2], stack, buffers);
stack.push(IndexedTerm::TripleTerm);
}
Variable(var_name) => {
let i = buffers.len();
buffers.push().push_str(&var_name);
stack.push(IndexedTerm::Variable(i));
}
}
stack.len() - 1
}
pub fn pop(stack: &mut Vec<IndexedTerm>, buffers: &mut StringStash) {
use IndexedTerm::*;
match stack.pop().unwrap() {
Iri(_) | BlankNode(_) | XsdLiteral(..) | Variable(..) => buffers.pop(),
TypedLiteral(..) | LangString(..) => {
for _ in 0..2 {
buffers.pop()
}
}
TripleTerm => {
for _ in 0..3 {
Self::pop(stack, buffers);
}
}
Reifier | RdfBuiltin(_) | BoolLiteral(_) | Rest => (),
}
}
}
pub(crate) trait IndexedTermSliceExt: AsRef<[IndexedTerm]> {
fn predecessor(&self, i: usize) -> Option<usize> {
if matches!(self.as_ref()[i], IndexedTerm::TripleTerm) {
let oi = i - 1;
let pi = self.predecessor(oi).unwrap();
debug_assert!(!matches!(self.as_ref()[pi - 1], IndexedTerm::TripleTerm));
debug_assert!(!matches!(self.as_ref()[pi - 2], IndexedTerm::TripleTerm));
Some(pi - 2)
} else if i > 0 {
Some(i - 1)
} else {
None
}
}
fn generalized_predecessor(&self, i: usize) -> Option<usize> {
if matches!(self.as_ref()[i], IndexedTerm::TripleTerm) {
let oi = i - 1;
let pi = self.generalized_predecessor(oi).unwrap();
let si = self.generalized_predecessor(pi).unwrap();
self.generalized_predecessor(si)
} else if i > 0 {
Some(i - 1)
} else {
None
}
}
}
impl<T: AsRef<[IndexedTerm]>> IndexedTermSliceExt for T {}
#[derive(Clone, Copy, Debug)]
pub struct StashedTerm<'a> {
pub(crate) terms: &'a [IndexedTerm],
pub(crate) buffers: &'a StringStash,
pub(crate) idx: usize,
}
impl<'a> StashedTerm<'a> {
pub(crate) fn new_triple(terms: &'a [IndexedTerm], buffers: &'a StringStash) -> [Self; 3] {
debug_assert!(terms.len() >= 3);
let o = terms.len() - 1;
let p = terms.predecessor(o).unwrap();
debug_assert!(matches!(
terms[p],
IndexedTerm::Iri(..)
| IndexedTerm::RdfBuiltin(..)
| IndexedTerm::Reifier
| IndexedTerm::Rest
));
let s = p - 1;
[
Self {
terms,
buffers,
idx: s,
},
Self {
terms,
buffers,
idx: p,
},
Self {
terms,
buffers,
idx: o,
},
]
}
pub(crate) fn new_spog(terms: &'a [IndexedTerm], buffers: &'a StringStash) -> Spog<Self> {
debug_assert!(terms.len() >= 4);
let g = terms.len() - 1;
debug_assert!(matches!(
terms[g],
IndexedTerm::Iri(..) | IndexedTerm::BlankNode(..)
));
let o = g - 1;
let p = terms.predecessor(o).unwrap();
debug_assert!(matches!(
terms[p],
IndexedTerm::Iri(..)
| IndexedTerm::RdfBuiltin(..)
| IndexedTerm::Reifier
| IndexedTerm::Rest
));
let s = p - 1;
(
[
Self {
terms,
buffers,
idx: s,
},
Self {
terms,
buffers,
idx: p,
},
Self {
terms,
buffers,
idx: o,
},
],
Some(Self {
terms,
buffers,
idx: g,
}),
)
}
pub(crate) fn new_generalized_triple(
terms: &'a [IndexedTerm],
buffers: &'a StringStash,
) -> [Self; 3] {
debug_assert!(terms.len() >= 3);
let o = terms.len() - 1;
let p = terms.generalized_predecessor(o).unwrap();
let s = terms.generalized_predecessor(p).unwrap();
[
Self {
terms,
buffers,
idx: s,
},
Self {
terms,
buffers,
idx: p,
},
Self {
terms,
buffers,
idx: o,
},
]
}
pub(crate) fn new_generalized_spog(
terms: &'a [IndexedTerm],
buffers: &'a StringStash,
) -> Spog<Self> {
debug_assert!(terms.len() >= 4);
let g = terms.len() - 1;
let o = terms.generalized_predecessor(g).unwrap();
let p = terms.generalized_predecessor(o).unwrap();
let s = terms.generalized_predecessor(p).unwrap();
(
[
Self {
terms,
buffers,
idx: s,
},
Self {
terms,
buffers,
idx: p,
},
Self {
terms,
buffers,
idx: o,
},
],
Some(Self {
terms,
buffers,
idx: g,
}),
)
}
pub(crate) fn indexed_term(&self) -> IndexedTerm {
self.terms[self.idx]
}
pub(crate) fn buf(&self, i: usize) -> MownStr<'_> {
MownStr::from_ref(&self.buffers[i])
}
}
impl<'a> Term for StashedTerm<'a> {
type BorrowTerm<'x>
= Self
where
Self: 'x;
fn kind(&self) -> sophia_api::prelude::TermKind {
match self.indexed_term() {
IndexedTerm::Iri(..) => TermKind::Iri,
IndexedTerm::RdfBuiltin(_) => TermKind::Iri,
IndexedTerm::BlankNode(..) => TermKind::BlankNode,
IndexedTerm::TypedLiteral(..) => TermKind::Literal,
IndexedTerm::LangString(..) => TermKind::Literal,
IndexedTerm::XsdLiteral(..) => TermKind::Literal,
IndexedTerm::BoolLiteral(_) => TermKind::Literal,
IndexedTerm::TripleTerm => TermKind::Triple,
IndexedTerm::Reifier => TermKind::Iri,
IndexedTerm::Rest => TermKind::Iri,
IndexedTerm::Variable(..) => TermKind::Variable,
}
}
fn borrow_term(&self) -> Self::BorrowTerm<'_> {
*self
}
fn iri(&self) -> Option<IriRef<MownStr<'_>>> {
match self.indexed_term() {
IndexedTerm::Iri(bi) => Some(IriRef::new_unchecked(self.buf(bi))),
IndexedTerm::RdfBuiltin(iriref) => {
debug_assert!(
iriref.starts_with('#'),
"{iriref:?} does not start with '#'"
);
Some(IriRef::new_unchecked(
format!("http://www.w3.org/1999/02/22-rdf-syntax-ns{iriref}").into(),
))
}
IndexedTerm::Reifier => Some(IriRef::new_unchecked(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies".into(),
)),
IndexedTerm::Rest => Some(IriRef::new_unchecked(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#rest".into(),
)),
_ => None,
}
}
fn bnode_id(&self) -> Option<BnodeId<MownStr<'_>>> {
match self.indexed_term() {
IndexedTerm::BlankNode(bi) => Some(BnodeId::new_unchecked(self.buf(bi))),
_ => None,
}
}
fn lexical_form(&self) -> Option<MownStr<'_>> {
match self.indexed_term() {
IndexedTerm::TypedLiteral(bi, _)
| IndexedTerm::LangString(bi, _, _)
| IndexedTerm::XsdLiteral(bi, _) => Some(self.buf(bi)),
IndexedTerm::BoolLiteral(b) => Some((if b { "true" } else { "false" }).into()),
_ => None,
}
}
fn datatype(&self) -> Option<IriRef<MownStr<'_>>> {
match self.indexed_term() {
IndexedTerm::TypedLiteral(_, bi) => Some(IriRef::new_unchecked(self.buf(bi))),
IndexedTerm::LangString(.., None) => Some(IriRef::new_unchecked(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#langString".into(),
)),
IndexedTerm::LangString(.., Some(_)) => Some(IriRef::new_unchecked(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#dirLangString".into(),
)),
IndexedTerm::XsdLiteral(_, iri_ref) => Some(xsd_dt(&iri_ref)),
IndexedTerm::BoolLiteral(_) => Some(xsd_dt("#boolean")),
_ => None,
}
}
fn language_tag(&self) -> Option<LanguageTag<MownStr<'_>>> {
match self.indexed_term() {
IndexedTerm::LangString(_, bi, _) => Some(LanguageTag::new_unchecked(self.buf(bi))),
_ => None,
}
}
fn base_direction(&self) -> Option<sophia_api::term::BaseDirection> {
match self.indexed_term() {
IndexedTerm::LangString(_, _, dir) => dir,
_ => None,
}
}
fn triple(&self) -> Option<[Self::BorrowTerm<'_>; 3]> {
if self.indexed_term() == IndexedTerm::TripleTerm {
let oi = self.idx - 1;
let pi = self.terms.generalized_predecessor(oi).unwrap();
let si = self.terms.generalized_predecessor(pi).unwrap();
Some([
Self { idx: si, ..*self },
Self { idx: pi, ..*self },
Self { idx: oi, ..*self },
])
} else {
None
}
}
fn to_triple(self) -> Option<[Self; 3]>
where
Self: Sized,
{
self.triple()
}
fn variable(&self) -> Option<VarName<MownStr<'_>>> {
match self.indexed_term() {
IndexedTerm::Variable(bi) => Some(VarName::new_unchecked(self.buf(bi))),
_ => None,
}
}
}
fn xsd_dt(suffix: &str) -> IriRef<MownStr<'static>> {
debug_assert!(IriRef::new(suffix).is_ok());
debug_assert!(suffix.starts_with('#'));
IriRef::new_unchecked(format!("http://www.w3.org/2001/XMLSchema{suffix}").into())
}
#[cfg(test)]
mod test {
use super::*;
use sophia_api::{
ns::{rdf, xsd},
term::{SimpleTerm, assert_consistent_term_impl},
triple::Triple,
};
use test_case::test_case;
#[test]
fn iri() {
let terms = [IndexedTerm::Iri(0)];
let buffers = stash(&["tag:s"]);
let term = StashedTerm {
terms: &terms,
buffers: &buffers,
idx: terms.len() - 1,
};
assert_consistent_term_impl(&term);
assert_eq!(TermKind::Iri, term.kind());
assert_eq!(buffers[0].as_str(), term.iri().unwrap().unwrap());
}
#[test]
fn rdf_builtin() {
let terms = [IndexedTerm::RdfBuiltin(IriRef::new_unchecked("#type"))];
let buffers = stash(&[]);
let term = StashedTerm {
terms: &terms,
buffers: &buffers,
idx: terms.len() - 1,
};
assert_consistent_term_impl(&term);
assert_eq!(TermKind::Iri, term.kind());
assert_eq!(rdf::type_, term.iri().unwrap());
}
#[test]
fn bnode() {
let terms = [IndexedTerm::BlankNode(0)];
let buffers = stash(&["bn0001"]);
let term = StashedTerm {
terms: &terms,
buffers: &buffers,
idx: terms.len() - 1,
};
assert_consistent_term_impl(&term);
assert_eq!(TermKind::BlankNode, term.kind());
assert_eq!(buffers[0].as_str(), term.bnode_id().unwrap().as_str());
}
#[test]
fn typed_literal() {
let terms = [IndexedTerm::TypedLiteral(0, 1)];
let buffers = stash(&["hello", "tag:dt"]);
let term = StashedTerm {
terms: &terms,
buffers: &buffers,
idx: terms.len() - 1,
};
assert_consistent_term_impl(&term);
assert_eq!(TermKind::Literal, term.kind());
assert_eq!(buffers[0].as_str(), term.lexical_form().unwrap());
assert_eq!(buffers[1].as_str(), term.datatype().unwrap().unwrap());
}
#[test]
fn lang_string() {
let terms = [IndexedTerm::LangString(0, 1, None)];
let buffers = stash(&["chat", "fr"]);
let term = StashedTerm {
terms: &terms,
buffers: &buffers,
idx: terms.len() - 1,
};
assert_consistent_term_impl(&term);
assert_eq!(TermKind::Literal, term.kind());
assert_eq!(buffers[0].as_str(), term.lexical_form().unwrap());
assert_eq!(rdf::langString, term.datatype().unwrap());
}
#[test]
fn lang_string_dir() {
let terms = [IndexedTerm::LangString(0, 1, Some(BaseDirection::Ltr))];
let buffers = stash(&["chat", "fr"]);
let term = StashedTerm {
terms: &terms,
buffers: &buffers,
idx: terms.len() - 1,
};
assert_consistent_term_impl(&term);
assert_eq!(TermKind::Literal, term.kind());
assert_eq!(buffers[0].as_str(), term.lexical_form().unwrap());
assert_eq!(rdf::dirLangString, term.datatype().unwrap());
}
#[test]
fn xsd_literal() {
let terms = [IndexedTerm::XsdLiteral(
0,
IriRef::new_unchecked("#integer"),
)];
let buffers = stash(&["42"]);
let term = StashedTerm {
terms: &terms,
buffers: &buffers,
idx: terms.len() - 1,
};
assert_consistent_term_impl(&term);
assert_eq!(TermKind::Literal, term.kind());
assert_eq!(buffers[0].as_str(), term.lexical_form().unwrap());
assert_eq!(xsd::integer, term.datatype().unwrap());
}
#[test_case(true)]
#[test_case(false)]
fn bool_literal(val: bool) {
let terms = [IndexedTerm::BoolLiteral(val)];
let buffers = stash(&[]);
let term = StashedTerm {
terms: &terms,
buffers: &buffers,
idx: terms.len() - 1,
};
assert_consistent_term_impl(&term);
assert_eq!(TermKind::Literal, term.kind());
assert_eq!(
term.lexical_form().unwrap(),
if val { "true" } else { "false" }
);
assert_eq!(xsd::boolean, term.datatype().unwrap());
}
#[test]
fn triple_term() {
let terms = [
IndexedTerm::Iri(0),
IndexedTerm::Iri(1),
IndexedTerm::Iri(2),
IndexedTerm::TripleTerm,
];
let buffers = stash(&["tag:s", "tag:p", "tag:o"]);
let term = StashedTerm {
terms: &terms,
buffers: &buffers,
idx: terms.len() - 1,
};
assert_consistent_term_impl(&term);
assert_eq!(TermKind::Triple, term.kind());
let t = term.triple().unwrap();
assert_eq!("tag:s", t.s().iri().unwrap().unwrap());
assert_eq!("tag:p", t.p().iri().unwrap().unwrap());
assert_eq!("tag:o", t.o().iri().unwrap().unwrap());
let t = term.to_triple().unwrap();
assert_eq!("tag:s", t.s().iri().unwrap().unwrap());
assert_eq!("tag:p", t.p().iri().unwrap().unwrap());
assert_eq!("tag:o", t.o().iri().unwrap().unwrap());
}
#[test]
fn triple_term_nested() {
let terms = [
IndexedTerm::Iri(0),
IndexedTerm::Iri(1),
IndexedTerm::Iri(2),
IndexedTerm::Iri(3),
IndexedTerm::Iri(4),
IndexedTerm::TripleTerm,
IndexedTerm::TripleTerm,
];
let buffers = stash(&["tag:s", "tag:p", "tag:a", "tag:b", "tag:c"]);
let term = StashedTerm {
terms: &terms,
buffers: &buffers,
idx: terms.len() - 1,
};
assert_consistent_term_impl(&term);
assert_eq!(TermKind::Triple, term.kind());
let t = term.triple().unwrap();
assert_eq!("tag:s", t.s().iri().unwrap().unwrap());
assert_eq!("tag:p", t.p().iri().unwrap().unwrap());
assert!(t.o().is_triple());
let t = term.to_triple().unwrap();
assert_eq!("tag:s", t.s().iri().unwrap().unwrap());
assert_eq!("tag:p", t.p().iri().unwrap().unwrap());
assert!(t.o().is_triple());
}
#[test]
fn reifier() {
let terms = [IndexedTerm::Reifier];
let buffers = stash(&[]);
let term = StashedTerm {
terms: &terms,
buffers: &buffers,
idx: terms.len() - 1,
};
assert_consistent_term_impl(&term);
assert_eq!(TermKind::Iri, term.kind());
assert_eq!(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies",
term.iri().unwrap().unwrap()
);
}
#[test]
fn rest() {
let terms = [IndexedTerm::Rest];
let buffers = stash(&[]);
let term = StashedTerm {
terms: &terms,
buffers: &buffers,
idx: terms.len() - 1,
};
assert_consistent_term_impl(&term);
assert_eq!(TermKind::Iri, term.kind());
assert_eq!(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#rest",
term.iri().unwrap().unwrap()
);
}
#[test]
fn variable() {
let terms = [IndexedTerm::Variable(0)];
let buffers = stash(&["x"]);
let term = StashedTerm {
terms: &terms,
buffers: &buffers,
idx: terms.len() - 1,
};
assert_consistent_term_impl(&term);
assert_eq!(TermKind::Variable, term.kind());
assert_eq!(buffers[0].as_str(), term.variable().unwrap().as_str());
}
#[test]
fn multiple_terms() {
let terms = [
IndexedTerm::TypedLiteral(0, 1),
IndexedTerm::LangString(2, 3, None),
IndexedTerm::LangString(4, 5, Some(BaseDirection::Ltr)),
IndexedTerm::XsdLiteral(6, IriRef::new_unchecked("#integer")),
IndexedTerm::BoolLiteral(true),
IndexedTerm::Iri(7),
IndexedTerm::RdfBuiltin(IriRef::new_unchecked("#type")),
IndexedTerm::BlankNode(8),
IndexedTerm::TripleTerm,
];
let buffers = stash(&[
"hello", "tag:dt", "chat", "fr", "chat", "fr", "42", "tag:s", "bn0001",
]);
for _ in 0..terms.len() {
let term = StashedTerm {
terms: &terms,
buffers: &buffers,
idx: terms.len() - 1,
};
assert_consistent_term_impl(&term);
}
}
fn stash(strs: &[&str]) -> StringStash {
let mut ret = StringStash::default();
for s in strs {
ret.push().push_str(s);
}
ret
}
#[test]
fn copy_iri() {
let t = IriRef::new_unchecked("https://example.org/");
let mut stack = vec![];
let mut buffers = StringStash::default();
let idx = IndexedTerm::copy_term(t, &mut stack, &mut buffers);
let got = StashedTerm {
terms: &stack,
buffers: &buffers,
idx,
};
assert!(Term::eq(&got, t));
}
#[test]
fn copy_bnode() {
let t = BnodeId::new_unchecked("b001");
let mut stack = vec![];
let mut buffers = StringStash::default();
let idx = IndexedTerm::copy_term(t, &mut stack, &mut buffers);
let got = StashedTerm {
terms: &stack,
buffers: &buffers,
idx,
};
assert!(Term::eq(&got, t));
}
#[test]
fn copy_typed_literal() {
let t = 42;
let mut stack = vec![];
let mut buffers = StringStash::default();
let idx = IndexedTerm::copy_term(t, &mut stack, &mut buffers);
let got = StashedTerm {
terms: &stack,
buffers: &buffers,
idx,
};
assert!(Term::eq(&got, t));
}
#[test]
fn copy_language_string() {
let t = "chat" * LanguageTag::new_unchecked("fr");
let mut stack = vec![];
let mut buffers = StringStash::default();
let idx = IndexedTerm::copy_term(t, &mut stack, &mut buffers);
let got = StashedTerm {
terms: &stack,
buffers: &buffers,
idx,
};
assert!(Term::eq(&got, t));
}
#[test]
fn copy_directional_language_string() {
let t = "chat" * LanguageTag::new_unchecked("fr") * BaseDirection::Ltr;
let mut stack = vec![];
let mut buffers = StringStash::default();
let idx = IndexedTerm::copy_term(&t, &mut stack, &mut buffers);
let got = StashedTerm {
terms: &stack,
buffers: &buffers,
idx,
};
assert!(Term::eq(&got, t));
}
#[test]
fn copy_triple() {
let t = SimpleTerm::from_triple([
BnodeId::new_unchecked("s").as_simple(),
IriRef::new_unchecked("x:p").as_simple(),
"o".as_simple(),
]);
let mut stack = vec![];
let mut buffers = StringStash::default();
let idx = IndexedTerm::copy_term(&t, &mut stack, &mut buffers);
let got = StashedTerm {
terms: &stack,
buffers: &buffers,
idx,
};
assert!(Term::eq(&got, t));
}
#[test]
fn copy_variable() {
let t = VarName::new_unchecked("x");
let mut stack = vec![];
let mut buffers = StringStash::default();
let idx = IndexedTerm::copy_term(t, &mut stack, &mut buffers);
let got = StashedTerm {
terms: &stack,
buffers: &buffers,
idx,
};
assert!(Term::eq(&got, t));
}
}