Skip to main content

oxirs_ttl/toolkit/
parser.rs

1//! Generic parser framework for converting tokens to RDF elements
2//!
3//! This module provides the rule-based parsing infrastructure that works
4//! with tokenizers to produce RDF triples and quads.
5
6use crate::error::{RuleRecognizerError, TurtleParseError, TurtleResult};
7use crate::toolkit::lexer::{TokenOrLineJump, TokenRecognizer};
8// use oxirs_core::model::{Quad, Triple};
9use std::io::{BufRead, Read};
10use std::marker::PhantomData;
11
12/// A rule recognizer that converts token streams into RDF elements
13pub trait RuleRecognizer {
14    /// The token recognizer this rule works with
15    type TokenRecognizer: TokenRecognizer;
16
17    /// The output produced (Triple, Quad, etc.)
18    type Output;
19
20    /// Parsing context (prefixes, base IRI, etc.)
21    type Context;
22
23    /// Recognize the next RDF element from a token
24    fn recognize_next(
25        self,
26        token: TokenOrLineJump<<Self::TokenRecognizer as TokenRecognizer>::Token<'_>>,
27        context: &mut Self::Context,
28        results: &mut Vec<Self::Output>,
29        errors: &mut Vec<RuleRecognizerError>,
30    ) -> Self;
31}
32
33/// A streaming parser that combines tokenization and rule recognition
34pub struct StreamingParser<R, T: crate::toolkit::lexer::TokenRecognizer, P: RuleRecognizer> {
35    tokenizer: crate::toolkit::lexer::StreamingTokenizer<R, T>,
36    rule_recognizer: P,
37    context: P::Context,
38    _phantom: PhantomData<P>,
39}
40
41impl<R: BufRead, T: TokenRecognizer, P: RuleRecognizer<TokenRecognizer = T>>
42    StreamingParser<R, T, P>
43{
44    /// Create a new streaming parser
45    pub fn new(
46        tokenizer: crate::toolkit::lexer::StreamingTokenizer<R, T>,
47        rule_recognizer: P,
48        context: P::Context,
49    ) -> Self {
50        Self {
51            tokenizer,
52            rule_recognizer,
53            context,
54            _phantom: PhantomData,
55        }
56    }
57}
58
59impl<R: BufRead, T: TokenRecognizer, P: RuleRecognizer<TokenRecognizer = T>> Iterator
60    for StreamingParser<R, T, P>
61where
62    P: Clone,
63{
64    type Item = TurtleResult<P::Output>;
65
66    fn next(&mut self) -> Option<Self::Item> {
67        loop {
68            match self.tokenizer.next() {
69                None => return None, // EOF
70                Some(Err(e)) => {
71                    return Some(Err(TurtleParseError::syntax(
72                        crate::error::TurtleSyntaxError::Generic {
73                            message: e.to_string(),
74                            position: self.tokenizer.position(),
75                        },
76                    )))
77                }
78                Some(Ok(token)) => {
79                    let mut results = Vec::new();
80                    let mut errors = Vec::new();
81
82                    self.rule_recognizer = self.rule_recognizer.clone().recognize_next(
83                        token,
84                        &mut self.context,
85                        &mut results,
86                        &mut errors,
87                    );
88
89                    // Handle errors
90                    if !errors.is_empty() {
91                        return Some(Err(TurtleParseError::syntax(
92                            crate::error::TurtleSyntaxError::Generic {
93                                message: format!("Rule recognition error: {:?}", errors[0]),
94                                position: self.tokenizer.position(),
95                            },
96                        )));
97                    }
98
99                    // Return first result if any
100                    if let Some(result) = results.into_iter().next() {
101                        return Some(Ok(result));
102                    }
103
104                    // Continue if no results (e.g., whitespace, comments)
105                }
106            }
107        }
108    }
109}
110
111/// A generic parser trait for all RDF formats
112pub trait Parser<Output> {
113    /// Parse from a reader
114    fn parse<R: Read>(&self, reader: R) -> TurtleResult<Vec<Output>>;
115
116    /// Create an iterator for streaming parsing
117    fn for_reader<R: BufRead + 'static>(
118        &self,
119        reader: R,
120    ) -> Box<dyn Iterator<Item = TurtleResult<Output>>>;
121}
122
123/// Async parser trait for Tokio integration
124#[cfg(feature = "async-tokio")]
125pub trait AsyncParser<Output> {
126    /// Parse from an async reader
127    fn parse_async<R: tokio::io::AsyncRead + Unpin>(
128        &self,
129        reader: R,
130    ) -> impl std::future::Future<Output = TurtleResult<Vec<Output>>> + Send;
131
132    /// Create an async stream for streaming parsing
133    fn for_async_reader<R: tokio::io::AsyncBufRead + Unpin>(
134        &self,
135        reader: R,
136    ) -> Box<dyn futures::Stream<Item = TurtleResult<Output>> + Unpin>;
137}
138
139/// Context for parsing operations
140#[derive(Debug, Clone, Default)]
141pub struct ParsingContext {
142    /// Base IRI for resolving relative IRIs
143    pub base_iri: Option<String>,
144    /// Prefix declarations
145    pub prefixes: std::collections::HashMap<String, String>,
146    /// Blank node ID generator state
147    pub blank_node_counter: usize,
148}
149
150impl ParsingContext {
151    /// Create a new parsing context
152    pub fn new() -> Self {
153        Self::default()
154    }
155
156    /// Set the base IRI
157    pub fn with_base_iri(mut self, base_iri: String) -> Self {
158        self.base_iri = Some(base_iri);
159        self
160    }
161
162    /// Add a prefix declaration
163    pub fn add_prefix(&mut self, prefix: String, iri: String) {
164        self.prefixes.insert(prefix, iri);
165    }
166
167    /// Resolve a prefixed name
168    pub fn resolve_prefixed_name(&self, prefix: &str, local: &str) -> Option<String> {
169        self.prefixes.get(prefix).map(|iri| format!("{iri}{local}"))
170    }
171
172    /// Generate a new blank node ID
173    pub fn generate_blank_node_id(&mut self) -> String {
174        let id = format!("_:b{}", self.blank_node_counter);
175        self.blank_node_counter += 1;
176        id
177    }
178
179    /// Resolve a relative IRI against the base IRI
180    pub fn resolve_iri(&self, iri: &str) -> String {
181        if let Some(ref base) = self.base_iri {
182            // Simple resolution - in practice would use proper IRI resolution
183            if iri.starts_with('#') || iri.starts_with('/') {
184                format!("{base}{iri}")
185            } else {
186                iri.to_string()
187            }
188        } else {
189            iri.to_string()
190        }
191    }
192}