Skip to main content

qualia_core_db/sparql_library/parsers/
trig_star.rs

1use crate::lexicon::{generate_60bit_token, generate_embedded_triple_id};
2use crate::rdf_star::{RdfStarParseError, RdfStarParser};
3///
4/// Implements RDF-Star (SPARQL 1.2) parsing for Trig syntax with embedded triples.
5/// Trig-Star extends Turtle-Star with named graph support via GRAPH {} blocks.
6use crate::NQuin;
7/// Trig-Star Parser for QualiaDB
8use std::io::BufReader;
9
10/// Trig-Star parser implementation
11pub struct TrigStarParser {
12    /// Context hash for the current parsing session
13    context_hash: u64,
14    /// Current named graph hash
15    current_graph: u64,
16}
17
18impl TrigStarParser {
19    /// Create a new Trig-Star parser
20    pub fn new(context_hash: u64) -> Self {
21        Self {
22            context_hash,
23            current_graph: 0, // Default graph
24        }
25    }
26
27    /// Set the current named graph
28    pub fn set_current_graph(&mut self, graph_hash: u64) {
29        self.current_graph = graph_hash;
30    }
31
32    /// Get the current named graph
33    pub fn current_graph(&self) -> u64 {
34        self.current_graph
35    }
36
37    /// Session context hash used when no explicit `GRAPH` block is active.
38    pub fn session_context(&self) -> u64 {
39        self.context_hash
40    }
41
42    fn effective_graph(&self) -> u64 {
43        if self.current_graph != 0 {
44            self.current_graph
45        } else {
46            self.context_hash
47        }
48    }
49
50    /// Parse a Trig line (subject, predicate, object) in current graph context
51    fn parse_line(&self, line: &str) -> Result<ParseResult, RdfStarParseError> {
52        let line = line.trim();
53        if line.is_empty() || line.starts_with('#') {
54            return Ok(ParseResult::Comment);
55        }
56
57        // Check for GRAPH keyword
58        if line.starts_with("GRAPH") {
59            return self.parse_graph_block(line);
60        }
61
62        // Check for embedded triple start marker
63        if line.contains("<<") {
64            self.parse_embedded_triple_line(line)
65        } else {
66            self.parse_triple_line(line)
67        }
68    }
69
70    /// Parse a GRAPH block declaration
71    fn parse_graph_block(&self, line: &str) -> Result<ParseResult, RdfStarParseError> {
72        // Format: GRAPH <graph_uri> { ... }
73        let parts: Vec<&str> = line.split_whitespace().collect();
74        if parts.len() < 2 {
75            return Err(RdfStarParseError::InvalidSyntax);
76        }
77
78        let graph_str = parts[1].trim_start_matches('<').trim_end_matches('>');
79        let graph_hash = generate_60bit_token(graph_str.as_bytes());
80
81        Ok(ParseResult::GraphDeclaration { graph_hash })
82    }
83
84    /// Parse a regular Trig triple
85    fn parse_triple_line(&self, line: &str) -> Result<ParseResult, RdfStarParseError> {
86        // Format: subject predicate object .
87        let parts: Vec<&str> = line.split_whitespace().collect();
88        if parts.len() < 3 {
89            return Err(RdfStarParseError::InvalidSyntax);
90        }
91
92        let subject_str = parts[0];
93        let predicate_str = parts[1];
94        let object_str = parts[2];
95
96        // Strip angle brackets and quotes
97        let subject = subject_str.trim_start_matches('<').trim_end_matches('>');
98        let predicate = predicate_str.trim_start_matches('<').trim_end_matches('>');
99        let object = object_str
100            .trim_start_matches('<')
101            .trim_end_matches('>')
102            .trim_start_matches('"')
103            .trim_end_matches('"');
104
105        let subject_hash = generate_60bit_token(subject.as_bytes());
106        let predicate_hash = generate_60bit_token(predicate.as_bytes());
107        let object_hash = generate_60bit_token(object.as_bytes());
108
109        Ok(ParseResult::RegularTriple {
110            subject: subject_hash,
111            predicate: predicate_hash,
112            object: object_hash,
113            graph_hash: self.effective_graph(),
114        })
115    }
116
117    /// Parse an embedded triple line
118    fn parse_embedded_triple_line(&self, line: &str) -> Result<ParseResult, RdfStarParseError> {
119        // Similar to Turtle-Star but with graph context
120        // Find the embedded triple
121        let start = line
122            .find("<<")
123            .ok_or(RdfStarParseError::MalformedEmbeddedTriple)?;
124        let end = line
125            .find(">>")
126            .ok_or(RdfStarParseError::MalformedEmbeddedTriple)?;
127
128        let embedded_part = &line[start + 2..end];
129        let embedded_parts: Vec<&str> = embedded_part.split_whitespace().collect();
130        if embedded_parts.len() < 3 {
131            return Err(RdfStarParseError::MalformedEmbeddedTriple);
132        }
133
134        let subject = embedded_parts[0]
135            .trim_start_matches('<')
136            .trim_end_matches('>');
137        let predicate = embedded_parts[1]
138            .trim_start_matches('<')
139            .trim_end_matches('>');
140        let object = embedded_parts[2]
141            .trim_start_matches('<')
142            .trim_end_matches('>')
143            .trim_start_matches('"')
144            .trim_end_matches('"');
145
146        let subject_hash = generate_60bit_token(subject.as_bytes());
147        let predicate_hash = generate_60bit_token(predicate.as_bytes());
148        let object_hash = generate_60bit_token(object.as_bytes());
149
150        let virtual_id = generate_embedded_triple_id(subject_hash, predicate_hash, object_hash);
151
152        // Parse the outer triple (after >>)
153        let remaining = &line[end + 2..];
154        let outer_parts: Vec<&str> = remaining.split_whitespace().collect();
155        if outer_parts.len() < 2 {
156            return Err(RdfStarParseError::MalformedEmbeddedTriple);
157        }
158
159        let outer_predicate = outer_parts[0].trim_start_matches('<').trim_end_matches('>');
160        let outer_object = outer_parts[1]
161            .trim_start_matches('<')
162            .trim_end_matches('>')
163            .trim_start_matches('"')
164            .trim_end_matches('"');
165
166        let outer_predicate_hash = generate_60bit_token(outer_predicate.as_bytes());
167        let outer_object_hash = generate_60bit_token(outer_object.as_bytes());
168
169        Ok(ParseResult::EmbeddedTriple {
170            virtual_id,
171            components: [subject_hash, predicate_hash, object_hash],
172            outer_predicate: outer_predicate_hash,
173            outer_object: outer_object_hash,
174            graph_hash: self.effective_graph(),
175        })
176    }
177}
178
179impl RdfStarParser for TrigStarParser {
180    fn parse_embedded_triple(
181        &mut self,
182        input: &[u8],
183    ) -> Result<(u64, [u64; 3]), RdfStarParseError> {
184        let line = std::str::from_utf8(input).map_err(|_| RdfStarParseError::InvalidUtf8)?;
185
186        match self.parse_line(line)? {
187            ParseResult::EmbeddedTriple {
188                virtual_id,
189                components,
190                ..
191            } => Ok((virtual_id, components)),
192            _ => Err(RdfStarParseError::MalformedEmbeddedTriple),
193        }
194    }
195
196    fn parse_triple(&mut self, input: &[u8]) -> Result<(u64, u64, u64), RdfStarParseError> {
197        let line = std::str::from_utf8(input).map_err(|_| RdfStarParseError::InvalidUtf8)?;
198
199        match self.parse_line(line)? {
200            ParseResult::RegularTriple {
201                subject,
202                predicate,
203                object,
204                ..
205            } => Ok((subject, predicate, object)),
206            _ => Err(RdfStarParseError::InvalidSyntax),
207        }
208    }
209
210    fn parse_quad(&mut self, input: &[u8]) -> Result<(u64, u64, u64, u64), RdfStarParseError> {
211        let line = std::str::from_utf8(input).map_err(|_| RdfStarParseError::InvalidUtf8)?;
212
213        match self.parse_line(line)? {
214            ParseResult::RegularTriple {
215                subject,
216                predicate,
217                object,
218                graph_hash,
219            } => Ok((subject, predicate, object, graph_hash)),
220            ParseResult::EmbeddedTriple {
221                outer_predicate,
222                outer_object,
223                graph_hash,
224                ..
225            } => Ok((0, outer_predicate, outer_object, graph_hash)),
226            _ => Err(RdfStarParseError::InvalidSyntax),
227        }
228    }
229
230    fn supports_quads(&self) -> bool {
231        true
232    }
233
234    fn supports_named_graphs(&self) -> bool {
235        true
236    }
237
238    fn format_name(&self) -> &'static str {
239        "Trig-Star"
240    }
241}
242
243/// Parse result for Trig-Star
244enum ParseResult {
245    Comment,
246    GraphDeclaration {
247        graph_hash: u64,
248    },
249    RegularTriple {
250        subject: u64,
251        predicate: u64,
252        object: u64,
253        graph_hash: u64,
254    },
255    EmbeddedTriple {
256        virtual_id: u64,
257        components: [u64; 3],
258        outer_predicate: u64,
259        outer_object: u64,
260        graph_hash: u64,
261    },
262}
263
264/// Parse Trig-Star into any [`QuinSink`].
265pub fn parse_trig_star_into<R: std::io::Read, S: crate::sparql_library::quin_sink::QuinSink>(
266    reader: R,
267    context_hash: u64,
268    sink: &mut S,
269) -> Result<u64, Box<dyn std::error::Error>> {
270    use std::io::BufRead;
271
272    let mut parser = TrigStarParser::new(context_hash);
273    let mut count = 0;
274    let buf_reader = BufReader::new(reader);
275
276    for line in buf_reader.lines() {
277        let line = line?;
278        match parser.parse_line(&line)? {
279            ParseResult::Comment => continue,
280            ParseResult::GraphDeclaration { graph_hash } => {
281                parser.set_current_graph(graph_hash);
282            }
283            ParseResult::RegularTriple {
284                subject,
285                predicate,
286                object,
287                graph_hash,
288            } => {
289                sink.push(NQuin {
290                    subject,
291                    predicate,
292                    object,
293                    context: graph_hash,
294                    metadata: 0b10 << 61,
295                    parity: 0,
296                })?;
297                count += 1;
298            }
299            ParseResult::EmbeddedTriple {
300                virtual_id,
301                components,
302                outer_predicate,
303                outer_object,
304                graph_hash,
305            } => {
306                sink.push(NQuin {
307                    subject: virtual_id,
308                    predicate: outer_predicate,
309                    object: outer_object,
310                    context: graph_hash,
311                    metadata: 0b10 << 61,
312                    parity: 0,
313                })?;
314                count += 1;
315
316                sink.push(NQuin {
317                    subject: components[0],
318                    predicate: components[1],
319                    object: components[2],
320                    context: graph_hash,
321                    metadata: 0b10 << 61,
322                    parity: 0,
323                })?;
324                count += 1;
325            }
326        }
327    }
328
329    Ok(count)
330}
331
332pub fn parse_trig_star_stream<R: std::io::Read>(
333    reader: R,
334    context_hash: u64,
335    sorter: &mut crate::external_sort::ExternalSorter,
336) -> Result<u64, Box<dyn std::error::Error>> {
337    parse_trig_star_into(reader, context_hash, sorter)
338}
339
340#[cfg(test)]
341mod tests {
342    use super::*;
343    use crate::rdf_star::RdfStarParser;
344
345    #[test]
346    fn test_trig_star_parser_creation() {
347        let parser = TrigStarParser::new(0);
348        assert_eq!(parser.format_name(), "Trig-Star");
349        assert!(parser.supports_quads());
350        assert!(parser.supports_named_graphs());
351    }
352
353    #[test]
354    fn test_set_current_graph() {
355        let mut parser = TrigStarParser::new(0);
356        parser.set_current_graph(123);
357        assert_eq!(parser.current_graph(), 123);
358    }
359
360    #[test]
361    fn test_parse_regular_triple() {
362        let mut parser = TrigStarParser::new(0);
363        let input =
364            b"<http://example.org/Alice> <http://example.org/knows> <http://example.org/Bob> .";
365        let result = parser.parse_triple(input);
366        assert!(result.is_ok());
367    }
368
369    #[test]
370    fn test_parse_embedded_triple() {
371        let mut parser = TrigStarParser::new(0);
372        let input = b"<<http://example.org/Alice http://example.org/knows http://example.org/Bob>> http://example.org/saidBy http://example.org/Charlie .";
373        let result = parser.parse_embedded_triple(input);
374        assert!(result.is_ok());
375    }
376}