1use rowan::SyntaxNode;
2
3use crate::Spanned;
4
5use super::parser::{Lang, SyntaxKind};
6use crate::model::*;
7
8type Node = SyntaxNode<Lang>;
9
10fn text_range(node: &Node) -> std::ops::Range<usize> {
11 let r = node.text_range();
12 r.start().into()..r.end().into()
13}
14
15fn child(node: &Node, kind: SyntaxKind) -> Option<Node> {
16 node.children().find(|c| c.kind() == kind)
17}
18
19fn children(node: &Node, kind: SyntaxKind) -> impl Iterator<Item = Node> {
20 node.children().filter(move |c| c.kind() == kind)
21}
22
23fn terminal_text(node: &Node) -> String {
24 node.text().to_string()
25}
26
27pub fn convert(root: &Node) -> Turtle {
28 let triples: Vec<Spanned<Triple>> = children(root, SyntaxKind::Triple)
29 .map(|t| {
30 let range = text_range(&t);
31 Spanned(convert_triple(&t), range)
32 })
33 .collect();
34
35 Turtle::new(None, Vec::new(), triples)
36}
37
38fn convert_triple(node: &Node) -> Triple {
39 let subject = child(node, SyntaxKind::Subject)
40 .map(|s| {
41 let range = text_range(&s);
42 Spanned(convert_subject(&s), range)
43 })
44 .unwrap_or_else(|| Spanned(Term::Invalid, text_range(node)));
45
46 let predicate = child(node, SyntaxKind::Predicate)
47 .map(|p| {
48 let range = text_range(&p);
49 Spanned(convert_predicate(&p), range)
50 })
51 .unwrap_or_else(|| Spanned(Term::Invalid, text_range(node)));
52
53 let object = child(node, SyntaxKind::Object)
54 .map(|o| {
55 let range = text_range(&o);
56 Spanned(convert_object(&o), range)
57 })
58 .unwrap_or_else(|| Spanned(Term::Invalid, text_range(node)));
59
60 let po_range = predicate.span().start..object.span().end;
61 let po = Spanned(
62 PO {
63 predicate,
64 object: vec![object],
65 },
66 po_range,
67 );
68
69 Triple {
70 subject,
71 po: vec![po],
72 graph: None,
73 }
74}
75
76fn convert_subject(node: &Node) -> Term {
77 if let Some(iriref) = child(node, SyntaxKind::Iriref) {
78 Term::NamedNode(iri_from_iriref_node(&iriref))
79 } else if let Some(bnode) = child(node, SyntaxKind::BlankNodeLabel) {
80 Term::BlankNode(blank_node_from_label(&bnode))
81 } else {
82 Term::Invalid
83 }
84}
85
86fn convert_predicate(node: &Node) -> Term {
87 if let Some(iriref) = child(node, SyntaxKind::Iriref) {
88 Term::NamedNode(iri_from_iriref_node(&iriref))
89 } else {
90 Term::Invalid
91 }
92}
93
94fn convert_object(node: &Node) -> Term {
95 if let Some(iriref) = child(node, SyntaxKind::Iriref) {
96 Term::NamedNode(iri_from_iriref_node(&iriref))
97 } else if let Some(bnode) = child(node, SyntaxKind::BlankNodeLabel) {
98 Term::BlankNode(blank_node_from_label(&bnode))
99 } else if let Some(lit) = child(node, SyntaxKind::Literal) {
100 Term::Literal(convert_literal(&lit))
101 } else {
102 Term::Invalid
103 }
104}
105
106fn iri_from_iriref_node(node: &Node) -> NamedNode {
107 let text = terminal_text(node);
108 let offset: usize = node.text_range().start().into();
109 let inner = text.trim_start_matches('<').trim_end_matches('>');
110 NamedNode::Full(inner.to_string(), offset)
111}
112
113fn blank_node_from_label(node: &Node) -> BlankNode {
114 let text = terminal_text(node);
115 let offset: usize = node.text_range().start().into();
116 let name = text.strip_prefix("_:").unwrap_or(&text);
117 BlankNode::Named(name.to_string(), offset)
118}
119
120fn convert_literal(node: &Node) -> Literal {
121 let offset: usize = node.text_range().start().into();
122
123 let (value, len) = if let Some(str_node) = child(node, SyntaxKind::StringLiteralQuote) {
124 let text = terminal_text(&str_node);
125 let inner = text
126 .strip_prefix('"')
127 .and_then(|s| s.strip_suffix('"'))
128 .unwrap_or(&text);
129 (inner.to_string(), 1)
130 } else {
131 (String::new(), 0)
132 };
133
134 let lang = child(node, SyntaxKind::Langtag).map(|n| {
135 let span = text_range(&n);
136 let text = terminal_text(&n);
137 let value = text.strip_prefix('@').unwrap_or(&text).to_string();
138 Spanned(value, span)
139 });
140
141 let ty =
142 child(node, SyntaxKind::Iriref).map(|n| Spanned(iri_from_iriref_node(&n), text_range(&n)));
143
144 Literal::RDF(RDFLiteral {
145 value,
146 quote_style: StringStyle::Double,
147 lang,
148 ty,
149 idx: offset,
150 len,
151 })
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use crate::{ntriples::parser as lang, parse as crate_parse};
158
159 fn parse(input: &str) -> Turtle {
160 let (result, _) = crate_parse(lang::Rule::new(lang::SyntaxKind::NtriplesDoc), input);
161 let root = result.syntax::<lang::Lang>();
162 convert(&root)
163 }
164
165 fn nn_eq(a: &NamedNode, b: &NamedNode) -> bool {
166 match (a, b) {
167 (NamedNode::Full(s1, _), NamedNode::Full(s2, _)) => s1 == s2,
168 _ => false,
169 }
170 }
171
172 fn term_nn(t: &Term) -> &NamedNode {
173 match t {
174 Term::NamedNode(nn) => nn,
175 other => panic!("expected NamedNode, got {:?}", other),
176 }
177 }
178
179 fn term_bn(t: &Term) -> &BlankNode {
180 match t {
181 Term::BlankNode(bn) => bn,
182 other => panic!("expected BlankNode, got {:?}", other),
183 }
184 }
185
186 fn term_lit(t: &Term) -> &Literal {
187 match t {
188 Term::Literal(l) => l,
189 other => panic!("expected Literal, got {:?}", other),
190 }
191 }
192
193 #[test]
194 fn simple_triple_with_iris() {
195 let doc = parse(
196 "<http://example.org/alice> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://xmlns.com/foaf/0.1/Person> .",
197 );
198 assert_eq!(doc.triples.len(), 1);
199 let t = doc.triples[0].value();
200 assert!(nn_eq(
201 term_nn(t.subject.value()),
202 &NamedNode::Full("http://example.org/alice".to_string(), 0)
203 ));
204 assert_eq!(t.po.len(), 1);
205 assert!(nn_eq(
206 term_nn(t.po[0].predicate.value()),
207 &NamedNode::Full(
208 "http://www.w3.org/1999/02/22-rdf-syntax-ns#type".to_string(),
209 0
210 )
211 ));
212 assert!(nn_eq(
213 term_nn(t.po[0].object[0].value()),
214 &NamedNode::Full("http://xmlns.com/foaf/0.1/Person".to_string(), 0)
215 ));
216 }
217
218 #[test]
219 fn blank_node_as_subject() {
220 let doc = parse("_:b1 <http://example.org/name> <http://example.org/value> .");
221 assert_eq!(doc.triples.len(), 1);
222 let t = doc.triples[0].value();
223 let bn = term_bn(t.subject.value());
224 assert!(matches!(bn, BlankNode::Named(name, _) if name == "b1"));
225 }
226
227 #[test]
228 fn blank_node_as_object() {
229 let doc = parse("<http://example.org/alice> <http://example.org/knows> _:b2 .");
230 assert_eq!(doc.triples.len(), 1);
231 let t = doc.triples[0].value();
232 let bn = term_bn(t.po[0].object[0].value());
233 assert!(matches!(bn, BlankNode::Named(name, _) if name == "b2"));
234 }
235
236 #[test]
237 fn string_literal_object() {
238 let doc = parse("<http://example.org/alice> <http://example.org/name> \"Alice\" .");
239 assert_eq!(doc.triples.len(), 1);
240 let t = doc.triples[0].value();
241 let lit = term_lit(t.po[0].object[0].value());
242 match lit {
243 Literal::RDF(rdf) => {
244 assert_eq!(rdf.value, "Alice");
245 assert_eq!(rdf.quote_style, StringStyle::Double);
246 assert!(rdf.lang.is_none());
247 assert!(rdf.ty.is_none());
248 }
249 other => panic!("expected RDF literal, got {:?}", other),
250 }
251 }
252
253 #[test]
254 fn literal_with_language_tag() {
255 let doc = parse("<http://example.org/alice> <http://example.org/name> \"Alice\"@en .");
256 assert_eq!(doc.triples.len(), 1);
257 let t = doc.triples[0].value();
258 let lit = term_lit(t.po[0].object[0].value());
259 match lit {
260 Literal::RDF(rdf) => {
261 assert_eq!(rdf.value, "Alice");
262 assert_eq!(rdf.lang.as_ref().map(|l| l.as_str()), Some("en"));
263 assert!(rdf.ty.is_none());
264 }
265 other => panic!("expected RDF literal, got {:?}", other),
266 }
267 }
268
269 #[test]
270 fn literal_with_datatype() {
271 let doc = parse(
272 "<http://example.org/alice> <http://example.org/age> \"30\"^^<http://www.w3.org/2001/XMLSchema#integer> .",
273 );
274 assert_eq!(doc.triples.len(), 1);
275 let t = doc.triples[0].value();
276 let lit = term_lit(t.po[0].object[0].value());
277 match lit {
278 Literal::RDF(rdf) => {
279 assert_eq!(rdf.value, "30");
280 assert!(rdf.lang.is_none());
281 let ty = rdf.ty.as_ref().expect("datatype should be present");
282 assert!(nn_eq(
283 ty,
284 &NamedNode::Full("http://www.w3.org/2001/XMLSchema#integer".to_string(), 0)
285 ));
286 }
287 other => panic!("expected RDF literal, got {:?}", other),
288 }
289 }
290
291 #[test]
292 fn multiple_triples() {
293 let doc = parse(concat!(
294 "<http://example.org/alice> <http://example.org/name> \"Alice\" .\n",
295 "<http://example.org/bob> <http://example.org/name> \"Bob\" .\n",
296 ));
297 assert_eq!(doc.triples.len(), 2);
298
299 let t0 = doc.triples[0].value();
300 assert!(nn_eq(
301 term_nn(t0.subject.value()),
302 &NamedNode::Full("http://example.org/alice".to_string(), 0)
303 ));
304
305 let t1 = doc.triples[1].value();
306 assert!(nn_eq(
307 term_nn(t1.subject.value()),
308 &NamedNode::Full("http://example.org/bob".to_string(), 0)
309 ));
310 }
311
312 fn parse_raw(input: &str) -> crate::Parse {
315 let (result, _) = crate_parse(lang::Rule::new(lang::SyntaxKind::NtriplesDoc), input);
316 result
317 }
318
319 #[test]
320 fn test_valid_input_has_no_errors() {
321 let p = parse_raw("<http://example.org/s> <http://example.org/p> <http://example.org/o> .");
322 assert_eq!(p.errors.len(), 0, "valid input should produce no errors");
323 }
324
325 #[test]
326 fn test_missing_trailing_dot_reports_error() {
327 let p = parse_raw("<http://example.org/s> <http://example.org/p> <http://example.org/o>");
328 assert!(
329 p.errors.len() > 0,
330 "missing trailing dot should produce an error"
331 );
332 assert!(
333 p.errors.iter().any(|e| e.contains("Stop")),
334 "expected an error mentioning Stop, got: {:?}",
335 p.errors.iter().collect::<Vec<_>>()
336 );
337 }
338
339 #[test]
340 fn no_prefixes_and_no_base() {
341 let doc = parse("<http://example.org/s> <http://example.org/p> <http://example.org/o> .");
342 assert!(doc.base.is_none());
343 assert!(doc.prefixes.is_empty());
344 }
345
346 use crate::{IncrementalBias, PrevParseInfo, TokenTrait, parse_incremental};
354
355 fn prev_info_incr(text: &str) -> PrevParseInfo {
356 let (_, tokens) = crate_parse(lang::Rule::new(lang::SyntaxKind::NtriplesDoc), text);
357 let mut depth: i32 = 0;
358 PrevParseInfo {
359 tokens: tokens
360 .iter()
361 .map(|t| {
362 let d = depth.clamp(0, 255) as u8;
363 depth += t.kind.bracket_delta() as i32;
364 t.to_prev_token(d)
365 })
366 .collect(),
367 had_errors: false,
368 }
369 }
370
371 fn parse_incr(before: &str, after: &str) -> Turtle {
372 let prev = prev_info_incr(before);
373 let (result, _) = parse_incremental(
374 lang::Rule::new(lang::SyntaxKind::NtriplesDoc),
375 after,
376 Some(&prev),
377 IncrementalBias::default(),
378 );
379 let root = result.syntax::<lang::Lang>();
380 convert(&root)
381 }
382
383 #[test]
386 fn test_incremental_remove_object() {
387 let doc = parse_incr(
388 "<http://a> <http://b> <http://c> .",
389 "<http://a> <http://b> .",
390 );
391 assert_eq!(doc.triples.len(), 1, "should produce one triple");
392 match doc.triples[0].value().subject.value() {
393 Term::NamedNode(NamedNode::Full(iri, _)) => {
394 assert_eq!(iri, "http://a")
395 }
396 other => panic!("expected http://a as subject, got {:?}", other),
397 }
398 }
399
400 #[test]
403 fn test_incremental_add_triple() {
404 let doc = parse_incr(
405 "<http://a> <http://b> <http://c> .",
406 "<http://a> <http://b> <http://c> .\n<http://d> <http://e> <http://f> .",
407 );
408 assert_eq!(doc.triples.len(), 2, "should produce two triples");
409 match doc.triples[0].value().subject.value() {
410 Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "http://a"),
411 other => panic!("expected http://a as first subject, got {:?}", other),
412 }
413 match doc.triples[1].value().subject.value() {
414 Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "http://d"),
415 other => panic!("expected http://d as second subject, got {:?}", other),
416 }
417 }
418
419 #[test]
422 fn test_incremental_change_subject() {
423 let doc = parse_incr(
424 "<http://a> <http://b> <http://c> .",
425 "<http://x> <http://b> <http://c> .",
426 );
427 assert_eq!(doc.triples.len(), 1, "should produce one triple");
428 match doc.triples[0].value().subject.value() {
429 Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "http://x"),
430 other => panic!("expected http://x as subject, got {:?}", other),
431 }
432 match doc.triples[0].value().po[0].predicate.value() {
433 Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "http://b"),
434 other => panic!("expected http://b as predicate, got {:?}", other),
435 }
436 }
437}