nquads-syntax 0.20.0

RDF 1.1 N-Quads parser with code mapping metadata
Documentation
use crate::{
	GrdfNquadDocument, NquadDocument,
	lexing::{Lexer, NquadsLexingError, Token},
};
use decoded_char::DecodedChar;
use locspan::{Span, Spanned};
use rdf_syntax::{Id, IriBuf, Literal, LiteralType, Quad, RdfQuad, Term, XSD_STRING};
use std::fmt;

#[derive(Debug)]
pub enum NquadsError<E = NquadsLexingError> {
	Lexer(E),
	Unexpected(Option<Token>, Span),
}

impl<E: fmt::Display> fmt::Display for NquadsError<E> {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		match self {
			Self::Unexpected(None, _) => write!(f, "unexpected end of file"),
			Self::Unexpected(Some(token), _) => write!(f, "unexpected {token}"),
			Self::Lexer(e) => e.fmt(f),
		}
	}
}

impl<E: 'static + std::error::Error> std::error::Error for NquadsError<E> {
	fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
		match self {
			Self::Lexer(e) => Some(e),
			_ => None,
		}
	}
}

pub struct NquadsParser<L> {
	lexer: L,
	position: usize,
	pending: Option<Token>,
}

impl<L> NquadsParser<L> {
	pub fn new(lexer: L) -> Self {
		Self {
			lexer,
			position: 0,
			pending: None,
		}
	}
}

impl<L, E> NquadsParser<L>
where
	L: Iterator<Item = Result<Token, E>>,
{
	fn next_token(&mut self) -> Result<Option<Token>, NquadsError<E>> {
		let token = match self.pending.take() {
			Some(token) => Some(token),
			None => self.lexer.next().transpose().map_err(NquadsError::Lexer)?,
		};

		if let Some(token) = &token {
			self.position = token.span().end
		}

		Ok(token)
	}

	#[allow(clippy::type_complexity)]
	fn peek(&mut self) -> Result<Option<&Token>, NquadsError<E>> {
		if self.pending.is_none() {
			self.pending = self.lexer.next().transpose().map_err(NquadsError::Lexer)?;
		}

		Ok(self.pending.as_ref())
	}

	#[allow(clippy::type_complexity)]
	fn parse_literal(
		&mut self,
		value: String,
		mut span: Span,
	) -> Result<(Literal, Span), NquadsError<E>> {
		match self.peek()? {
			Some(Token::LangTag(_, _)) => {
				let Some(Token::LangTag(tag, tag_span)) = self.next_token()? else {
					unreachable!()
				};

				span.append(tag_span);

				Ok((Literal::new(value, LiteralType::LangString(tag)), span))
			}
			Some(Token::Carets(_)) => {
				self.next_token()?;

				match self.next_token()? {
					Some(Token::Iri(iri, iri_span)) => {
						span.append(iri_span);
						Ok((Literal::new(value, LiteralType::Any(iri)), span))
					}
					Some(token) => {
						let span = token.span();
						Err(NquadsError::Unexpected(Some(token), span))
					}
					None => Err(NquadsError::Unexpected(None, self.position.into())),
				}
			}
			_ => Ok((
				Literal::new(value, LiteralType::Any(XSD_STRING.to_owned())),
				span,
			)),
		}
	}

	fn parse_iri(&mut self) -> Result<(IriBuf, Span), NquadsError<E>> {
		match self.next_token()? {
			Some(Token::Iri(iri, span)) => Ok((iri, span)),
			Some(token) => {
				let span = token.span();
				Err(NquadsError::Unexpected(Some(token), span))
			}
			None => Err(NquadsError::Unexpected(None, self.position.into())),
		}
	}

	fn parse_id(&mut self) -> Result<(Id, Span), NquadsError<E>> {
		match self.next_token()? {
			Some(Token::Iri(iri, span)) => Ok((Id::Iri(iri), span)),
			Some(Token::BlankNodeLabel(label, span)) => Ok((Id::BlankId(label), span)),
			Some(token) => {
				let span = token.span();
				Err(NquadsError::Unexpected(Some(token), span))
			}
			None => Err(NquadsError::Unexpected(None, self.position.into())),
		}
	}

	fn parse_id_opt(&mut self) -> Result<Option<(Id, Span)>, NquadsError<E>> {
		match self.peek()? {
			Some(Token::Dot(_)) => Ok(None),
			_ => self.parse_id().map(Some),
		}
	}

	fn parse_term(&mut self) -> Result<(Term, Span), NquadsError<E>> {
		match self.next_token()? {
			Some(Token::Iri(iri, span)) => Ok((Term::iri(iri), span)),
			Some(Token::BlankNodeLabel(label, span)) => Ok((Term::BlankId(label), span)),
			Some(Token::StringLiteral(value, span)) => {
				let (literal, span) = self.parse_literal(value, span)?;
				Ok((Term::literal(literal), span))
			}
			Some(token) => {
				let span = token.span();
				Err(NquadsError::Unexpected(Some(token), span))
			}
			None => Err(NquadsError::Unexpected(None, self.position.into())),
		}
	}

	fn parse_term_opt(&mut self) -> Result<Option<(Term, Span)>, NquadsError<E>> {
		match self.peek()? {
			Some(Token::Dot(_)) => Ok(None),
			_ => self.parse_term().map(Some),
		}
	}

	fn parse_dot(&mut self) -> Result<Span, NquadsError<E>> {
		match self.next_token()? {
			Some(Token::Dot(span)) => Ok(span),
			Some(token) => {
				let span = token.span();
				Err(NquadsError::Unexpected(Some(token), span))
			}
			None => Err(NquadsError::Unexpected(None, self.position.into())),
		}
	}

	fn parse_quad(&mut self) -> Result<(RdfQuad, NquadsCodeMapEntry), NquadsError<E>> {
		let (s, s_span) = self.parse_id()?;
		let (p, p_span) = self.parse_iri()?;
		let (o, o_span) = self.parse_term()?;
		let (g, g_span) = split_option(self.parse_id_opt()?);
		let dot_span = self.parse_dot()?;
		let span = s_span.union(dot_span);
		Ok((
			Quad(s, p, o, g),
			NquadsCodeMapEntry {
				quad: span,
				components: Quad(s_span, p_span, o_span, g_span),
			},
		))
	}

	fn parse_grdf_quad(&mut self) -> Result<(Quad<Term>, NquadsCodeMapEntry), NquadsError<E>> {
		let (s, s_span) = self.parse_term()?;
		let (p, p_span) = self.parse_term()?;
		let (o, o_span) = self.parse_term()?;
		let (g, g_span) = split_option(self.parse_term_opt()?);
		let dot_span = self.parse_dot()?;
		let span = s_span.union(dot_span);
		Ok((
			Quad(s, p, o, g),
			NquadsCodeMapEntry {
				quad: span,
				components: Quad(s_span, p_span, o_span, g_span),
			},
		))
	}

	fn parse_document(&mut self) -> Result<(Vec<RdfQuad>, NquadsCodeMap), NquadsError<E>> {
		let mut quads = Vec::new();
		let mut spans = Vec::new();

		while self.peek()?.is_some() {
			let (quad, span) = self.parse_quad()?;
			quads.push(quad);
			spans.push(span);
		}

		Ok((quads, spans))
	}

	fn parse_grdf_document(&mut self) -> Result<(Vec<Quad<Term>>, NquadsCodeMap), NquadsError<E>> {
		let mut quads = Vec::new();
		let mut spans = Vec::new();

		while self.peek()?.is_some() {
			let (quad, span) = self.parse_grdf_quad()?;
			quads.push(quad);
			spans.push(span);
		}

		Ok((quads, spans))
	}
}

fn split_option<A, B>(value: Option<(A, B)>) -> (Option<A>, Option<B>) {
	match value {
		Some((a, b)) => (Some(a), Some(b)),
		None => (None, None),
	}
}

pub type NquadsCodeMap = Vec<NquadsCodeMapEntry>;

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NquadsCodeMapEntry {
	pub quad: Span,
	pub components: Quad<Span>,
}

pub trait NquadsParse: Sized {
	#[allow(clippy::type_complexity)]
	fn parse_with<L, E>(
		parser: &mut NquadsParser<L>,
	) -> Result<(Self, NquadsCodeMap), NquadsError<E>>
	where
		L: Iterator<Item = Result<Token, E>>;

	#[inline(always)]
	fn parse<C, E>(chars: C) -> Result<(Self, NquadsCodeMap), NquadsError<NquadsLexingError<E>>>
	where
		C: Iterator<Item = Result<DecodedChar, E>>,
	{
		let mut parser = NquadsParser::new(Lexer::new(chars));
		Self::parse_with(&mut parser)
	}

	#[inline(always)]
	fn parse_infallible<C>(chars: C) -> Result<(Self, NquadsCodeMap), NquadsError>
	where
		C: Iterator<Item = DecodedChar>,
	{
		Self::parse(chars.map(Ok))
	}

	#[inline(always)]
	fn parse_utf8<C, E>(
		chars: C,
	) -> Result<(Self, NquadsCodeMap), NquadsError<NquadsLexingError<E>>>
	where
		C: Iterator<Item = Result<char, E>>,
	{
		Self::parse(decoded_char::FallibleUtf8Decoded::new(chars))
	}

	#[inline(always)]
	fn parse_utf8_infallible<C>(chars: C) -> Result<(Self, NquadsCodeMap), NquadsError>
	where
		C: Iterator<Item = char>,
	{
		Self::parse_infallible(decoded_char::Utf8Decoded::new(chars))
	}

	#[inline(always)]
	fn parse_utf16<C, E>(
		chars: C,
	) -> Result<(Self, NquadsCodeMap), NquadsError<NquadsLexingError<E>>>
	where
		C: Iterator<Item = Result<char, E>>,
	{
		Self::parse(decoded_char::FallibleUtf16Decoded::new(chars))
	}

	#[inline(always)]
	fn parse_utf16_infallible<C>(chars: C) -> Result<(Self, NquadsCodeMap), NquadsError>
	where
		C: Iterator<Item = char>,
	{
		Self::parse_infallible(decoded_char::Utf16Decoded::new(chars))
	}

	#[inline(always)]
	fn parse_str(string: &str) -> Result<(Self, NquadsCodeMap), NquadsError> {
		Self::parse_utf8_infallible(string.chars())
	}
}

impl NquadsParse for NquadDocument {
	fn parse_with<L, E>(
		parser: &mut NquadsParser<L>,
	) -> Result<(Self, NquadsCodeMap), NquadsError<E>>
	where
		L: Iterator<Item = Result<Token, E>>,
	{
		parser.parse_document()
	}
}

impl NquadsParse for GrdfNquadDocument {
	fn parse_with<L, E>(
		parser: &mut NquadsParser<L>,
	) -> Result<(Self, NquadsCodeMap), NquadsError<E>>
	where
		L: Iterator<Item = Result<Token, E>>,
	{
		parser.parse_grdf_document()
	}
}