use std::io::BufRead;
use sophia_api::{
parser::QuadParser,
quad::Spog,
source::{Source, StreamError::SinkError, StreamResult},
version::Version,
};
use sophia_iri::resolve::BaseIriRef;
use crate::{
_term::StashedTerm,
lazy_regex,
parser::{
_common::{Extra, GenericSource, Inner, State, TxSource},
_error::{Error, ErrorKind, ResultExt},
},
};
mod _state;
use _state::*;
sophia_api::def_mod_functions_for_bufread_parser!(TriGParser, QuadParser);
#[derive(Clone, Debug, Default)]
pub struct TriGParser {
pub base: Option<BaseIriRef<Box<str>>>,
pub version: Version,
pub preserve_bn_labels: bool,
}
impl TriGParser {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_base(self, base: Option<BaseIriRef<Box<str>>>) -> Self {
Self { base, ..self }
}
#[must_use]
pub fn with_version(self, version: Version) -> Self {
Self { version, ..self }
}
#[must_use]
pub fn with_preserve_bn_labels(self, preserve_bn_labels: bool) -> Self {
Self {
preserve_bn_labels,
..self
}
}
}
impl<I: BufRead> QuadParser<I> for TriGParser {
type Source = TriGSource<I>;
fn parse(&self, data: I) -> Self::Source {
TriGSource {
input: data,
inner: Inner::new(self.version, self.preserve_bn_labels),
extra: Extra {
base: self.base.clone(),
..Default::default()
},
}
}
}
#[derive(Clone, Debug)]
pub struct TriGSource<I> {
input: I,
inner: Inner,
extra: Extra<TriGState>,
}
impl<I> GenericSource for TriGSource<I> {
type Output<'x> = Spog<StashedTerm<'x>>;
fn inner(&self) -> &Inner {
&self.inner
}
fn inner_mut(&mut self) -> &mut Inner {
&mut self.inner
}
fn emit_tuple<C, E, E2>(&self, callback: &mut C, offset: usize) -> StreamResult<usize, E, E2>
where
C: FnMut(Self::Output<'_>) -> Result<(), E2>,
E: std::error::Error,
E2: std::error::Error,
{
let g = match self.extra.state[0] {
TriGState::NamedGraph(has_name) => {
debug_assert!(has_name);
debug_assert!(!self.inner.terms.is_empty());
Some(StashedTerm {
terms: &self.inner.terms,
buffers: &self.inner.buffers,
idx: 0,
})
}
_ => None,
};
let spo = StashedTerm::new_triple(&self.inner.terms, &self.inner.buffers);
callback((spo, g)).map_err(SinkError).map(|_| offset)
}
}
impl<I> TxSource<TriGState> for TriGSource<I> {
fn extra(&self) -> &Extra<TriGState> {
&self.extra
}
fn extra_mut(&mut self) -> &mut Extra<TriGState> {
&mut self.extra
}
fn inner_and_extra_mut(&mut self) -> (&mut Inner, &mut Extra<TriGState>) {
(&mut self.inner, &mut self.extra)
}
fn emit_reification_tuple<C, E, E2>(
&self,
callback: &mut C,
offset: usize,
) -> StreamResult<usize, E, E2>
where
C: FnMut(Self::Output<'_>) -> Result<(), E2>,
E: std::error::Error,
E2: std::error::Error,
{
debug_assert!(!self.extra.state.is_empty());
let g = match self.extra.state[0] {
TriGState::NamedGraph(has_name) => {
debug_assert!(has_name);
debug_assert!(!self.inner.terms.is_empty());
Some(StashedTerm {
terms: &self.inner.terms,
buffers: &self.inner.buffers,
idx: 0,
})
}
_ => None,
};
let [o, p, s] = StashedTerm::new_triple(&self.inner.terms, &self.inner.buffers);
callback(([s, p, o], g)).map_err(SinkError).map(|_| offset)
}
fn parse_token<C, E>(&mut self, txt: &str, callback: &mut C) -> StreamResult<usize, Error, E>
where
C: FnMut(<Self as GenericSource>::Output<'_>) -> Result<(), E>,
E: std::error::Error + Send + Sync + 'static,
{
use TriGState::*;
debug_assert!(
!txt.is_empty()
&& (self.ws(txt) == 0
|| matches!(self.extra.state.last(), Some(StringLiteralLong(_))))
);
match self.extra.state.last() {
None => self.enter_trig_doc(txt),
Some(DefaultGraph) => self.in_default_graph(txt),
Some(NamedGraph(has_name)) => self.in_named_graph(txt, *has_name),
Some(Block(square_bracket)) => self.in_block(txt, *square_bracket),
Some(LabelNotSubject) => self.after_label_not_subject(txt),
Some(AtDirective) => self.end_at_directive(txt),
Some(BaseIri) => self.in_base_iri(txt),
Some(PrefixDeclaration) => self.in_prefix_declaration(txt),
Some(PrefixIri) => self.in_prefix_iri(txt),
Some(VersionSpecifier) => self.in_version_specifier(txt),
Some(Triples(require_pol)) => self.in_triples(txt, *require_pol),
Some(PredicateObjectList) => self.in_predicate_object_list(txt),
Some(ObjectList) => self.in_object_list(txt, callback),
Some(Verb) => self.exit_verb(txt, callback),
Some(Object) => self.exit_object(txt, callback),
Some(BlankNodePropertyListOrAnon) => self.in_blank_node_property_list_or_anon(txt),
Some(Collection) => self.in_collection(txt, callback),
Some(RdfLiteral(dt)) => self.in_rdf_literal(txt, *dt),
Some(Reifier) => self.in_reifier(txt, callback),
Some(ReifiedTriple(true)) => self.enter_rt_subject(txt),
Some(ReifiedTriple(false)) => self.exit_reified_triple(txt),
Some(RtSubject) => self.exit_rt_subject(txt),
Some(RtObject) => self.exit_rt_object(txt, callback),
Some(TripleTerm) => self.enter_tt_subject(txt),
Some(TtSubject) => self.exit_tt_subject(txt),
Some(TtObject) => self.exit_tt_object(txt),
Some(AnnotationBlock) => self.enter_predicate_object_list(txt),
Some(StringLiteralLong(quote)) => self.in_string_literal_long(txt, *quote),
Some(Anon(false)) => self.in_anon(txt),
Some(Anon(true)) => self.in_anon_reifier(txt, callback),
}
}
fn in_predicate_object_list<E>(&mut self, txt: &str) -> StreamResult<usize, Error, E>
where
E: std::error::Error + Send + Sync + 'static,
{
if txt.starts_with('}') {
if !(3..=4).contains(&self.extra.state.len()) {
return Err(ErrorKind::Expected("verb, ';' or closing bracket".into()))
.wrap_in(self);
}
debug_assert!(
self.extra.state[self.extra.state.len() - 2].in_triples(),
"{:?}",
self.extra.state
);
debug_assert!(
self.extra.state.len() == 3
|| matches!(
self.extra.state[self.extra.state.len() - 3],
TriGState::DefaultGraph | TriGState::NamedGraph(true)
),
"{:?}",
self.extra.state
);
self.pop_triple_components(1);
if self.extra.state[0] == TriGState::NamedGraph(true) {
self.pop_triple_components(1);
}
debug_assert!(self.inner.terms.is_empty());
for _ in 0..3 {
self.extra.state.pop();
}
Ok(1)
} else {
self.in_predicate_object_list_common(txt)
}
}
fn in_object_list<E, C>(&mut self, txt: &str, callback: &mut C) -> StreamResult<usize, Error, E>
where
E: std::error::Error + Send + Sync + 'static,
C: FnMut(Self::Output<'_>) -> Result<(), E>,
{
if txt.starts_with('}') {
if self.extra.state.len() != 4 {
return Err(ErrorKind::Expected(
"reifier, annotation, punctuation".into(),
))
.wrap_in(self);
}
debug_assert!(self.extra.state[self.extra.state.len() - 2].in_predicate_object_list());
debug_assert!(self.extra.state[self.extra.state.len() - 3].in_triples());
debug_assert!(
self.extra.state.len() == 3
|| matches!(
self.extra.state[self.extra.state.len() - 4],
TriGState::DefaultGraph | TriGState::NamedGraph(true)
),
"{:?}",
self.extra.state
);
self.pop_triple_components(3);
if self.extra.state[0] == TriGState::NamedGraph(true) {
self.pop_triple_components(1);
}
debug_assert!(self.inner.terms.is_empty());
for _ in 0..self.extra.state.len() {
self.extra.state.pop();
}
Ok(1)
} else {
self.in_object_list_common::<E, C>(txt, callback)
}
}
fn end_triples<E2>(&mut self, txt: &str) -> StreamResult<usize, Error, E2>
where
E2: std::error::Error + Send + Sync + 'static,
{
debug_assert!(!txt.is_empty());
debug_assert!((1..=2).contains(&self.extra.state.len()));
debug_assert!(matches!(
self.extra.state.last(),
Some(&TriGState::Triples(false))
));
if self.extra.state.len() == 1 {
if txt.starts_with('.') {
self.extra.state.pop();
self.pop_term();
Ok(1)
} else {
Err(ErrorKind::Expected("'.'".into())).wrap_in(self)
}
} else {
debug_assert!(matches!(
self.extra.state[0],
TriGState::DefaultGraph | TriGState::NamedGraph(true)
));
let close_graph = match txt.as_bytes()[0] {
b'.' => false,
b'}' => true,
_ => return Err(ErrorKind::Expected("'.' or '}'".into())).wrap_in(self),
};
self.extra.state.pop();
self.pop_term();
if close_graph {
self.extra.state.pop();
}
Ok(1)
}
}
}
impl<I: BufRead> Source for TriGSource<I> {
type Item<'x> = Spog<StashedTerm<'x>>;
type Error = Error;
fn try_for_some_item<E, F>(&mut self, f: F) -> StreamResult<bool, Self::Error, E>
where
E: std::error::Error + Send + Sync + 'static,
F: FnMut(Self::Item<'_>) -> Result<(), E>,
{
let mut line = String::with_capacity(1024);
self.inner.col = 0;
let read = self.input.read_line(&mut line).wrap_in(self)?;
if read == 0 {
if let Some(state) = self.extra.state.pop() {
Err(ErrorKind::EOF(format!("{state:?}"))).wrap_in(self)
} else {
Ok(false)
}
} else {
self.parse_line(&line, f)?;
self.inner.line += 1;
Ok(true)
}
}
}
impl<I> TriGSource<I> {
#[cfg(test)]
fn new(input: I) -> Self {
TriGSource {
input,
inner: Default::default(),
extra: Default::default(),
}
}
fn enter_trig_doc<E>(&mut self, txt: &str) -> StreamResult<usize, Error, E>
where
E: std::error::Error + Send + Sync + 'static,
{
debug_assert!(!txt.is_empty() && self.ws(txt) == 0);
debug_assert!(self.extra.state.last().is_none(), "{:?}", self.extra.state);
match txt.as_bytes()[0] {
b'@' => self.enter_at_keyword(txt),
b'<' => {
if txt[1..].starts_with('<') {
self.extra.state.push(TriGState::Triples(false));
self.extra.state.push(TriGState::ReifiedTriple(true));
Ok(2)
} else {
self.extra.state.push(TriGState::Block(false));
self.iriref(txt)
}
}
b'[' => {
self.extra.state.push(TriGState::Block(true));
self.mint_bnode_label();
Ok(1)
}
b'_' => {
self.extra.state.push(TriGState::Block(false));
self.blank_node_label(txt)
}
b'(' => {
self.extra.state.push(TriGState::Triples(true));
self.extra.state.push(TriGState::Collection);
Ok(1)
}
b'{' => {
self.extra.state.push(TriGState::DefaultGraph);
Ok(1)
}
_ => {
lazy_regex!(
SPARQL_KW = [
r"^(?i)BASE[ \n\r\t#<]",
r"^(?i)PREFIX[ \n\r\t#]",
r#"^(?i)VERSION[ \n\r\t#"']"#,
r"^(?i)GRAPH[ \n\r\t#<]"
]
);
match SPARQL_KW.matches(txt).iter().next() {
Some(0) => {
self.extra.state.push(TriGState::BaseIri);
Ok("BASE".len())
}
Some(1) => {
self.extra.state.push(TriGState::PrefixDeclaration);
Ok("PREFIX".len())
}
Some(2) => {
self.extra.state.push(TriGState::VersionSpecifier);
Ok("VERSION".len())
}
Some(3) => {
self.extra.state.push(TriGState::NamedGraph(false));
Ok("GRAPH".len())
}
None => {
self.extra.state.push(TriGState::Block(false));
self.prefixed_name(txt)
}
Some(_) => unreachable!(),
}
}
}
}
fn in_default_graph<E>(&mut self, txt: &str) -> StreamResult<usize, Error, E>
where
E: std::error::Error + Send + Sync + 'static,
{
debug_assert!(!txt.is_empty() && self.ws(txt) == 0);
debug_assert!(matches!(
self.extra.state.last(),
Some(TriGState::DefaultGraph)
));
debug_assert!(self.extra.state.len() == 1);
if txt.starts_with("}") {
self.extra.state.pop();
Ok(1)
} else {
self.subject(txt)
}
}
fn subject<E>(&mut self, txt: &str) -> StreamResult<usize, Error, E>
where
E: std::error::Error + Send + Sync + 'static,
{
match txt.as_bytes()[0] {
b'<' => {
if txt[1..].starts_with('<') {
self.extra.state.push(TriGState::Triples(false));
self.extra.state.push(TriGState::ReifiedTriple(true));
Ok(2)
} else {
self.extra.state.push(TriGState::Triples(true));
self.iriref(txt)
}
}
b'[' => {
self.extra.state.push(TriGState::Triples(false));
self.extra
.state
.push(TriGState::BlankNodePropertyListOrAnon);
self.mint_bnode_label();
Ok(1)
}
b'_' => {
self.extra.state.push(TriGState::Triples(true));
self.blank_node_label(txt)
}
b'(' => {
self.extra.state.push(TriGState::Triples(true));
self.extra.state.push(TriGState::Collection);
Ok(1)
}
_ => {
self.extra.state.push(TriGState::Triples(true));
self.prefixed_name(txt)
}
}
}
fn in_named_graph<E>(&mut self, txt: &str, has_name: bool) -> StreamResult<usize, Error, E>
where
E: std::error::Error + Send + Sync + 'static,
{
debug_assert!(!txt.is_empty() && self.ws(txt) == 0);
debug_assert_eq!(
self.extra.state.last(),
Some(&TriGState::NamedGraph(has_name))
);
if !has_name {
debug_assert!(self.extra.state.len() == 1);
self.extra.state[0] = TriGState::NamedGraph(true);
self.extra.state.push(TriGState::LabelNotSubject);
match txt.as_bytes()[0] {
b'<' => self.iriref(txt),
b'[' => {
self.extra.state.push(TriGState::Anon(false));
Ok(1)
}
b'_' => self.blank_node_label(txt),
_ => self.prefixed_name(txt),
}
} else {
debug_assert!(has_name);
if txt.starts_with("}") {
self.extra.state.pop();
self.pop_triple_components(1);
Ok(1)
} else {
self.subject(txt)
}
}
}
fn in_block<E>(&mut self, txt: &str, square_bracket: bool) -> StreamResult<usize, Error, E>
where
E: std::error::Error + Send + Sync + 'static,
{
debug_assert!(!txt.is_empty() && self.ws(txt) == 0);
let state = &mut self.extra.state;
debug_assert_eq!(state.last(), Some(&TriGState::Block(square_bracket)));
debug_assert_eq!(state.len(), 1);
#[allow(clippy::collapsible_else_if)]
if square_bracket {
if txt.starts_with(']') {
state[0] = TriGState::Block(false);
Ok(1)
} else {
state[0] = TriGState::Triples(false);
state.push(TriGState::BlankNodePropertyListOrAnon);
self.enter_predicate_object_list(txt)
}
} else {
debug_assert!(!square_bracket);
if txt.starts_with('{') {
state[0] = TriGState::NamedGraph(true);
Ok(1)
} else {
state[0] = TriGState::Triples(true);
self.enter_predicate_object_list(txt)
}
}
}
fn after_label_not_subject<E>(&mut self, txt: &str) -> StreamResult<usize, Error, E>
where
E: std::error::Error + Send + Sync + 'static,
{
debug_assert!(!txt.is_empty() && self.ws(txt) == 0);
debug_assert_eq!(self.extra.state.last(), Some(&TriGState::LabelNotSubject));
if txt.starts_with('{') {
self.extra.state.pop();
Ok(1)
} else {
Err(ErrorKind::Expected("'{'".into())).wrap_in(self)
}
}
fn exit_verb<E, C>(&mut self, txt: &str, callback: &mut C) -> StreamResult<usize, Error, E>
where
E: std::error::Error + Send + Sync + 'static,
C: FnMut(<Self as GenericSource>::Output<'_>) -> Result<(), E>,
{
debug_assert!(!txt.is_empty() && self.ws(txt) == 0);
debug_assert!(
matches!(self.extra.state.last(), Some(TriGState::Verb)),
"{:?}",
self.extra.state
);
self.extra.state.pop();
match self.extra.state.last() {
Some(TriGState::PredicateObjectList) => self.enter_object_list(txt, callback),
Some(TriGState::ReifiedTriple(true)) => self.enter_rt_object(txt),
Some(TriGState::TripleTerm) => self.enter_tt_object(txt),
_ => unreachable!(),
}
}
fn exit_object<E, C>(&mut self, txt: &str, callback: &mut C) -> StreamResult<usize, Error, E>
where
E: std::error::Error + Send + Sync + 'static,
C: FnMut(<Self as GenericSource>::Output<'_>) -> Result<(), E>,
{
debug_assert!(!txt.is_empty() && self.ws(txt) == 0);
debug_assert!(
matches!(self.extra.state.last(), Some(TriGState::Object)),
"{:?}",
self.extra.state
);
self.extra.state.pop();
match self.extra.state.last() {
Some(TriGState::ObjectList) => {
self.emit_tuple(callback, 0)?;
self.in_object_list(txt, callback)
}
Some(TriGState::Collection) => {
self.emit_tuple(callback, 0)?;
self.grow_collection()?;
self.in_collection(txt, callback)
}
_ => unreachable!(),
}
}
}
#[cfg(test)]
mod test;