Skip to main content

rdf_parsers/trig/
convert.rs

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
23/// Get text from a terminal node (a node that wraps a single token of the same kind).
24fn terminal_text(node: &Node) -> String {
25    node.text().to_string()
26}
27
28pub fn convert(root: &Node) -> Turtle {
29    // TrigDoc ::= (directive | block)*
30    // Children of the root are Directive and Block nodes directly.
31    let mut base = None;
32    let mut prefixes = Vec::new();
33    let mut triples = Vec::new();
34
35    for child_node in root.children() {
36        let kind = child_node.kind();
37        if kind == SyntaxKind::Directive {
38            if let Some(b) = child(&child_node, SyntaxKind::Base)
39                .or_else(|| child(&child_node, SyntaxKind::SparqlBase))
40            {
41                base = Some(Spanned(convert_base(&b), text_range(&b)));
42            } else if let Some(p) = child(&child_node, SyntaxKind::PrefixId)
43                .or_else(|| child(&child_node, SyntaxKind::SparqlPrefix))
44            {
45                prefixes.push(Spanned(convert_prefix(&p), text_range(&p)));
46            }
47        } else if kind == SyntaxKind::Block {
48            convert_block(&child_node, &mut triples);
49        }
50    }
51
52    Turtle::new(base, prefixes, triples)
53}
54
55// ── trig-specific conversion ─────────────────────────────────────────────────
56
57fn convert_block(block: &Node, triples: &mut Vec<Spanned<Triple>>) {
58    // block ::= graphBlock | triples2 | "GRAPH" labelOrSubject wrappedGraph | triplesOrGraph
59    // Check GraphToken first: the "GRAPH" variant also has a WrappedGraph child.
60    if let Some(gb) = child(block, SyntaxKind::GraphBlock) {
61        convert_graph_block(&gb, triples);
62    } else if let Some(t2) = child(block, SyntaxKind::Triples) {
63        let range = text_range(block);
64        triples.push(Spanned(convert_triples(&t2), range));
65    }
66}
67
68fn convert_graph_block(node: &Node, triples: &mut Vec<Spanned<Triple>>) {
69    // graphBlock ::= labelOrSubject? wrappedGraph
70    // The optional labelOrSubject is the named-graph IRI; absent means default graph.
71    let graph_term = child(node, SyntaxKind::GraphTerm).map(|los| {
72        let range = text_range(&los);
73        Spanned(convert_label_or_subject(&los), range)
74    });
75    if let Some(wg) = child(node, SyntaxKind::WrappedGraph) {
76        convert_wrapped_graph(&wg, graph_term, triples);
77    }
78}
79
80fn convert_wrapped_graph(
81    node: &Node,
82    graph: Option<Spanned<Term>>,
83    triples: &mut Vec<Spanned<Triple>>,
84) {
85    // wrappedGraph ::= '{' triplesBlock? '}'
86    if let Some(tb) = child(node, SyntaxKind::TriplesBlock) {
87        convert_triples_block(&tb, &graph, triples);
88    }
89}
90
91fn convert_triples_block(
92    node: &Node,
93    graph: &Option<Spanned<Term>>,
94    triples: &mut Vec<Spanned<Triple>>,
95) {
96    // triplesBlock ::= triples ('.' triplesBlock?)?
97    if let Some(t) = child(node, SyntaxKind::Triples) {
98        let range = text_range(&t);
99        let mut triple = convert_triples(&t);
100        triple.graph = graph.clone();
101        triples.push(Spanned(triple, range));
102    }
103    // Recurse into nested triplesBlock
104    if let Some(tb) = child(node, SyntaxKind::TriplesBlock) {
105        convert_triples_block(&tb, graph, triples);
106    }
107}
108
109fn convert_label_or_subject(node: &Node) -> Term {
110    // labelOrSubject ::= iri | BlankNode
111    if let Some(iri) = child(node, SyntaxKind::Iri) {
112        Term::NamedNode(convert_iri(&iri))
113    } else if let Some(bn) = child(node, SyntaxKind::BlankNode) {
114        Term::BlankNode(convert_blank_node(&bn))
115    } else {
116        Term::Invalid
117    }
118}
119
120// ── shared conversion (same rules as turtle, different SyntaxKind type) ──────
121
122fn convert_base(node: &Node) -> Base {
123    let range = text_range(node);
124    let nn = child(node, SyntaxKind::Iriref)
125        .map(|n| iri_from_iriref_node(&n))
126        .or_else(|| child(node, SyntaxKind::Iri).map(|n| convert_iri(&n)))
127        .unwrap_or(NamedNode::Invalid);
128
129    let nn_range = child(node, SyntaxKind::Iriref)
130        .or_else(|| child(node, SyntaxKind::Iri))
131        .map(|n| text_range(&n))
132        .unwrap_or(range.clone());
133
134    Base(range, Spanned(nn, nn_range))
135}
136
137fn convert_prefix(node: &Node) -> TurtlePrefix {
138    let range = text_range(node);
139
140    let prefix_text = child(node, SyntaxKind::PnameNs)
141        .map(|n| {
142            let text = terminal_text(&n);
143            let tr = text_range(&n);
144            Spanned(text.trim_end_matches(':').to_string(), tr)
145        })
146        .unwrap_or_else(|| Spanned(String::new(), range.clone()));
147
148    let value = child(node, SyntaxKind::Iriref)
149        .map(|n| {
150            let tr = text_range(&n);
151            Spanned(iri_from_iriref_node(&n), tr)
152        })
153        .or_else(|| {
154            child(node, SyntaxKind::Iri).map(|n| {
155                let r = text_range(&n);
156                Spanned(convert_iri(&n), r)
157            })
158        })
159        .unwrap_or_else(|| Spanned(NamedNode::Invalid, range.clone()));
160
161    TurtlePrefix {
162        span: range,
163        prefix: prefix_text,
164        value,
165    }
166}
167
168fn convert_triples(node: &Node) -> Triple {
169    // triples ::= subject predicateObjectList | blankNodePropertyList predicateObjectList?
170    let (subject, po_node) = if let Some(bpl) = child(node, SyntaxKind::BlankNodePropertyList) {
171        let range = text_range(&bpl);
172        let subject = convert_blank_node_property_list(&bpl);
173        (
174            Spanned(subject, range),
175            child(node, SyntaxKind::PredicateObjectList),
176        )
177    } else if let Some(subj) = child(node, SyntaxKind::Subject) {
178        let range = text_range(&subj);
179        (
180            Spanned(convert_subject(&subj), range),
181            child(node, SyntaxKind::PredicateObjectList),
182        )
183    } else {
184        (Spanned(Term::Invalid, text_range(node)), None)
185    };
186
187    let po = po_node
188        .map(|n| convert_predicate_object_list(&n))
189        .unwrap_or_default();
190
191    Triple {
192        subject,
193        po,
194        graph: None,
195    }
196}
197
198fn convert_predicate_object_list(node: &Node) -> Vec<Spanned<PO>> {
199    let verbs: Vec<_> = children(node, SyntaxKind::Verb).collect();
200    let object_lists: Vec<_> = children(node, SyntaxKind::ObjectList).collect();
201
202    verbs
203        .into_iter()
204        .zip(object_lists.into_iter())
205        .map(|(v, ol)| {
206            let range = text_range(&v).start..text_range(&ol).end;
207            let pred_range = text_range(&v);
208            let predicate = convert_verb(&v);
209            let objects = convert_object_list(&ol);
210            Spanned(
211                PO {
212                    predicate: Spanned(predicate, pred_range),
213                    object: objects,
214                },
215                range,
216            )
217        })
218        .collect()
219}
220
221fn convert_verb(node: &Node) -> Term {
222    if let Some(pred) = child(node, SyntaxKind::Predicate) {
223        if let Some(iri) = child(&pred, SyntaxKind::Iri) {
224            Term::NamedNode(convert_iri(&iri))
225        } else {
226            Term::Invalid
227        }
228    } else if let Some(alit) = child(node, SyntaxKind::Alit) {
229        Term::NamedNode(NamedNode::A(alit.text_range().start().into()))
230    } else {
231        Term::Invalid
232    }
233}
234
235fn convert_object_list(node: &Node) -> Vec<Spanned<Term>> {
236    children(node, SyntaxKind::Object)
237        .map(|o| {
238            let range = text_range(&o);
239            Spanned(convert_object(&o), range)
240        })
241        .collect()
242}
243
244fn convert_subject(node: &Node) -> Term {
245    // In trig: subject ::= iri | blank
246    // blank ::= BlankNode | collection
247    if let Some(iri) = child(node, SyntaxKind::Iri) {
248        Term::NamedNode(convert_iri(&iri))
249    } else {
250        convert_blank(node)
251    }
252}
253
254fn convert_blank(node: &Node) -> Term {
255    // blank ::= BlankNode | collection
256    if let Some(bn) = child(node, SyntaxKind::BlankNode) {
257        Term::BlankNode(convert_blank_node(&bn))
258    } else if let Some(coll) = child(node, SyntaxKind::Collection) {
259        Term::Collection(convert_collection(&coll))
260    } else {
261        Term::Invalid
262    }
263}
264
265fn convert_object(node: &Node) -> Term {
266    // In trig: object ::= iri | blank | blankNodePropertyList2 | blankNodePropertyList | literal
267    if let Some(iri) = child(node, SyntaxKind::Iri) {
268        Term::NamedNode(convert_iri(&iri))
269    } else if let Some(bpl) = child(node, SyntaxKind::BlankNodePropertyList2) {
270        convert_blank_node_property_list(&bpl)
271    } else if let Some(bpl) = child(node, SyntaxKind::BlankNodePropertyList) {
272        convert_blank_node_property_list(&bpl)
273    } else if let Some(lit) = child(node, SyntaxKind::Literal) {
274        Term::Literal(convert_literal(&lit))
275    } else {
276        convert_blank(&node)
277    }
278}
279
280fn convert_iri(node: &Node) -> NamedNode {
281    if let Some(iriref_node) = child(node, SyntaxKind::Iriref) {
282        iri_from_iriref_node(&iriref_node)
283    } else if let Some(pn) = child(node, SyntaxKind::PrefixedName) {
284        convert_prefixed_name(&pn)
285    } else {
286        NamedNode::Invalid
287    }
288}
289
290fn iri_from_iriref_node(node: &Node) -> NamedNode {
291    let text = terminal_text(node);
292    let offset: usize = node.text_range().start().into();
293    let inner = text.trim_start_matches('<').trim_end_matches('>');
294    NamedNode::Full(inner.to_string(), offset)
295}
296
297fn convert_prefixed_name(node: &Node) -> NamedNode {
298    if let Some(pname_ln) = child(node, SyntaxKind::PnameLn) {
299        let text = terminal_text(&pname_ln);
300        let offset: usize = pname_ln.text_range().start().into();
301        let (prefix, value) = text.split_once(':').unwrap_or((&text, ""));
302        NamedNode::Prefixed {
303            prefix: prefix.to_string(),
304            value: value.to_string(),
305            idx: offset,
306            computed: None,
307        }
308    } else if let Some(pname_ns) = child(node, SyntaxKind::PnameNs) {
309        let text = terminal_text(&pname_ns);
310        let offset: usize = pname_ns.text_range().start().into();
311        let prefix = text.trim_end_matches(':');
312        NamedNode::Prefixed {
313            prefix: prefix.to_string(),
314            value: String::new(),
315            idx: offset,
316            computed: None,
317        }
318    } else {
319        NamedNode::Invalid
320    }
321}
322
323fn convert_blank_node(node: &Node) -> BlankNode {
324    if let Some(label) = child(node, SyntaxKind::BlankNodeLabel) {
325        let text = terminal_text(&label);
326        let offset: usize = label.text_range().start().into();
327        let name = text.strip_prefix("_:").unwrap_or(&text);
328        BlankNode::Named(name.to_string(), offset)
329    } else {
330        let offset: usize = node.text_range().start().into();
331        BlankNode::Unnamed(Vec::new(), offset, offset)
332    }
333}
334
335fn convert_blank_node_property_list(node: &Node) -> Term {
336    let offset: usize = node.text_range().start().into();
337    let end: usize = node.text_range().end().into();
338    let po = child(node, SyntaxKind::PredicateObjectList)
339        .map(|n| convert_predicate_object_list(&n))
340        .unwrap_or_default();
341    Term::BlankNode(BlankNode::Unnamed(po, offset, end))
342}
343
344fn convert_collection(node: &Node) -> Vec<Spanned<Term>> {
345    children(node, SyntaxKind::Object)
346        .map(|o| {
347            let range = text_range(&o);
348            Spanned(convert_object(&o), range)
349        })
350        .collect()
351}
352
353fn convert_literal(node: &Node) -> Literal {
354    if let Some(rdf) = child(node, SyntaxKind::Rdfliteral) {
355        Literal::RDF(convert_rdf_literal(&rdf))
356    } else if let Some(num) = child(node, SyntaxKind::NumericLiteral) {
357        Literal::Numeric(num.text().to_string().trim().to_string())
358    } else if let Some(b) = child(node, SyntaxKind::BooleanLiteral) {
359        Literal::Boolean(b.text().to_string().trim() == "true")
360    } else {
361        Literal::Numeric(String::new())
362    }
363}
364
365fn convert_rdf_literal(node: &Node) -> RDFLiteral {
366    let offset: usize = node.text_range().start().into();
367
368    let (value, quote_style, len) = if let Some(str_node) = child(node, SyntaxKind::MyString) {
369        let string_node = str_node.children().next();
370        if let Some(sn) = string_node {
371            let text = terminal_text(&sn);
372            let (inner, style) = strip_string_delimiters(&text);
373            (inner.to_string(), style, 1)
374        } else {
375            (String::new(), StringStyle::Double, 0)
376        }
377    } else {
378        (String::new(), StringStyle::Double, 0)
379    };
380
381    let lang = child(node, SyntaxKind::Langtag).map(|n| {
382        let span = text_range(&n);
383        let text = terminal_text(&n);
384        let value = text.strip_prefix('@').unwrap_or(&text).to_string();
385        Spanned(value, span)
386    });
387
388    let ty = child(node, SyntaxKind::Iri).map(|n| Spanned(convert_iri(&n), text_range(&n)));
389
390    RDFLiteral {
391        value,
392        quote_style,
393        lang,
394        ty,
395        idx: offset,
396        len,
397    }
398}
399
400fn strip_string_delimiters(text: &str) -> (&str, StringStyle) {
401    if let Some(inner) = text
402        .strip_prefix("\"\"\"")
403        .and_then(|s| s.strip_suffix("\"\"\""))
404    {
405        (inner, StringStyle::DoubleLong)
406    } else if let Some(inner) = text.strip_prefix("'''").and_then(|s| s.strip_suffix("'''")) {
407        (inner, StringStyle::SingleLong)
408    } else if let Some(inner) = text.strip_prefix('"').and_then(|s| s.strip_suffix('"')) {
409        (inner, StringStyle::Double)
410    } else if let Some(inner) = text.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
411        (inner, StringStyle::Single)
412    } else {
413        (text, StringStyle::Double)
414    }
415}
416
417#[cfg(test)]
418mod tests {
419    use super::*;
420    use crate::{
421        IncrementalBias, PrevParseInfo, TokenTrait, parse as crate_parse, parse_incremental,
422        trig::parser as lang,
423    };
424
425    fn parse(input: &str) -> Turtle {
426        let (result, _) = crate_parse(lang::Rule::new(lang::SyntaxKind::TrigDoc), input);
427        let root = result.syntax::<lang::Lang>();
428        println!("{:#?}", root);
429        convert(&root)
430    }
431
432    fn prev_info(text: &str) -> PrevParseInfo {
433        let (_, tokens) = crate_parse(lang::Rule::new(lang::SyntaxKind::TrigDoc), text);
434        let mut depth: i32 = 0;
435        PrevParseInfo {
436            tokens: tokens
437                .iter()
438                .map(|t| {
439                    let d = depth.clamp(0, 255) as u8;
440                    depth += t.kind.bracket_delta() as i32;
441                    t.to_prev_token(d)
442                })
443                .collect(),
444            had_errors: false,
445        }
446    }
447
448    fn parse_incr(before: &str, after: &str) -> Turtle {
449        let prev = prev_info(before);
450        let bias = IncrementalBias::default();
451        let (result, _) = parse_incremental(
452            lang::Rule::new(lang::SyntaxKind::TrigDoc),
453            after,
454            Some(&prev),
455            bias,
456        );
457        let root = result.syntax::<lang::Lang>();
458        convert(&root)
459    }
460
461    // ── helpers ──────────────────────────────────────────────────────────────
462
463    fn prefixed(prefix: &str, value: &str) -> NamedNode {
464        NamedNode::Prefixed {
465            prefix: prefix.to_string(),
466            value: value.to_string(),
467            idx: 0,
468            computed: None,
469        }
470    }
471
472    fn nn_eq(a: &NamedNode, b: &NamedNode) -> bool {
473        match (a, b) {
474            (NamedNode::Full(s1, _), NamedNode::Full(s2, _)) => s1 == s2,
475            (
476                NamedNode::Prefixed {
477                    prefix: p1,
478                    value: v1,
479                    ..
480                },
481                NamedNode::Prefixed {
482                    prefix: p2,
483                    value: v2,
484                    ..
485                },
486            ) => p1 == p2 && v1 == v2,
487            (NamedNode::A(_), NamedNode::A(_)) => true,
488            (NamedNode::Invalid, NamedNode::Invalid) => true,
489            _ => false,
490        }
491    }
492
493    fn term_nn(t: &Term) -> &NamedNode {
494        match t {
495            Term::NamedNode(nn) => nn,
496            other => panic!("expected NamedNode, got {:?}", other),
497        }
498    }
499
500    fn term_lit(t: &Term) -> &Literal {
501        match t {
502            Term::Literal(l) => l,
503            other => panic!("expected Literal, got {:?}", other),
504        }
505    }
506
507    fn term_bn(t: &Term) -> &BlankNode {
508        match t {
509            Term::BlankNode(bn) => bn,
510            other => panic!("expected BlankNode, got {:?}", other),
511        }
512    }
513
514    // ── directives ───────────────────────────────────────────────────────────
515
516    #[test]
517    fn test_prefix_directive() {
518        let doc = parse("@prefix ex: <http://example.org/> .");
519        assert_eq!(doc.prefixes.len(), 1);
520        let p = doc.prefixes[0].value();
521        assert_eq!(p.prefix.value(), "ex");
522        assert!(nn_eq(
523            p.value.value(),
524            &NamedNode::Full("http://example.org/".to_string(), 0)
525        ));
526    }
527
528    #[test]
529    fn test_base_directive() {
530        let doc = parse("@base <http://example.org/> .");
531        let base = doc.base.as_ref().expect("base should be present");
532        assert!(nn_eq(
533            base.value().1.value(),
534            &NamedNode::Full("http://example.org/".to_string(), 0)
535        ));
536    }
537
538    // ── default graph triples (triplesOrGraph with predicateObjectList) ──────
539
540    #[test]
541    fn test_simple_triple() {
542        let doc = parse("@prefix ex: <http://example.org/> . ex:alice ex:knows ex:bob .");
543        assert_eq!(doc.triples.len(), 1);
544        let t = doc.triples[0].value();
545        assert!(nn_eq(term_nn(t.subject.value()), &prefixed("ex", "alice")));
546        assert!(nn_eq(
547            term_nn(t.po[0].object[0].value()),
548            &prefixed("ex", "bob")
549        ));
550        assert!(t.graph.is_none());
551    }
552
553    #[test]
554    fn test_full_iri_triple() {
555        let doc = parse(
556            "<http://example.org/alice> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://xmlns.com/foaf/0.1/Person> .",
557        );
558        assert_eq!(doc.triples.len(), 1);
559        let t = doc.triples[0].value();
560        assert!(nn_eq(
561            term_nn(t.subject.value()),
562            &NamedNode::Full("http://example.org/alice".to_string(), 0)
563        ));
564    }
565
566    #[test]
567    fn test_a_shorthand() {
568        let doc = parse(
569            "@prefix foaf: <http://xmlns.com/foaf/0.1/> . <http://example.org/alice> a foaf:Person .",
570        );
571        let t = doc.triples[0].value();
572        assert!(matches!(
573            t.po[0].predicate.value(),
574            Term::NamedNode(NamedNode::A(_))
575        ));
576    }
577
578    #[test]
579    fn test_multiple_po_pairs() {
580        let doc =
581            parse("@prefix ex: <http://example.org/> . ex:alice ex:name \"Alice\" ; ex:age 30 .");
582        let t = doc.triples[0].value();
583        assert_eq!(t.po.len(), 2);
584    }
585
586    #[test]
587    fn test_multiple_objects() {
588        let doc =
589            parse("@prefix ex: <http://example.org/> . ex:alice ex:knows ex:bob , ex:carol .");
590        let t = doc.triples[0].value();
591        assert_eq!(t.po[0].object.len(), 2);
592    }
593
594    #[test]
595    fn test_string_literal() {
596        let doc = parse("@prefix ex: <http://example.org/> . ex:alice ex:name \"Alice\" .");
597        let t = doc.triples[0].value();
598        let lit = term_lit(t.po[0].object[0].value());
599        assert_eq!(lit.plain_string(), "Alice");
600    }
601
602    #[test]
603    fn test_string_literal_with_lang() {
604        let doc = parse("@prefix ex: <http://example.org/> . ex:alice ex:name \"Alice\"@en .");
605        let t = doc.triples[0].value();
606        match term_lit(t.po[0].object[0].value()) {
607            Literal::RDF(r) => {
608                assert_eq!(r.value, "Alice");
609                assert_eq!(r.lang.as_ref().map(|l| l.as_str()), Some("en"));
610            }
611            other => panic!("expected RDF literal, got {:?}", other),
612        }
613    }
614
615    #[test]
616    fn test_numeric_literal() {
617        let doc = parse("@prefix ex: <http://example.org/> . ex:alice ex:age 30 .");
618        let t = doc.triples[0].value();
619        match term_lit(t.po[0].object[0].value()) {
620            Literal::Numeric(n) => assert_eq!(n, "30"),
621            other => panic!("expected Numeric literal, got {:?}", other),
622        }
623    }
624
625    #[test]
626    fn test_boolean_literal() {
627        let doc = parse("@prefix ex: <http://example.org/> . ex:alice ex:active true .");
628        println!("{:?}", doc);
629        let t = doc.triples[0].value();
630        assert_eq!(*term_lit(t.po[0].object[0].value()), Literal::Boolean(true));
631    }
632
633    #[test]
634    fn test_named_blank_node() {
635        let doc = parse("@prefix ex: <http://example.org/> . _:b0 ex:knows ex:alice .");
636        let t = doc.triples[0].value();
637        match term_bn(t.subject.value()) {
638            BlankNode::Named(name, _) => assert_eq!(name, "b0"),
639            other => panic!("expected Named blank node, got {:?}", other),
640        }
641    }
642
643    #[test]
644    fn test_anon_blank_node_property_list() {
645        let doc =
646            parse("@prefix ex: <http://example.org/> . ex:alice ex:knows [ ex:name \"Bob\" ] .");
647        let t = doc.triples[0].value();
648        match term_bn(t.po[0].object[0].value()) {
649            BlankNode::Unnamed(pos, _, _) => {
650                assert_eq!(pos.len(), 1);
651                assert!(nn_eq(
652                    term_nn(pos[0].predicate.value()),
653                    &prefixed("ex", "name")
654                ));
655            }
656            other => panic!("expected Unnamed blank node, got {:?}", other),
657        }
658    }
659
660    #[test]
661    fn test_collection() {
662        let doc =
663            parse("@prefix ex: <http://example.org/> . ex:alice ex:list ( ex:a ex:b ex:c ) .");
664        let t = doc.triples[0].value();
665        match t.po[0].object[0].value() {
666            Term::Collection(items) => {
667                assert_eq!(items.len(), 3);
668                assert!(nn_eq(term_nn(items[0].value()), &prefixed("ex", "a")));
669                assert!(nn_eq(term_nn(items[1].value()), &prefixed("ex", "b")));
670                assert!(nn_eq(term_nn(items[2].value()), &prefixed("ex", "c")));
671            }
672            other => panic!("expected Collection, got {:?}", other),
673        }
674    }
675
676    // ── default graph wrapped in braces ──────────────────────────────────────
677
678    #[test]
679    fn test_default_graph_wrapped() {
680        let doc = parse("@prefix ex: <http://example.org/> . { ex:alice ex:knows ex:bob . }");
681        assert_eq!(doc.triples.len(), 1);
682        let t = doc.triples[0].value();
683        assert!(nn_eq(term_nn(t.subject.value()), &prefixed("ex", "alice")));
684        assert!(t.graph.is_none());
685    }
686
687    #[test]
688    fn test_default_graph_multiple_triples() {
689        let doc = parse(
690            "@prefix ex: <http://example.org/> . { ex:alice ex:knows ex:bob . ex:bob ex:knows ex:carol . }",
691        );
692        assert_eq!(doc.triples.len(), 2);
693        assert!(doc.triples[0].value().graph.is_none());
694        assert!(doc.triples[1].value().graph.is_none());
695    }
696
697    // ── named graph with GRAPH keyword ───────────────────────────────────────
698
699    #[test]
700    fn test_named_graph_with_keyword() {
701        let doc =
702            parse("@prefix ex: <http://example.org/> . GRAPH ex:g1 { ex:alice ex:knows ex:bob . }");
703        assert_eq!(doc.triples.len(), 1);
704        let t = doc.triples[0].value();
705        assert!(nn_eq(term_nn(t.subject.value()), &prefixed("ex", "alice")));
706        let graph = t.graph.as_ref().expect("should have graph");
707        assert!(nn_eq(term_nn(graph.value()), &prefixed("ex", "g1")));
708    }
709
710    #[test]
711    fn test_named_graph_multiple_triples() {
712        let doc = parse(
713            "@prefix ex: <http://example.org/> . GRAPH ex:g1 { ex:alice ex:knows ex:bob . ex:bob ex:knows ex:carol . }",
714        );
715        assert_eq!(doc.triples.len(), 2);
716        for t in &doc.triples {
717            let graph = t.value().graph.as_ref().expect("should have graph");
718            assert!(nn_eq(term_nn(graph.value()), &prefixed("ex", "g1")));
719        }
720    }
721
722    #[test]
723    fn test_named_graph_full_iri() {
724        let doc = parse(
725            "GRAPH <http://example.org/g1> { <http://example.org/alice> <http://example.org/knows> <http://example.org/bob> . }",
726        );
727        assert_eq!(doc.triples.len(), 1);
728        let t = doc.triples[0].value();
729        let graph = t.graph.as_ref().expect("should have graph");
730        assert!(nn_eq(
731            term_nn(graph.value()),
732            &NamedNode::Full("http://example.org/g1".to_string(), 0)
733        ));
734    }
735
736    // ── named graph via triplesOrGraph (iri followed by wrappedGraph) ────────
737
738    #[test]
739    fn test_named_graph_without_keyword() {
740        let doc = parse("@prefix ex: <http://example.org/> . ex:g1 { ex:alice ex:knows ex:bob . }");
741        assert_eq!(doc.triples.len(), 1);
742        let t = doc.triples[0].value();
743        assert!(nn_eq(term_nn(t.subject.value()), &prefixed("ex", "alice")));
744        let graph = t.graph.as_ref().expect("should have graph");
745        assert!(nn_eq(term_nn(graph.value()), &prefixed("ex", "g1")));
746    }
747
748    // ── triples2 (blankNodePropertyList as subject at block level) ───────────
749
750    #[test]
751    fn test_triples2_blank_node_property_list() {
752        let doc = parse("@prefix ex: <http://example.org/> . [ ex:name \"Alice\" ] ex:age 30 .");
753        assert_eq!(doc.triples.len(), 1);
754        let t = doc.triples[0].value();
755        assert!(matches!(
756            t.subject.value(),
757            Term::BlankNode(BlankNode::Unnamed(_, _, _))
758        ));
759        assert_eq!(t.po.len(), 1);
760    }
761
762    // ── sparql-style directives ──────────────────────────────────────────────
763
764    #[test]
765    fn test_sparql_prefix_directive() {
766        let doc = parse("PREFIX ex: <http://example.org/> ex:alice ex:knows ex:bob .");
767        assert_eq!(doc.prefixes.len(), 1);
768        assert_eq!(doc.prefixes[0].value().prefix.value(), "ex");
769    }
770
771    #[test]
772    fn test_sparql_base_directive() {
773        let doc = parse("BASE <http://example.org/> <alice> <knows> <bob> .");
774        let base = doc.base.as_ref().expect("base should be present");
775        assert!(nn_eq(
776            base.value().1.value(),
777            &NamedNode::Full("http://example.org/".to_string(), 0)
778        ));
779    }
780
781    // ── string literal with datatype ─────────────────────────────────────────
782
783    #[test]
784    fn test_string_literal_with_datatype() {
785        let doc = parse(
786            "@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . @prefix ex: <http://example.org/> . ex:alice ex:age \"30\"^^xsd:integer .",
787        );
788        let t = doc.triples[0].value();
789        match term_lit(t.po[0].object[0].value()) {
790            Literal::RDF(r) => {
791                assert_eq!(r.value, "30");
792                assert!(r.ty.is_some());
793                assert!(nn_eq(r.ty.as_ref().unwrap(), &prefixed("xsd", "integer")));
794            }
795            other => panic!("expected RDF literal, got {:?}", other),
796        }
797    }
798
799    // ── multiple triples (no graph) ──────────────────────────────────────────
800
801    #[test]
802    fn test_multiple_triples() {
803        let doc =
804            parse("@prefix ex: <http://example.org/> . ex:alice ex:age 30 . ex:bob ex:age 25 .");
805        assert_eq!(doc.triples.len(), 2);
806        assert!(nn_eq(
807            term_nn(doc.triples[0].value().subject.value()),
808            &prefixed("ex", "alice")
809        ));
810        assert!(nn_eq(
811            term_nn(doc.triples[1].value().subject.value()),
812            &prefixed("ex", "bob")
813        ));
814    }
815
816    // ── fault-tolerant parsing ────────────────────────────────────────────────
817
818    fn parse_raw(input: &str) -> crate::Parse {
819        let (result, _) = crate_parse(lang::Rule::new(lang::SyntaxKind::TrigDoc), input);
820        result
821    }
822
823    #[test]
824    fn test_valid_input_has_no_errors() {
825        let p = parse_raw("@prefix ex: <http://example.org/> . ex:alice ex:age 30 .");
826        assert_eq!(p.errors.len(), 0, "valid input should produce no errors");
827    }
828
829    #[test]
830    fn test_missing_trailing_dot_reports_error() {
831        let p = parse_raw("@prefix ex: <http://example.org/> . ex:alice ex:age 30");
832        assert!(
833            p.errors.len() > 0,
834            "missing trailing dot should produce an error"
835        );
836        assert!(
837            p.errors.iter().any(|e| e.contains("Stop")),
838            "expected an error mentioning Stop, got: {:?}",
839            p.errors.iter().collect::<Vec<_>>()
840        );
841    }
842
843    #[test]
844    fn test_missing_prefix_iri_reports_error() {
845        let p = parse_raw("@prefix ex: .");
846        assert!(
847            p.errors.len() > 0,
848            "missing prefix IRI should produce an error"
849        );
850    }
851
852    /// `"  <a> <b> <c> }"` — two leading spaces, then a triple, missing the
853    /// opening `{`.  The parser inserts a synthetic `{` via error recovery.
854    ///
855    /// This test verifies three things:
856    ///   1. Exactly one parse error is reported.
857    ///   2. The error node sits strictly before the `}` in the CST.
858    ///   3. `effective_error_span` widens the zero-width error point to include
859    ///      the two leading whitespace bytes, returning a span of width 2.
860    #[test]
861    fn test_missing_open_curly_error_span_precedes_close_curly() {
862        let input = "  <a <a> <b> <c> }";
863
864        let p = parse_raw(input);
865        // Two errors: the lexer can't tokenize `<a` (InvalidToken), and the
866        // grammar expects a `{` (Expected(CurlyOpen)).
867        assert!(
868            p.errors.len() >= 1,
869            "expected at least one error, got: {:?}",
870            p.errors.iter().collect::<Vec<_>>()
871        );
872
873        let root = p.syntax::<lang::Lang>();
874
875        let error_node = root
876            .descendants_with_tokens()
877            .filter_map(|e| e.into_node())
878            .find(|n| n.kind() == lang::SyntaxKind::Error)
879            .expect("expected an Error node in the CST");
880
881        let close_curly_offset: usize = root
882            .descendants_with_tokens()
883            .filter(|t| t.kind() == lang::SyntaxKind::CurlyClose)
884            .map(|t| usize::from(t.text_range().start()))
885            .next()
886            .expect("expected a CurlyClose token in the CST");
887
888        assert!(
889            usize::from(error_node.text_range().start()) < close_curly_offset,
890            "error should precede `}}` in the CST"
891        );
892
893        let span = crate::effective_error_span::<lang::Lang>(&error_node);
894        assert_eq!(
895            span.len(),
896            5,
897            "effective span should cover the 2 leading whitespace bytes, got {:?}",
898            span
899        );
900    }
901
902    // ── mixed content ────────────────────────────────────────────────────────
903
904    #[test]
905    fn test_mixed_default_and_named() {
906        let doc = parse(
907            "@prefix ex: <http://example.org/> .\n\
908             ex:s1 ex:p1 ex:o1 .\n\
909             GRAPH ex:g1 { ex:s2 ex:p2 ex:o2 . }",
910        );
911        assert_eq!(doc.triples.len(), 2);
912        assert!(doc.triples[0].value().graph.is_none());
913        assert!(doc.triples[1].value().graph.is_some());
914    }
915
916    #[test]
917    fn test_empty_wrapped_graph() {
918        let doc = parse("@prefix ex: <http://example.org/> . GRAPH ex:g1 { }");
919        assert_eq!(doc.triples.len(), 0);
920    }
921
922    #[test]
923    fn test_empty_default_graph() {
924        let doc = parse("{ }");
925        assert_eq!(doc.triples.len(), 0);
926    }
927
928    #[test]
929    fn test_named_graph_blank_node_graph_name() {
930        let doc =
931            parse("@prefix ex: <http://example.org/> . GRAPH _:g1 { ex:alice ex:knows ex:bob . }");
932        assert_eq!(doc.triples.len(), 1);
933        let graph = doc.triples[0]
934            .value()
935            .graph
936            .as_ref()
937            .expect("should have graph");
938        match graph.value() {
939            Term::BlankNode(BlankNode::Named(name, _)) => assert_eq!(name, "g1"),
940            other => panic!("expected Named blank node graph, got {:?}", other),
941        }
942    }
943
944    // ── incremental: remove graph IRI ────────────────────────────────────────
945
946    /// Going from `{ <b> <c> <d> }` to `<b> <c> <d> .` (removing the wrapping
947    /// curly braces), the incremental parser should produce one triple in the
948    /// default graph with no errors.  The inner tokens shift from bracket depth
949    /// 1 to depth 0; the depth-delta mechanism pays the adoption cost once and
950    /// gives subsequent tokens a free pass.
951    #[test]
952    fn test_incremental_remove_curly_braces() {
953        let before = "{ <b> <c> <d> }";
954        let after = "<b> <c> <d> }";
955
956        let (result, _) = parse_incremental(
957            lang::Rule::new(lang::SyntaxKind::TrigDoc),
958            after,
959            Some(&prev_info(before)),
960            IncrementalBias::default(),
961        );
962        assert!(
963            result.errors.len() == 1,
964            "expected one parse errors, got: {:?}",
965            result.errors.iter().collect::<Vec<_>>()
966        );
967
968        let root = result.syntax::<lang::Lang>();
969        println!("{:#?}", root);
970        let doc = convert(&root);
971
972        assert_eq!(doc.triples.len(), 1, "should produce exactly one triple");
973
974        let t = doc.triples[0].value();
975        assert!(t.graph.is_none(), "triple should be in the default graph");
976
977        match t.subject.value() {
978            Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "b"),
979            other => panic!("expected <b> as subject, got {:?}", other),
980        }
981        match t.po[0].value().predicate.value() {
982            Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "c"),
983            other => panic!("expected <c> as predicate, got {:?}", other),
984        }
985        match t.po[0].value().object[0].value() {
986            Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "d"),
987            other => panic!("expected <d> as object, got {:?}", other),
988        }
989    }
990
991    /// Going from `<a> { <b> <c> <d> }` to `{ <b> <c> <d> }` (dropping the
992    /// named-graph IRI), the incremental parser should produce one triple in
993    /// the default graph with subject <b>, predicate <c>, object <d>.
994    #[test]
995    fn test_incremental_remove_graph_iri() {
996        let before = "<a> { <b> <c> <d> }";
997        let after = "{ <b> <c> <d> }";
998
999        let doc = parse_incr(before, after);
1000
1001        assert_eq!(doc.triples.len(), 1, "should produce exactly one triple");
1002
1003        let t = doc.triples[0].value();
1004        assert!(t.graph.is_none(), "triple should be in the default graph");
1005
1006        match t.subject.value() {
1007            Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "b"),
1008            other => panic!("expected <b> as subject, got {:?}", other),
1009        }
1010        match t.po[0].value().predicate.value() {
1011            Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "c"),
1012            other => panic!("expected <c> as predicate, got {:?}", other),
1013        }
1014        match t.po[0].value().object[0].value() {
1015            Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "d"),
1016            other => panic!("expected <d> as object, got {:?}", other),
1017        }
1018    }
1019
1020    /// Add a named-graph IRI: `{ <b> <c> <d> }` → `<g> { <b> <c> <d> }`
1021    /// The tokens inside the graph shift context; the A* must find the named-graph
1022    /// parse within budget, exercising graph_token and curly-brace weights.
1023    #[test]
1024    fn test_incremental_add_graph_iri() {
1025        let doc = parse_incr("{ <b> <c> <d> }", "<g> { <b> <c> <d> }");
1026
1027        assert_eq!(doc.triples.len(), 1, "should produce exactly one triple");
1028
1029        let t = doc.triples[0].value();
1030        match t.graph.as_ref().map(|g| g.value()) {
1031            Some(Term::NamedNode(NamedNode::Full(iri, _))) => assert_eq!(iri, "g"),
1032            other => panic!("expected <g> as graph name, got {:?}", other),
1033        }
1034        match t.subject.value() {
1035            Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "b"),
1036            other => panic!("expected <b> as subject, got {:?}", other),
1037        }
1038    }
1039
1040    /// Change the object inside a default-graph block:
1041    /// `{ <b> <c> <d> }` → `{ <b> <c> <e> }`.  Only one token changes; the
1042    /// curly-brace tokens are unchanged, exercising depth-delta stability.
1043    #[test]
1044    fn test_incremental_change_object_inside_graph() {
1045        let doc = parse_incr("{ <b> <c> <d> }", "{ <b> <c> <e> }");
1046
1047        assert_eq!(doc.triples.len(), 1, "should produce exactly one triple");
1048
1049        let t = doc.triples[0].value();
1050        assert!(t.graph.is_none(), "triple should be in the default graph");
1051        match t.po[0].value().object[0].value() {
1052            Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "e"),
1053            other => panic!("expected <e> as object, got {:?}", other),
1054        }
1055    }
1056
1057    /// Add a triple inside a named graph:
1058    /// `<g> { <b> <c> <d> }` → `<g> { <b> <c> <d> . <e> <f> <h> }`
1059    /// The parser must handle the depth shift for the new tokens while keeping
1060    /// both triples in the named graph.
1061    #[test]
1062    fn test_incremental_add_triple_inside_named_graph() {
1063        let doc = parse_incr("<g> { <b> <c> <d> }", "<g> { <b> <c> <d> . <e> <f> <h> }");
1064
1065        assert_eq!(doc.triples.len(), 2, "should produce two triples");
1066
1067        for t in doc.triples.iter() {
1068            match t.value().graph.as_ref().map(|g| g.value()) {
1069                Some(Term::NamedNode(NamedNode::Full(iri, _))) => assert_eq!(iri, "g"),
1070                other => panic!("expected <g> as graph for both triples, got {:?}", other),
1071            }
1072        }
1073        match doc.triples[0].value().subject.value() {
1074            Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "b"),
1075            other => panic!("expected <b> as first subject, got {:?}", other),
1076        }
1077        match doc.triples[1].value().subject.value() {
1078            Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "e"),
1079            other => panic!("expected <e> as second subject, got {:?}", other),
1080        }
1081    }
1082
1083    /// Remove a triple from a named graph:
1084    /// `<g> { <b> <c> <d> . <e> <f> <h> }` → `<g> { <b> <c> <d> }`
1085    /// After removal the parser should produce one triple still in graph <g>.
1086    #[test]
1087    fn test_incremental_remove_triple_inside_named_graph() {
1088        let doc = parse_incr("<g> { <b> <c> <d> . <e> <f> <h> }", "<g> { <b> <c> <d> }");
1089
1090        assert_eq!(doc.triples.len(), 1, "should produce exactly one triple");
1091
1092        let t = doc.triples[0].value();
1093        match t.graph.as_ref().map(|g| g.value()) {
1094            Some(Term::NamedNode(NamedNode::Full(iri, _))) => assert_eq!(iri, "g"),
1095            other => panic!("expected <g> as graph, got {:?}", other),
1096        }
1097        match t.subject.value() {
1098            Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "b"),
1099            other => panic!("expected <b> as subject, got {:?}", other),
1100        }
1101    }
1102}