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 {
25 node.text().to_string()
26}
27
28pub fn convert(root: &Node) -> Turtle {
29 let mut base = None;
31 let mut prefixes = Vec::new();
32 let mut triples = Vec::new();
33
34 for stmt in children(root, SyntaxKind::Statement) {
35 if let Some(dir) = child(&stmt, SyntaxKind::Directive) {
36 if let Some(b) =
37 child(&dir, SyntaxKind::Base).or_else(|| child(&dir, SyntaxKind::SparqlBase))
38 {
39 base = Some(Spanned(convert_base(&b), text_range(&b)));
40 } else if let Some(p) =
41 child(&dir, SyntaxKind::PrefixId).or_else(|| child(&dir, SyntaxKind::SparqlPrefix))
42 {
43 prefixes.push(Spanned(convert_prefix(&p), text_range(&p)));
44 }
45 } else if let Some(t) = child(&stmt, SyntaxKind::Triples) {
46 let range = text_range(&stmt);
47 triples.push(Spanned(convert_triples(&t), range));
48 }
49 }
50
51 Turtle::new(base, prefixes, triples)
52}
53
54fn convert_base(node: &Node) -> Base {
55 let range = text_range(node);
56 let nn = child(node, SyntaxKind::Iriref)
58 .map(|n| iri_from_iriref_node(&n))
59 .or_else(|| child(node, SyntaxKind::Iri).map(|n| convert_iri(&n)))
60 .unwrap_or(NamedNode::Invalid);
61
62 let nn_range = child(node, SyntaxKind::Iriref)
63 .or_else(|| child(node, SyntaxKind::Iri))
64 .map(|n| text_range(&n))
65 .unwrap_or(range.clone());
66
67 Base(range, Spanned(nn, nn_range))
68}
69
70fn convert_prefix(node: &Node) -> TurtlePrefix {
71 let range = text_range(node);
72
73 let prefix_text = child(node, SyntaxKind::PnameNs)
76 .map(|n| {
77 let text = terminal_text(&n);
78 let tr = text_range(&n);
79 Spanned(text.trim_end_matches(':').to_string(), tr)
80 })
81 .unwrap_or_else(|| Spanned(String::new(), range.clone()));
82
83 let value = child(node, SyntaxKind::Iriref)
85 .map(|n| {
86 let tr = text_range(&n);
87 Spanned(iri_from_iriref_node(&n), tr)
88 })
89 .or_else(|| {
90 child(node, SyntaxKind::Iri).map(|n| {
91 let r = text_range(&n);
92 Spanned(convert_iri(&n), r)
93 })
94 })
95 .unwrap_or_else(|| Spanned(NamedNode::Invalid, range.clone()));
96
97 TurtlePrefix {
98 span: range,
99 prefix: prefix_text,
100 value,
101 }
102}
103
104fn convert_triples(node: &Node) -> Triple {
105 let (subject, po_node) = if let Some(bpl) = child(node, SyntaxKind::BlankNodePropertyList) {
107 let range = text_range(&bpl);
108 let subject = convert_blank_node_property_list(&bpl);
109 (
110 Spanned(subject, range),
111 child(node, SyntaxKind::PredicateObjectList),
112 )
113 } else if let Some(subj) = child(node, SyntaxKind::Subject) {
114 let range = text_range(&subj);
115 (
116 Spanned(convert_subject(&subj), range),
117 child(node, SyntaxKind::PredicateObjectList),
118 )
119 } else {
120 (Spanned(Term::Invalid, text_range(node)), None)
121 };
122
123 let po = po_node
124 .map(|n| convert_predicate_object_list(&n))
125 .unwrap_or_default();
126
127 Triple {
128 subject,
129 po,
130 graph: None,
131 }
132}
133
134fn convert_predicate_object_list(node: &Node) -> Vec<Spanned<PO>> {
135 let verbs: Vec<_> = children(node, SyntaxKind::Verb).collect();
137 let object_lists: Vec<_> = children(node, SyntaxKind::ObjectList).collect();
138
139 verbs
140 .into_iter()
141 .zip(object_lists.into_iter())
142 .map(|(v, ol)| {
143 let range = text_range(&v).start..text_range(&ol).end;
144 let pred_range = text_range(&v);
145 let predicate = convert_verb(&v);
146 let objects = convert_object_list(&ol);
147 Spanned(
148 PO {
149 predicate: Spanned(predicate, pred_range),
150 object: objects,
151 },
152 range,
153 )
154 })
155 .collect()
156}
157
158fn convert_verb(node: &Node) -> Term {
159 if let Some(pred) = child(node, SyntaxKind::Predicate) {
161 if let Some(iri) = child(&pred, SyntaxKind::Iri) {
162 Term::NamedNode(convert_iri(&iri))
163 } else {
164 Term::Invalid
165 }
166 } else if let Some(alit) = child(node, SyntaxKind::Alit) {
167 Term::NamedNode(NamedNode::A(alit.text_range().start().into()))
168 } else {
169 Term::Invalid
170 }
171}
172
173fn convert_object_list(node: &Node) -> Vec<Spanned<Term>> {
174 children(node, SyntaxKind::Object)
175 .map(|o| {
176 let range = text_range(&o);
177 Spanned(convert_object(&o), range)
178 })
179 .collect()
180}
181
182fn convert_subject(node: &Node) -> Term {
183 if let Some(iri) = child(node, SyntaxKind::Iri) {
185 Term::NamedNode(convert_iri(&iri))
186 } else if let Some(bn) = child(node, SyntaxKind::BlankNode) {
187 Term::BlankNode(convert_blank_node(&bn))
188 } else if let Some(coll) = child(node, SyntaxKind::Collection) {
189 Term::Collection(convert_collection(&coll))
190 } else {
191 Term::Invalid
192 }
193}
194
195fn convert_object(node: &Node) -> Term {
196 if let Some(iri) = child(node, SyntaxKind::Iri) {
198 Term::NamedNode(convert_iri(&iri))
199 } else if let Some(label) = child(node, SyntaxKind::BlankNodeLabel) {
200 let text = terminal_text(&label);
201 let offset: usize = label.text_range().start().into();
202 let name = text.strip_prefix("_:").unwrap_or(&text);
203 Term::BlankNode(BlankNode::Named(name.to_string(), offset))
204 } else if let Some(bn) = child(node, SyntaxKind::BlankNode) {
205 Term::BlankNode(convert_blank_node(&bn))
206 } else if let Some(coll) = child(node, SyntaxKind::Collection) {
207 Term::Collection(convert_collection(&coll))
208 } else if let Some(bpl) = child(node, SyntaxKind::BlankNodePropertyList2) {
209 convert_blank_node_property_list(&bpl)
210 } else if let Some(lit) = child(node, SyntaxKind::Literal) {
211 Term::Literal(convert_literal(&lit))
212 } else {
213 Term::Invalid
214 }
215}
216
217fn convert_iri(node: &Node) -> NamedNode {
218 if let Some(iriref_node) = child(node, SyntaxKind::Iriref) {
221 iri_from_iriref_node(&iriref_node)
222 } else if let Some(pn) = child(node, SyntaxKind::PrefixedName) {
223 convert_prefixed_name(&pn)
224 } else {
225 NamedNode::Invalid
226 }
227}
228
229fn iri_from_iriref_node(node: &Node) -> NamedNode {
230 let text = terminal_text(node);
231 let offset: usize = node.text_range().start().into();
232 let inner = text.trim_start_matches('<').trim_end_matches('>');
233 NamedNode::Full(inner.to_string(), offset)
234}
235
236fn convert_prefixed_name(node: &Node) -> NamedNode {
237 if let Some(pname_ln) = child(node, SyntaxKind::PnameLn) {
239 let text = terminal_text(&pname_ln);
240 let offset: usize = pname_ln.text_range().start().into();
241 let (prefix, value) = text.split_once(':').unwrap_or((&text, ""));
242 NamedNode::Prefixed {
243 prefix: prefix.to_string(),
244 value: value.to_string(),
245 idx: offset,
246 computed: None,
247 }
248 } else if let Some(pname_ns) = child(node, SyntaxKind::PnameNs) {
249 let text = terminal_text(&pname_ns);
250 let offset: usize = pname_ns.text_range().start().into();
251 let prefix = text.trim_end_matches(':');
252 NamedNode::Prefixed {
253 prefix: prefix.to_string(),
254 value: String::new(),
255 idx: offset,
256 computed: None,
257 }
258 } else {
259 NamedNode::Invalid
260 }
261}
262
263fn convert_blank_node(node: &Node) -> BlankNode {
264 if let Some(label) = child(node, SyntaxKind::BlankNodeLabel) {
266 let text = terminal_text(&label);
267 let offset: usize = label.text_range().start().into();
268 let name = text.strip_prefix("_:").unwrap_or(&text);
269 BlankNode::Named(name.to_string(), offset)
270 } else {
271 let offset: usize = node.text_range().start().into();
273 BlankNode::Unnamed(Vec::new(), offset, offset)
274 }
275}
276
277fn convert_blank_node_property_list(node: &Node) -> Term {
278 let offset: usize = node.text_range().start().into();
280 let end: usize = node.text_range().end().into();
281 let po = child(node, SyntaxKind::PredicateObjectList)
282 .map(|n| convert_predicate_object_list(&n))
283 .unwrap_or_default();
284 Term::BlankNode(BlankNode::Unnamed(po, offset, end))
285}
286
287fn convert_collection(node: &Node) -> Vec<Spanned<Term>> {
288 children(node, SyntaxKind::Object)
289 .map(|o| {
290 let range = text_range(&o);
291 Spanned(convert_object(&o), range)
292 })
293 .collect()
294}
295
296fn convert_literal(node: &Node) -> Literal {
297 if let Some(rdf) = child(node, SyntaxKind::Rdfliteral) {
299 Literal::RDF(convert_rdf_literal(&rdf))
300 } else if let Some(num) = child(node, SyntaxKind::NumericLiteral) {
301 Literal::Numeric(num.text().to_string().trim().to_string())
302 } else if let Some(b) = child(node, SyntaxKind::BooleanLiteral) {
303 Literal::Boolean(b.text().to_string().trim() == "true")
304 } else {
305 Literal::Numeric(String::new())
306 }
307}
308
309fn convert_rdf_literal(node: &Node) -> RDFLiteral {
310 let offset: usize = node.text_range().start().into();
312
313 let (value, quote_style, len) = if let Some(str_node) = child(node, SyntaxKind::MyString) {
314 let string_node = str_node.children().next();
316 if let Some(sn) = string_node {
317 let text = terminal_text(&sn);
318 let (inner, style) = strip_string_delimiters(&text);
319 (inner.to_string(), style, 1)
320 } else {
321 (String::new(), StringStyle::Double, 0)
322 }
323 } else {
324 (String::new(), StringStyle::Double, 0)
325 };
326
327 let lang = child(node, SyntaxKind::Langtag).map(|n| {
328 let span = text_range(&n);
329 let text = terminal_text(&n);
330 let value = text.strip_prefix('@').unwrap_or(&text).to_string();
331 Spanned(value, span)
332 });
333
334 let ty = child(node, SyntaxKind::Iri).map(|n| Spanned(convert_iri(&n), text_range(&n)));
335
336 RDFLiteral {
337 value,
338 quote_style,
339 lang,
340 ty,
341 idx: offset,
342 len,
343 }
344}
345
346fn strip_string_delimiters(text: &str) -> (&str, StringStyle) {
347 if let Some(inner) = text
348 .strip_prefix("\"\"\"")
349 .and_then(|s| s.strip_suffix("\"\"\""))
350 {
351 (inner, StringStyle::DoubleLong)
352 } else if let Some(inner) = text.strip_prefix("'''").and_then(|s| s.strip_suffix("'''")) {
353 (inner, StringStyle::SingleLong)
354 } else if let Some(inner) = text.strip_prefix('"').and_then(|s| s.strip_suffix('"')) {
355 (inner, StringStyle::Double)
356 } else if let Some(inner) = text.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
357 (inner, StringStyle::Single)
358 } else {
359 (text, StringStyle::Double)
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use rowan::TextSize;
366
367 use super::*;
368 use crate::{parse as crate_parse, turtle::parser as lang};
369
370 fn parse(input: &str) -> Turtle {
371 let (result, _) = crate_parse(lang::Rule::new(lang::SyntaxKind::TurtleDoc), input);
372 let root = result.syntax::<lang::Lang>();
373 convert(&root)
374 }
375
376 fn prefixed(prefix: &str, value: &str) -> NamedNode {
379 NamedNode::Prefixed {
380 prefix: prefix.to_string(),
381 value: value.to_string(),
382 idx: 0, computed: None,
384 }
385 }
386
387 fn nn_eq(a: &NamedNode, b: &NamedNode) -> bool {
389 match (a, b) {
390 (NamedNode::Full(s1, _), NamedNode::Full(s2, _)) => s1 == s2,
391 (
392 NamedNode::Prefixed {
393 prefix: p1,
394 value: v1,
395 ..
396 },
397 NamedNode::Prefixed {
398 prefix: p2,
399 value: v2,
400 ..
401 },
402 ) => p1 == p2 && v1 == v2,
403 (NamedNode::A(_), NamedNode::A(_)) => true,
404 (NamedNode::Invalid, NamedNode::Invalid) => true,
405 _ => false,
406 }
407 }
408
409 fn term_nn(t: &Term) -> &NamedNode {
410 match t {
411 Term::NamedNode(nn) => nn,
412 other => panic!("expected NamedNode, got {:?}", other),
413 }
414 }
415
416 fn term_lit(t: &Term) -> &Literal {
417 match t {
418 Term::Literal(l) => l,
419 other => panic!("expected Literal, got {:?}", other),
420 }
421 }
422
423 fn term_bn(t: &Term) -> &BlankNode {
424 match t {
425 Term::BlankNode(bn) => bn,
426 other => panic!("expected BlankNode, got {:?}", other),
427 }
428 }
429
430 #[test]
433 fn test_prefix_directive() {
434 let doc = parse("@prefix ex: <http://example.org/> .");
435 assert_eq!(doc.prefixes.len(), 1);
436 let p = doc.prefixes[0].value();
437 assert_eq!(p.prefix.value(), "ex");
438 assert!(nn_eq(
439 p.value.value(),
440 &NamedNode::Full("http://example.org/".to_string(), 0)
441 ));
442 }
443
444 #[test]
445 fn test_sparql_prefix_directive() {
446 let doc = parse("PREFIX ex: <http://example.org/>");
447 assert_eq!(doc.prefixes.len(), 1);
448 assert_eq!(doc.prefixes[0].prefix.value(), "ex");
449 }
450
451 #[test]
454 fn test_base_directive() {
455 let doc = parse("@base <http://example.org/> .");
456 let base = doc.base.as_ref().expect("base should be present");
457 assert!(nn_eq(
458 base.value().1.value(),
459 &NamedNode::Full("http://example.org/".to_string(), 0)
460 ));
461 }
462
463 #[test]
466 fn test_full_iri_subject_and_predicate() {
467 let doc = parse(
468 "<http://example.org/alice> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://xmlns.com/foaf/0.1/Person> .",
469 );
470 assert_eq!(doc.triples.len(), 1);
471 let t = doc.triples[0].value();
472 assert!(nn_eq(
473 term_nn(t.subject.value()),
474 &NamedNode::Full("http://example.org/alice".to_string(), 0)
475 ));
476 assert_eq!(t.po.len(), 1);
477 assert!(nn_eq(
478 term_nn(t.po[0].predicate.value()),
479 &NamedNode::Full(
480 "http://www.w3.org/1999/02/22-rdf-syntax-ns#type".to_string(),
481 0
482 )
483 ));
484 assert!(nn_eq(
485 term_nn(t.po[0].object[0].value()),
486 &NamedNode::Full("http://xmlns.com/foaf/0.1/Person".to_string(), 0)
487 ));
488 }
489
490 #[test]
493 fn test_a_shorthand() {
494 let doc = parse(
495 "@prefix foaf: <http://xmlns.com/foaf/0.1/> . <http://example.org/alice> a foaf:Person .",
496 );
497 let t = doc.triples[0].value();
498 assert!(matches!(
499 t.po[0].predicate.value(),
500 Term::NamedNode(NamedNode::A(_))
501 ));
502 assert!(nn_eq(
503 term_nn(t.po[0].object[0].value()),
504 &prefixed("foaf", "Person")
505 ));
506 }
507
508 #[test]
511 fn test_prefixed_name_subject() {
512 let doc = parse("@prefix ex: <http://example.org/> . ex:alice ex:knows ex:bob .");
513 let t = doc.triples[0].value();
514 assert!(nn_eq(term_nn(t.subject.value()), &prefixed("ex", "alice")));
515 assert!(nn_eq(
516 term_nn(t.po[0].object[0].value()),
517 &prefixed("ex", "bob")
518 ));
519 }
520
521 #[test]
524 fn test_multiple_po_pairs() {
525 let doc =
526 parse("@prefix ex: <http://example.org/> . ex:alice ex:name \"Alice\" ; ex:age 30 .");
527 let t = doc.triples[0].value();
528 assert_eq!(t.po.len(), 2);
529 assert!(nn_eq(
530 term_nn(t.po[0].predicate.value()),
531 &prefixed("ex", "name")
532 ));
533 assert!(nn_eq(
534 term_nn(t.po[1].predicate.value()),
535 &prefixed("ex", "age")
536 ));
537 }
538
539 #[test]
542 fn test_multiple_objects() {
543 let doc =
544 parse("@prefix ex: <http://example.org/> . ex:alice ex:knows ex:bob , ex:carol .");
545 let t = doc.triples[0].value();
546 assert_eq!(t.po[0].object.len(), 2);
547 assert!(nn_eq(
548 term_nn(t.po[0].object[0].value()),
549 &prefixed("ex", "bob")
550 ));
551 assert!(nn_eq(
552 term_nn(t.po[0].object[1].value()),
553 &prefixed("ex", "carol")
554 ));
555 }
556
557 #[test]
560 fn test_string_literal() {
561 let doc = parse("@prefix ex: <http://example.org/> . ex:alice ex:name \"Alice\" .");
562 let t = doc.triples[0].value();
563 let lit = term_lit(t.po[0].object[0].value());
564 assert_eq!(lit.plain_string(), "Alice");
565 }
566
567 #[test]
568 fn test_string_literal_with_lang() {
569 let src = "@prefix ex: <http://example.org/> . ex:alice ex:name \"Alice\"@en .";
570 let doc = parse(src);
571 let t = doc.triples[0].value();
572 match term_lit(t.po[0].object[0].value()) {
573 Literal::RDF(r) => {
574 assert_eq!(r.value, "Alice");
575 let lang = r.lang.as_ref().expect("lang tag should be present");
576 assert_eq!(lang.as_str(), "en");
577 assert_eq!(&src[lang.span().clone()], "@en");
578 }
579 other => panic!("expected RDF literal, got {:?}", other),
580 }
581 }
582
583 #[test]
584 fn test_string_literal_with_datatype() {
585 let src = "@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . @prefix ex: <http://example.org/> . ex:alice ex:age \"30\"^^xsd:integer .";
586 let doc = parse(src);
587 let t = doc.triples[0].value();
588 match term_lit(t.po[0].object[0].value()) {
589 Literal::RDF(r) => {
590 assert_eq!(r.value, "30");
591 let ty = r.ty.as_ref().expect("datatype should be present");
592 assert!(nn_eq(ty, &prefixed("xsd", "integer")));
593 assert_eq!(&src[ty.span().clone()], "xsd:integer");
594 }
595 other => panic!("expected RDF literal, got {:?}", other),
596 }
597 }
598
599 #[test]
602 fn test_numeric_literal() {
603 let doc = parse("@prefix ex: <http://example.org/> . ex:alice ex:age 30 .");
604 let t = doc.triples[0].value();
605 match term_lit(t.po[0].object[0].value()) {
606 Literal::Numeric(n) => assert_eq!(n, "30"),
607 other => panic!("expected Numeric literal, got {:?}", other),
608 }
609 }
610
611 #[test]
614 fn test_boolean_literal() {
615 let doc = parse("@prefix ex: <http://example.org/> . ex:alice ex:active true .");
616 let t = doc.triples[0].value();
617 assert_eq!(*term_lit(t.po[0].object[0].value()), Literal::Boolean(true));
618 }
619
620 #[test]
623 fn test_named_blank_node() {
624 let doc = parse("@prefix ex: <http://example.org/> . _:b0 ex:knows ex:alice .");
625 let t = doc.triples[0].value();
626 match term_bn(t.subject.value()) {
627 BlankNode::Named(name, _) => assert_eq!(name, "b0"),
628 other => panic!("expected Named blank node, got {:?}", other),
629 }
630 }
631
632 #[test]
635 fn test_anon_blank_node_property_list() {
636 let doc =
637 parse("@prefix ex: <http://example.org/> . ex:alice ex:knows [ ex:name \"Bob\" ] .");
638 let t = doc.triples[0].value();
639 match term_bn(t.po[0].object[0].value()) {
640 BlankNode::Unnamed(pos, _, _) => {
641 assert_eq!(pos.len(), 1);
642 assert!(nn_eq(
643 term_nn(pos[0].predicate.value()),
644 &prefixed("ex", "name")
645 ));
646 }
647 other => panic!("expected Unnamed blank node, got {:?}", other),
648 }
649 }
650
651 #[test]
654 fn test_blank_node_subject_property_list() {
655 let doc = parse("@prefix ex: <http://example.org/> . [ ex:name \"Alice\" ] ex:age 30 .");
656 let t = doc.triples[0].value();
657 assert!(matches!(
658 t.subject.value(),
659 Term::BlankNode(BlankNode::Unnamed(_, _, _))
660 ));
661 assert_eq!(t.po.len(), 1);
662 }
663
664 #[test]
667 fn test_collection() {
668 let doc =
669 parse("@prefix ex: <http://example.org/> . ex:alice ex:list ( ex:a ex:b ex:c ) .");
670 let t = doc.triples[0].value();
671 match t.po[0].object[0].value() {
672 Term::Collection(items) => {
673 assert_eq!(items.len(), 3);
674 assert!(nn_eq(term_nn(items[0].value()), &prefixed("ex", "a")));
675 assert!(nn_eq(term_nn(items[1].value()), &prefixed("ex", "b")));
676 assert!(nn_eq(term_nn(items[2].value()), &prefixed("ex", "c")));
677 }
678 other => panic!("expected Collection, got {:?}", other),
679 }
680 }
681
682 fn parse_raw(input: &str) -> crate::Parse {
685 let (result, _) = crate_parse(lang::Rule::new(lang::SyntaxKind::TurtleDoc), input);
686 result
687 }
688
689 #[test]
690 fn test_valid_input_has_no_errors() {
691 let p = parse_raw("@prefix ex: <http://example.org/> . ex:alice ex:age 30 .");
692 assert_eq!(p.errors.len(), 0, "valid input should produce no errors");
693 }
694
695 #[test]
696 fn test_verb_is_verb() {
697 let p = parse("<a> <b>.");
698 let predicate = p.triples[0].po[0].predicate.value();
699 assert!(nn_eq(
700 term_nn(predicate),
701 &NamedNode::Full("b".to_string(), 0)
702 ))
703 }
704
705 #[test]
706 fn test_missing_trailing_dot_reports_error() {
707 let p = parse_raw("@prefix ex: <http://example.org/> . ex:alice ex:age 30");
708 assert!(
709 p.errors.len() > 0,
710 "missing trailing dot should produce an error"
711 );
712 assert!(
714 p.errors.iter().any(|e| e.contains("Stop")),
715 "expected an error mentioning Stop, got: {:?}",
716 p.errors.iter().collect::<Vec<_>>()
717 );
718 }
719
720 #[test]
721 fn test_missing_prefix_iri_reports_error() {
722 let p = parse_raw("@prefix ex: .");
724 assert!(
725 p.errors.len() > 0,
726 "missing prefix IRI should produce an error"
727 );
728 }
729
730 #[test]
731 fn test_recovery_missing_trailing_dot_still_extracts_triple() {
732 let doc = parse("@prefix ex: <http://example.org/> . ex:alice ex:age 30");
735 assert_eq!(doc.triples.len(), 1);
736 assert!(nn_eq(
737 term_nn(doc.triples[0].subject.value()),
738 &prefixed("ex", "alice")
739 ));
740 match term_lit(doc.triples[0].po[0].object[0].value()) {
741 Literal::Numeric(n) => assert_eq!(n, "30"),
742 other => panic!("expected Numeric, got {:?}", other),
743 }
744 }
745
746 #[test]
747 fn test_recovery_second_triple_after_missing_dot() {
748 let input = "@prefix ex: <http://example.org/> .\n\
751 ex:alice ex:age 30\n\
752 ex:bob ex:age 25 .";
753 let p = parse_raw(input);
754 let root = p.syntax::<lang::Lang>();
755 println!("Parse tree:\n{:#?}", root);
756 assert!(p.errors.len() > 0, "should report at least one error");
757
758 let doc = parse(input);
759 assert_eq!(doc.triples.len(), 2, "both triples should be recovered");
760 assert!(nn_eq(
761 term_nn(doc.triples[0].subject.value()),
762 &prefixed("ex", "alice")
763 ));
764 assert!(nn_eq(
765 term_nn(doc.triples[1].subject.value()),
766 &prefixed("ex", "bob")
767 ));
768 }
769
770 #[test]
773 fn test_multiple_triples() {
774 let doc =
775 parse("@prefix ex: <http://example.org/> . ex:alice ex:age 30 . ex:bob ex:age 25 .");
776 assert_eq!(doc.triples.len(), 2);
777 assert!(nn_eq(
778 term_nn(doc.triples[0].subject.value()),
779 &prefixed("ex", "alice")
780 ));
781 assert!(nn_eq(
782 term_nn(doc.triples[1].subject.value()),
783 &prefixed("ex", "bob")
784 ));
785 }
786
787 use crate::{IncrementalBias, PrevParseInfo, TokenTrait, parse_incremental};
790
791 fn prev_info(text: &str) -> PrevParseInfo {
792 let (_, tokens) = crate_parse(lang::Rule::new(lang::SyntaxKind::TurtleDoc), text);
793 let mut depth: i32 = 0;
794 PrevParseInfo {
795 tokens: tokens
796 .iter()
797 .map(|t| {
798 let d = depth.clamp(0, 255) as u8;
799 depth += t.kind.bracket_delta() as i32;
800 t.to_prev_token(d)
801 })
802 .collect(),
803 had_errors: false,
804 }
805 }
806
807 #[test]
818 fn test_incremental_two_triples_from_inserted_token() {
819 let prev = prev_info("<b> <c> <d> .");
820 let bias = IncrementalBias::default();
821 let (parse, _) = parse_incremental(
822 lang::Rule::new(lang::SyntaxKind::TurtleDoc),
823 "<a> <b> <c> <d> .",
824 Some(&prev),
825 bias,
826 );
827 let root = parse.syntax::<lang::Lang>();
828 let doc = convert(&root);
829
830 match doc.triples.len() {
835 1 => {
836 let t = doc.triples[0].value();
838 match t.subject.value() {
839 Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "b"),
840 other => panic!("expected <b> as subject, got {:?}", other),
841 }
842 }
843 2 => {
844 match doc.triples[0].value().subject.value() {
846 Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "a"),
847 other => panic!("expected <a> as first subject, got {:?}", other),
848 }
849 let t2 = doc.triples[1].value();
850 match t2.subject.value() {
851 Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "b"),
852 other => panic!("expected <b> as subject of second triple, got {:?}", other),
853 }
854 }
855 n => panic!("expected 1 or 2 triples, got {}", n),
856 }
857
858 let last = doc.triples.last().unwrap().value();
860 assert_eq!(last.po.len(), 1);
861 match last.po[0].value().predicate.value() {
862 Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "c"),
863 other => panic!("expected <c> as predicate, got {:?}", other),
864 }
865 assert_eq!(last.po[0].value().object.len(), 1);
866 match last.po[0].value().object[0].value() {
867 Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "d"),
868 other => panic!("expected <d> as object, got {:?}", other),
869 }
870 }
871
872 #[test]
873 fn test_incremental_remove_object() {
874 let prev = prev_info("<a> <b> <c> .");
875 let bias = IncrementalBias::default();
876 let (parse, _) = parse_incremental(
877 lang::Rule::new(lang::SyntaxKind::TurtleDoc),
878 "<a> <b> .",
879 Some(&prev),
880 bias,
881 );
882 let root = parse.syntax::<lang::Lang>();
883 let doc = convert(&root);
884
885 assert_eq!(doc.triples.len(), 1, "should produce two triples");
886
887 match doc.triples[0].value().subject.value() {
890 Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "a"),
891 other => panic!("expected <a> as first subject, got {:?}", other),
892 }
893 }
894
895 #[test]
896 fn test_incremental_remove_prefix() {
897 let prev =
898 prev_info("@prefix foaf: <http://xmlns.com/foaf/0.1/> .\n <a> foaf:knows ex:Bob .");
899 let bias = IncrementalBias::default();
900 let (parse, _) = parse_incremental(
901 lang::Rule::new(lang::SyntaxKind::TurtleDoc),
902 "<a> foaf:knows ex:Bob .",
903 Some(&prev),
904 bias,
905 );
906 let root = parse.syntax::<lang::Lang>();
907 let doc = convert(&root);
908
909 assert_eq!(doc.triples.len(), 1, "should produce one triples");
910
911 match doc.triples[0].value().subject.value() {
914 Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "a"),
915 other => panic!("expected <a> as first subject, got {:?}", other),
916 }
917 }
918
919 #[test]
920 fn test_suggest_correct_sq_close_location() {
921 let before = r#"ex:Bob foaf: [
922 foaf:knows [
923 foaf:name "Alice"
924 ];
925 foaf:name "Bob";
926 ]."#;
927
928 let after = r#"ex:Bob foaf: [
929 foaf:knows [
930 foaf:name "Alice"
931 ;
932 foaf:name "Bob";
933 ]."#;
934 let prev = prev_info(before);
935 let bias = IncrementalBias::default();
936 let (parse, _) = parse_incremental(
937 lang::Rule::new(lang::SyntaxKind::TurtleDoc),
938 after,
939 Some(&prev),
940 bias,
941 );
942 let root = parse.syntax::<lang::Lang>();
943
944 let first_location = before.find(']').unwrap_or_default();
945 let error_span = root
946 .descendants_with_tokens()
947 .filter(|t| t.kind() == lang::SyntaxKind::Error)
948 .map(|t| t.text_range())
949 .next()
950 .expect("has an error");
951
952 println!("error span {:?} location {}", error_span, first_location);
953 let loc = TextSize::new(first_location as u32);
954 assert!(
955 error_span.contains(loc) || error_span.start() == loc,
956 "The error span should start at or include the location of the removed bracket"
957 );
958 }
959
960 #[test]
961 fn test_suggest_completing_triple() {
962 let before = r#"ex:Alice a foaf:Person ;
963 foaf:knows ex:Bob . "#;
964
965 let after = r#"ex:Alice a foaf:Person ; <a>
966 foaf:knows ex:Bob . "#;
967 let prev = prev_info(before);
968 let bias = IncrementalBias::default();
969 let (parse, _) = parse_incremental(
970 lang::Rule::new(lang::SyntaxKind::TurtleDoc),
971 after,
972 Some(&prev),
973 bias,
974 );
975
976 let root = parse.syntax::<lang::Lang>();
977 let doc = convert(&root);
978 assert_eq!(doc.triples.len(), 1, "should produce one triples");
979 assert_eq!(
980 doc.triples[0].0.po.len(),
981 3,
982 "should produce three predicate objects"
983 );
984
985 let errors: Vec<_> = parse.errors.iter().collect();
986
987 assert_eq!(errors.len(), 2, "expect a term and a semi colon");
988 }
989
990 #[test]
991 fn test_suggest_completing_triple_2() {
992 let before = r#"ex:Alice a foaf:Person ;
993 foaf:knows ex:Bob . "#;
994
995 let after_1 = r#"ex:Alice a foaf:Person ; <a>
996 foaf:knows ex:Bob . "#;
997
998 let after_2 = r#"ex:Alice a foaf:Person ; <a> <b>
999 foaf:knows ex:Bob . "#;
1000 let prev = prev_info(before);
1001 let bias = IncrementalBias::default();
1002 let (_, prev) = parse_incremental(
1003 lang::Rule::new(lang::SyntaxKind::TurtleDoc),
1004 after_1,
1005 Some(&prev),
1006 bias,
1007 );
1008 let (parse, _) = parse_incremental(
1009 lang::Rule::new(lang::SyntaxKind::TurtleDoc),
1010 after_2,
1011 Some(&prev),
1012 bias,
1013 );
1014
1015 let root = parse.syntax::<lang::Lang>();
1016 let doc = convert(&root);
1017 assert_eq!(doc.triples.len(), 1, "should produce one triples");
1018 assert_eq!(
1019 doc.triples[0].0.po.len(),
1020 3,
1021 "should produce three predicate objects"
1022 );
1023
1024 let errors: Vec<_> = parse.errors.iter().collect();
1025
1026 assert_eq!(errors.len(), 1, "expect a semi colon");
1027 }
1028
1029 #[test]
1030 fn test_suggest_inside_blank_node() {
1031 let before = r#"<a> a <b> ;
1032 <knows> [ ] ."#;
1033
1034 let after_1 = r#"<a> a <b> ;
1035 <knows> [ ] ."#;
1036 let after_2 = r#"<a> a <b> ;
1040 <knows> [ <name> ] ."#;
1041
1042 let prev = prev_info(before);
1043 let bias = IncrementalBias::default();
1044
1045 let (_, prev) = parse_incremental(
1046 lang::Rule::new(lang::SyntaxKind::TurtleDoc),
1047 after_1,
1048 Some(&prev),
1049 bias,
1050 );
1051 let (parse, _) = parse_incremental(
1052 lang::Rule::new(lang::SyntaxKind::TurtleDoc),
1053 after_2,
1054 Some(&prev),
1055 bias,
1056 );
1057
1058 let root = parse.syntax::<lang::Lang>();
1059 let doc = convert(&root);
1060 assert_eq!(doc.triples.len(), 1, "should produce one triples");
1061 assert_eq!(
1062 doc.triples[0].0.po.len(),
1063 2,
1064 "should produce two predicate objects"
1065 );
1066 }
1067
1068 #[test]
1069 fn test_changing_whitespace_does_not_influence_errors() {
1070 let before = r#"<a> a <b> . "#;
1071
1072 let after_1 = r#"<a> <b> . "#;
1073 let after_2 = r#"<a> <b> . "#;
1074
1075 let prev = prev_info(before);
1076 let bias = IncrementalBias::default();
1077
1078 let (parse, prev) = parse_incremental(
1079 lang::Rule::new(lang::SyntaxKind::TurtleDoc),
1080 after_1,
1081 Some(&prev),
1082 bias,
1083 );
1084 assert!(
1085 parse
1086 .errors
1087 .iter()
1088 .any(|x| x.to_lowercase().contains("verb")),
1089 "expected a verb error; got: {:?}",
1090 parse.errors,
1091 );
1092 let (parse, _) = parse_incremental(
1093 lang::Rule::new(lang::SyntaxKind::TurtleDoc),
1094 after_2,
1095 Some(&prev),
1096 bias,
1097 );
1098 assert!(
1099 parse
1100 .errors
1101 .iter()
1102 .any(|x| x.to_lowercase().contains("verb")),
1103 "expected a verb error; got: {:?}",
1104 parse.errors,
1105 );
1106 }
1107
1108 #[test]
1112 fn test_remove_rdf_type_verb_error_names_verb_rule() {
1113 let prev = prev_info("<s> a <o> .");
1114 let bias = IncrementalBias::default();
1115 let (parse, _) = parse_incremental(
1116 lang::Rule::new(lang::SyntaxKind::TurtleDoc),
1117 "<s> <o> .",
1118 Some(&prev),
1119 bias,
1120 );
1121
1122 let errors: Vec<_> = parse.errors.iter().collect();
1123 assert!(
1124 !errors.is_empty(),
1125 "removing the verb should produce an error, got none"
1126 );
1127 assert!(
1128 errors.iter().any(|e| e.to_lowercase().contains("verb")),
1129 "error should name the Verb grammar rule, got: {:?}",
1130 errors
1131 );
1132 assert!(
1133 !errors.iter().any(|e| e.to_lowercase().contains("alit")),
1134 "error should not expose the low-level Alit terminal, got: {:?}",
1135 errors
1136 );
1137 }
1138
1139 #[test]
1144 fn test_remove_verb_from_iri_triple_reports_verb_error() {
1145 let prev = prev_info("<b> a <c> .");
1146 let bias = IncrementalBias::default();
1147 let (parse, _) = parse_incremental(
1148 lang::Rule::new(lang::SyntaxKind::TurtleDoc),
1149 "<b> <c> .",
1150 Some(&prev),
1151 bias,
1152 );
1153
1154 let errors: Vec<_> = parse.errors.iter().collect();
1155 assert!(
1156 errors.iter().any(|e| e.to_lowercase().contains("verb")),
1157 "error should name the Verb grammar rule, got: {:?}",
1158 errors
1159 );
1160
1161 let root = parse.syntax::<lang::Lang>();
1162 let error_node = root
1163 .descendants()
1164 .find(|n| n.kind() == lang::SyntaxKind::Error)
1165 .expect("should have an error node");
1166 let effective = crate::effective_error_span::<lang::Lang>(&error_node);
1167 assert_eq!(
1168 effective.end - effective.start,
1169 1,
1170 "effective_error_span should cover the whitespace gap (1 byte), got {:?}",
1171 effective
1172 );
1173 }
1174
1175 use crate::parse_fast;
1178
1179 fn parse_fast_doc(input: &str) -> Option<Turtle> {
1180 let (result, _) = parse_fast(lang::Rule::new(lang::SyntaxKind::TurtleDoc), input)?;
1181 let root = result.syntax::<lang::Lang>();
1182 Some(convert(&root))
1183 }
1184
1185 fn parse_fast_raw(input: &str) -> Option<crate::Parse> {
1186 let (result, _) = parse_fast(lang::Rule::new(lang::SyntaxKind::TurtleDoc), input)?;
1187 Some(result)
1188 }
1189
1190 #[test]
1192 fn test_fast_valid_simple_triple() {
1193 let doc = parse_fast_doc("<http://a> <http://b> <http://c> .")
1194 .expect("valid input should return Some");
1195 assert_eq!(doc.triples.len(), 1);
1196 }
1197
1198 #[test]
1200 fn test_fast_returns_none_on_error() {
1201 assert!(
1203 parse_fast_doc("<http://a> <http://b> <http://c>").is_none(),
1204 "document with missing dot should return None"
1205 );
1206 }
1207
1208 #[test]
1210 fn test_fast_returns_none_on_mid_document_error() {
1211 assert!(
1214 parse_fast_doc("<http://a> <http://b> <http://c> . <http://dangling>").is_none(),
1215 "incomplete trailing statement should return None"
1216 );
1217 }
1218
1219 #[test]
1221 fn test_fast_produces_no_errors() {
1222 let p = parse_fast_raw("@prefix ex: <http://example.org/> . ex:alice ex:age 30 .")
1223 .expect("valid input should return Some");
1224 assert_eq!(
1225 p.errors.len(),
1226 0,
1227 "fast parse of valid input should have no errors"
1228 );
1229 }
1230
1231 #[test]
1233 fn test_fast_prefix_directive() {
1234 let doc = parse_fast_doc("@prefix ex: <http://example.org/> . ex:alice ex:knows ex:bob .")
1235 .expect("valid input should return Some");
1236 assert_eq!(doc.prefixes.len(), 1);
1237 assert_eq!(doc.triples.len(), 1);
1238 assert!(nn_eq(
1239 term_nn(doc.triples[0].subject.value()),
1240 &prefixed("ex", "alice")
1241 ));
1242 }
1243
1244 #[test]
1246 fn test_fast_multiple_triples() {
1247 let doc = parse_fast_doc(
1248 "@prefix ex: <http://example.org/> . ex:alice ex:age 30 . ex:bob ex:age 25 .",
1249 )
1250 .expect("valid input should return Some");
1251 assert_eq!(doc.triples.len(), 2);
1252 assert!(nn_eq(
1253 term_nn(doc.triples[0].subject.value()),
1254 &prefixed("ex", "alice")
1255 ));
1256 assert!(nn_eq(
1257 term_nn(doc.triples[1].subject.value()),
1258 &prefixed("ex", "bob")
1259 ));
1260 }
1261
1262 #[test]
1264 fn test_fast_matches_regular_parse() {
1265 let input = "@prefix ex: <http://example.org/> . ex:alice ex:name \"Alice\" ; ex:age 30 .";
1266 let fast_doc = parse_fast_doc(input).expect("valid input should return Some");
1267 let regular_doc = parse(input);
1268 assert_eq!(fast_doc.triples.len(), regular_doc.triples.len());
1269 assert_eq!(fast_doc.prefixes.len(), regular_doc.prefixes.len());
1270 }
1271
1272 #[test]
1274 fn test_fast_blank_node_property_list() {
1275 let doc = parse_fast_doc(
1276 "@prefix ex: <http://example.org/> . ex:alice ex:knows [ ex:name \"Bob\" ] .",
1277 )
1278 .expect("valid input should return Some");
1279 assert_eq!(doc.triples.len(), 1);
1280 }
1281
1282 #[test]
1284 fn test_fast_collection() {
1285 let doc = parse_fast_doc(
1286 "@prefix ex: <http://example.org/> . ex:alice ex:list ( ex:a ex:b ex:c ) .",
1287 )
1288 .expect("valid input should return Some");
1289 assert_eq!(doc.triples.len(), 1);
1290 }
1291
1292 #[test]
1298 fn test_recovery_badly_broken_document() {
1299 let broken = "\
1300@prefix foaf: <http://xmlns.com/foaf/0.1/> .
1301@prefix ex: <http://example.org/> .
1302
1303ex:alice a foaf:Person ;
1304 foaf:name \"Alice\" ;
1305 foaf:mbox <mailto:alice@example.org>
1306 foaf:homepage <http://example.org/alice> .
1307
1308ex:bob a foaf:Person ;
1309 foaf:name \"Bob\" GARBAGE_TOKEN more_garbage ;
1310 foaf:mbox <mailto:bob@example.org> .
1311
1312ex:carol a foaf:Person ;
1313 foaf:name \"Carol\" ;
1314 foaf:mbox <mailto:carol@example.org> .
1315";
1316 let doc = parse(broken);
1317 assert!(
1319 !doc.triples.is_empty(),
1320 "error recovery should extract some triples from broken document"
1321 );
1322 }
1323
1324 #[test]
1327 fn test_recovery_performance_bounded() {
1328 use std::time::Instant;
1329 let broken = "\
1330@prefix ex: <http://example.org/> .
1331ex:s1 ex:p1 INVALID GARBAGE LOTS OF BAD TOKENS .
1332ex:s2 ex:p2 ex:o2 .
1333ex:s3 ex:p3 ANOTHER MESS OF GARBAGE .
1334ex:s4 ex:p4 ex:o4 .
1335";
1336 let start = Instant::now();
1337 for _ in 0..10 {
1338 let _ = parse(broken);
1339 }
1340 let elapsed = start.elapsed();
1341 assert!(
1343 elapsed.as_millis() < 1000,
1344 "10 error recovery parses took {:?} — performance regression?",
1345 elapsed
1346 );
1347 }
1348}