pub mod lexer;
pub mod parser;
mod tests;
pub use lexer::{NQuadsLexer, Token};
pub use parser::{parse_line, parse_term};
use std::io::{BufRead, BufReader, Read};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum NQuadsParseError {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Invalid N-Quads line {line}: {message}")]
InvalidLine {
line: usize,
message: String,
},
#[error("Invalid IRI at line {line}: <{iri}>")]
InvalidIri {
line: usize,
iri: String,
},
#[error("Invalid literal at line {line}: {message}")]
InvalidLiteral {
line: usize,
message: String,
},
#[error("Invalid blank node at line {line}: _:{name}")]
InvalidBlankNode {
line: usize,
name: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StreamedLiteral {
pub value: String,
pub datatype: Option<String>,
pub language: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StreamedTerm {
NamedNode(String),
BlankNode(String),
Literal(StreamedLiteral),
}
impl StreamedTerm {
pub fn as_iri(&self) -> Option<&str> {
match self {
Self::NamedNode(iri) => Some(iri),
_ => None,
}
}
pub fn as_blank(&self) -> Option<&str> {
match self {
Self::BlankNode(label) => Some(label),
_ => None,
}
}
pub fn as_literal(&self) -> Option<&StreamedLiteral> {
match self {
Self::Literal(lit) => Some(lit),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StreamedQuad {
pub subject: StreamedTerm,
pub predicate: StreamedTerm,
pub object: StreamedTerm,
pub graph_name: Option<StreamedTerm>,
}
pub struct NQuadsStreamingParser<R: Read> {
reader: BufReader<R>,
line_num: usize,
}
impl<R: Read> NQuadsStreamingParser<R> {
pub fn new(reader: R) -> Self {
Self {
reader: BufReader::new(reader),
line_num: 0,
}
}
pub fn lines_read(&self) -> usize {
self.line_num
}
}
impl<R: Read> Iterator for NQuadsStreamingParser<R> {
type Item = Result<StreamedQuad, NQuadsParseError>;
fn next(&mut self) -> Option<Self::Item> {
let mut line = String::new();
loop {
line.clear();
match self.reader.read_line(&mut line) {
Err(e) => return Some(Err(NQuadsParseError::Io(e))),
Ok(0) => return None, Ok(_) => {
self.line_num += 1;
let trimmed = line.trim_end_matches('\n').trim_end_matches('\r').trim();
match parse_line(trimmed, self.line_num) {
Ok(None) => {
continue;
}
Ok(Some(quad)) => return Some(Ok(quad)),
Err(e) => return Some(Err(e)),
}
}
}
}
}
}