Skip to main content

rdf_parsers/n3/
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
23fn terminal_text(node: &Node) -> String {
24    node.text().to_string()
25}
26
27pub fn convert(root: &Node) -> Turtle {
28    let mut base = None;
29    let mut prefixes = Vec::new();
30    let mut triples = Vec::new();
31
32    // Walk the entire tree to collect triples at all nesting levels (including
33    // inside formulas), and collect directives from top-level statements.
34    for child_node in root.children() {
35        let kind = child_node.kind();
36        match kind {
37            SyntaxKind::N3Statement => {
38                if let Some(dir) = child(&child_node, SyntaxKind::N3Directive) {
39                    if let Some(b) = child(&dir, SyntaxKind::Base) {
40                        base = Some(Spanned(convert_base(&b), text_range(&b)));
41                    } else if let Some(p) = child(&dir, SyntaxKind::PrefixId) {
42                        prefixes.push(Spanned(convert_prefix(&p), text_range(&p)));
43                    }
44                } else if let Some(t) = child(&child_node, SyntaxKind::Triples) {
45                    // Collect triples nested inside formulas first
46                    collect_nested_triples(&t, &mut triples);
47                    // Convert the top-level triple itself, but skip if subject
48                    // is invalid (e.g. N3 formula — inner triples already collected)
49                    let range = text_range(&child_node);
50                    let triple = convert_triples(&t);
51                    if !matches!(triple.subject.value(), Term::Invalid) {
52                        triples.push(Spanned(triple, range));
53                    }
54                }
55            }
56            SyntaxKind::SparqlDirective => {
57                if let Some(b) = child(&child_node, SyntaxKind::SparqlBase) {
58                    base = Some(Spanned(convert_base(&b), text_range(&b)));
59                } else if let Some(p) = child(&child_node, SyntaxKind::SparqlPrefix) {
60                    prefixes.push(Spanned(convert_prefix(&p), text_range(&p)));
61                }
62            }
63            _ => {}
64        }
65    }
66
67    Turtle::new(base, prefixes, triples)
68}
69
70/// Recursively find all Formula descendants and collect Triples within them.
71fn collect_nested_triples(node: &Node, triples: &mut Vec<Spanned<Triple>>) {
72    for c in node.children() {
73        let kind = c.kind();
74        if kind == SyntaxKind::Formula || kind == SyntaxKind::FormulaContent {
75            collect_all_triples(&c, triples);
76        } else {
77            collect_nested_triples(&c, triples);
78        }
79    }
80}
81
82/// Recursively collect ALL Triples nodes from a subtree.
83fn collect_all_triples(node: &Node, triples: &mut Vec<Spanned<Triple>>) {
84    for c in node.children() {
85        if c.kind() == SyntaxKind::Triples {
86            let range = text_range(&c);
87            triples.push(Spanned(convert_triples(&c), range));
88        }
89        // Always recurse to find deeper triples (e.g., formulas within formulas)
90        collect_all_triples(&c, triples);
91    }
92}
93
94// ── directive conversion ─────────────────────────────────────────────────────
95
96fn convert_base(node: &Node) -> Base {
97    let range = text_range(node);
98    let nn = child(node, SyntaxKind::Iriref)
99        .map(|n| iri_from_iriref_node(&n))
100        .or_else(|| child(node, SyntaxKind::Iri).map(|n| convert_iri(&n)))
101        .unwrap_or(NamedNode::Invalid);
102
103    let nn_range = child(node, SyntaxKind::Iriref)
104        .or_else(|| child(node, SyntaxKind::Iri))
105        .map(|n| text_range(&n))
106        .unwrap_or(range.clone());
107
108    Base(range, Spanned(nn, nn_range))
109}
110
111fn convert_prefix(node: &Node) -> TurtlePrefix {
112    let range = text_range(node);
113
114    let prefix_text = child(node, SyntaxKind::PnameNs)
115        .map(|n| {
116            let text = terminal_text(&n);
117            let tr = text_range(&n);
118            Spanned(text.trim_end_matches(':').to_string(), tr)
119        })
120        .unwrap_or_else(|| Spanned(String::new(), range.clone()));
121
122    let value = child(node, SyntaxKind::Iriref)
123        .map(|n| {
124            let tr = text_range(&n);
125            Spanned(iri_from_iriref_node(&n), tr)
126        })
127        .or_else(|| {
128            child(node, SyntaxKind::Iri).map(|n| {
129                let r = text_range(&n);
130                Spanned(convert_iri(&n), r)
131            })
132        })
133        .unwrap_or_else(|| Spanned(NamedNode::Invalid, range.clone()));
134
135    TurtlePrefix {
136        span: range,
137        prefix: prefix_text,
138        value,
139    }
140}
141
142// ── triples conversion ───────────────────────────────────────────────────────
143
144fn convert_triples(node: &Node) -> Triple {
145    // triples ::= subject predicateObjectList?
146    let subject = if let Some(subj) = child(node, SyntaxKind::Subject) {
147        let range = text_range(&subj);
148        Spanned(convert_subject(&subj), range)
149    } else {
150        Spanned(Term::Invalid, text_range(node))
151    };
152
153    let po = child(node, SyntaxKind::PredicateObjectList)
154        .map(|n| convert_predicate_object_list(&n))
155        .unwrap_or_default();
156
157    Triple {
158        subject,
159        po,
160        graph: None,
161    }
162}
163
164fn convert_predicate_object_list(node: &Node) -> Vec<Spanned<PO>> {
165    let verbs: Vec<_> = children(node, SyntaxKind::Verb).collect();
166    let object_lists: Vec<_> = children(node, SyntaxKind::ObjectList).collect();
167
168    verbs
169        .into_iter()
170        .zip(object_lists.into_iter())
171        .map(|(v, ol)| {
172            let range = text_range(&v).start..text_range(&ol).end;
173            let pred_range = text_range(&v);
174            let predicate = convert_verb(&v);
175            let objects = convert_object_list(&ol);
176            Spanned(
177                PO {
178                    predicate: Spanned(predicate, pred_range),
179                    object: objects,
180                },
181                range,
182            )
183        })
184        .collect()
185}
186
187fn convert_verb(node: &Node) -> Term {
188    // verb ::= predicate | "a" | ("has" expression) | ("is" expression "of") | "=" | "<=" | "=>"
189    if let Some(pred) = child(node, SyntaxKind::Predicate) {
190        convert_predicate(&pred)
191    } else if let Some(alit) = child(node, SyntaxKind::Alit) {
192        Term::NamedNode(NamedNode::A(alit.text_range().start().into()))
193    } else if child(node, SyntaxKind::HasLit).is_some() {
194        // "has" expression — use the expression as the predicate term
195        if let Some(expr) = child(node, SyntaxKind::Expression) {
196            convert_expression(&expr)
197        } else {
198            Term::Invalid
199        }
200    } else if child(node, SyntaxKind::IsLit).is_some() {
201        // "is" expression "of" — use the expression as the predicate term
202        if let Some(expr) = child(node, SyntaxKind::Expression) {
203            convert_expression(&expr)
204        } else {
205            Term::Invalid
206        }
207    } else if child(node, SyntaxKind::Eq).is_some() {
208        Term::NamedNode(NamedNode::Full(
209            "http://www.w3.org/2002/07/owl#sameAs".to_string(),
210            node.text_range().start().into(),
211        ))
212    } else if child(node, SyntaxKind::ImplyLeft).is_some() {
213        Term::NamedNode(NamedNode::Full(
214            "http://www.w3.org/2000/10/swap/log#impliedBy".to_string(),
215            node.text_range().start().into(),
216        ))
217    } else if child(node, SyntaxKind::ImplyRight).is_some() {
218        Term::NamedNode(NamedNode::Full(
219            "http://www.w3.org/2000/10/swap/log#implies".to_string(),
220            node.text_range().start().into(),
221        ))
222    } else {
223        Term::Invalid
224    }
225}
226
227fn convert_predicate(node: &Node) -> Term {
228    // predicate ::= expression | ("<-" expression)
229    // In both cases, extract the expression as the term.
230    if let Some(expr) = child(node, SyntaxKind::Expression) {
231        convert_expression(&expr)
232    } else {
233        Term::Invalid
234    }
235}
236
237fn convert_object_list(node: &Node) -> Vec<Spanned<Term>> {
238    children(node, SyntaxKind::Object)
239        .map(|o| {
240            let range = text_range(&o);
241            Spanned(convert_object(&o), range)
242        })
243        .collect()
244}
245
246fn convert_subject(node: &Node) -> Term {
247    // subject ::= expression
248    if let Some(expr) = child(node, SyntaxKind::Expression) {
249        convert_expression(&expr)
250    } else {
251        Term::Invalid
252    }
253}
254
255fn convert_object(node: &Node) -> Term {
256    // object ::= expression
257    if let Some(expr) = child(node, SyntaxKind::Expression) {
258        convert_expression(&expr)
259    } else {
260        Term::Invalid
261    }
262}
263
264// ── expression / path / pathItem chain ───────────────────────────────────────
265
266fn convert_expression(node: &Node) -> Term {
267    child(node, SyntaxKind::Path)
268        .map(|p| convert_path(&p))
269        .unwrap_or(Term::Invalid)
270}
271
272fn convert_path(node: &Node) -> Term {
273    // path ::= pathItem (("!" path) | ("^" path))?
274    // Extract just the PathItem term.
275    child(node, SyntaxKind::PathItem)
276        .map(|pi| convert_path_item(&pi))
277        .unwrap_or(Term::Invalid)
278}
279
280fn convert_path_item(node: &Node) -> Term {
281    if let Some(iri) = child(node, SyntaxKind::Iri) {
282        Term::NamedNode(convert_iri(&iri))
283    } else if let Some(bn) = child(node, SyntaxKind::BlankNode) {
284        Term::BlankNode(convert_blank_node(&bn))
285    } else if let Some(qv) = child(node, SyntaxKind::QuickVar) {
286        convert_quick_var(&qv)
287    } else if let Some(coll) = child(node, SyntaxKind::Collection) {
288        Term::Collection(convert_collection(&coll))
289    } else if let Some(bpl) = child(node, SyntaxKind::BlankNodePropertyList) {
290        convert_blank_node_property_list(&bpl)
291    } else if let Some(ipl) = child(node, SyntaxKind::IriPropertyList) {
292        convert_iri_property_list(&ipl)
293    } else if let Some(lit) = child(node, SyntaxKind::Literal) {
294        Term::Literal(convert_literal(&lit))
295    } else if child(node, SyntaxKind::Formula).is_some() {
296        // Formulas as terms are N3-specific; triples inside are collected separately
297        Term::Invalid
298    } else {
299        Term::Invalid
300    }
301}
302
303// ── term converters ──────────────────────────────────────────────────────────
304
305fn convert_iri(node: &Node) -> NamedNode {
306    if let Some(iriref_node) = child(node, SyntaxKind::Iriref) {
307        iri_from_iriref_node(&iriref_node)
308    } else if let Some(pn) = child(node, SyntaxKind::PrefixedName) {
309        convert_prefixed_name(&pn)
310    } else {
311        NamedNode::Invalid
312    }
313}
314
315fn iri_from_iriref_node(node: &Node) -> NamedNode {
316    let text = terminal_text(node);
317    let offset: usize = node.text_range().start().into();
318    let inner = text.trim_start_matches('<').trim_end_matches('>');
319    NamedNode::Full(inner.to_string(), offset)
320}
321
322fn convert_prefixed_name(node: &Node) -> NamedNode {
323    if let Some(pname_ln) = child(node, SyntaxKind::PnameLn) {
324        let text = terminal_text(&pname_ln);
325        let offset: usize = pname_ln.text_range().start().into();
326        let (prefix, value) = text.split_once(':').unwrap_or((&text, ""));
327        NamedNode::Prefixed {
328            prefix: prefix.to_string(),
329            value: value.to_string(),
330            idx: offset,
331            computed: None,
332        }
333    } else if let Some(pname_ns) = child(node, SyntaxKind::PnameNs) {
334        let text = terminal_text(&pname_ns);
335        let offset: usize = pname_ns.text_range().start().into();
336        let prefix = text.trim_end_matches(':');
337        NamedNode::Prefixed {
338            prefix: prefix.to_string(),
339            value: String::new(),
340            idx: offset,
341            computed: None,
342        }
343    } else {
344        NamedNode::Invalid
345    }
346}
347
348fn convert_blank_node(node: &Node) -> BlankNode {
349    if let Some(label) = child(node, SyntaxKind::BlankNodeLabel) {
350        let text = terminal_text(&label);
351        let offset: usize = label.text_range().start().into();
352        let name = text.strip_prefix("_:").unwrap_or(&text);
353        BlankNode::Named(name.to_string(), offset)
354    } else {
355        let offset: usize = node.text_range().start().into();
356        BlankNode::Unnamed(Vec::new(), offset, offset)
357    }
358}
359
360fn convert_blank_node_property_list(node: &Node) -> Term {
361    let offset: usize = node.text_range().start().into();
362    let end: usize = node.text_range().end().into();
363    let po = child(node, SyntaxKind::PredicateObjectList)
364        .map(|n| convert_predicate_object_list(&n))
365        .unwrap_or_default();
366    Term::BlankNode(BlankNode::Unnamed(po, offset, end))
367}
368
369fn convert_iri_property_list(node: &Node) -> Term {
370    // iriPropertyList ::= IPLSTART iri predicateObjectList "]"
371    // Use the Iri child for the term representation.
372    if let Some(iri) = child(node, SyntaxKind::Iri) {
373        Term::NamedNode(convert_iri(&iri))
374    } else {
375        Term::Invalid
376    }
377}
378
379fn convert_quick_var(node: &Node) -> Term {
380    // quickVar ::= QUICK_VAR_NAME
381    if let Some(qv) = child(node, SyntaxKind::QuickVarName) {
382        let text = terminal_text(&qv);
383        let offset: usize = qv.text_range().start().into();
384        let name = text.strip_prefix('?').unwrap_or(&text);
385        Term::Variable(Variable(name.to_string(), offset))
386    } else {
387        Term::Invalid
388    }
389}
390
391fn convert_collection(node: &Node) -> Vec<Spanned<Term>> {
392    children(node, SyntaxKind::Object)
393        .map(|o| {
394            let range = text_range(&o);
395            Spanned(convert_object(&o), range)
396        })
397        .collect()
398}
399
400fn convert_literal(node: &Node) -> Literal {
401    if let Some(rdf) = child(node, SyntaxKind::RdfLiteral) {
402        Literal::RDF(convert_rdf_literal(&rdf))
403    } else if let Some(num) = child(node, SyntaxKind::NumericLiteral) {
404        Literal::Numeric(num.text().to_string().trim().to_string())
405    } else if let Some(b) = child(node, SyntaxKind::BooleanLiteral) {
406        Literal::Boolean(b.text().to_string().trim() == "true")
407    } else {
408        Literal::Numeric(String::new())
409    }
410}
411
412fn convert_rdf_literal(node: &Node) -> RDFLiteral {
413    // rdfLiteral ::= STRING (LANGTAG | ("^^" iri))?
414    // In N3, String is a single terminal (not wrapped in MyString).
415    let offset: usize = node.text_range().start().into();
416
417    let (value, quote_style, len) = if let Some(str_node) = child(node, SyntaxKind::String) {
418        let text = terminal_text(&str_node);
419        let (inner, style) = strip_string_delimiters(&text);
420        (inner.to_string(), style, 1)
421    } else {
422        (String::new(), StringStyle::Double, 0)
423    };
424
425    let lang = child(node, SyntaxKind::Langtag).map(|n| {
426        let span = text_range(&n);
427        let text = terminal_text(&n);
428        let value = text.strip_prefix('@').unwrap_or(&text).to_string();
429        Spanned(value, span)
430    });
431
432    let ty = child(node, SyntaxKind::Iri).map(|n| Spanned(convert_iri(&n), text_range(&n)));
433
434    RDFLiteral {
435        value,
436        quote_style,
437        lang,
438        ty,
439        idx: offset,
440        len,
441    }
442}
443
444fn strip_string_delimiters(text: &str) -> (&str, StringStyle) {
445    if let Some(inner) = text
446        .strip_prefix("\"\"\"")
447        .and_then(|s| s.strip_suffix("\"\"\""))
448    {
449        (inner, StringStyle::DoubleLong)
450    } else if let Some(inner) = text.strip_prefix("'''").and_then(|s| s.strip_suffix("'''")) {
451        (inner, StringStyle::SingleLong)
452    } else if let Some(inner) = text.strip_prefix('"').and_then(|s| s.strip_suffix('"')) {
453        (inner, StringStyle::Double)
454    } else if let Some(inner) = text.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
455        (inner, StringStyle::Single)
456    } else {
457        (text, StringStyle::Double)
458    }
459}
460
461#[cfg(test)]
462mod tests {
463    use super::*;
464    use crate::{n3::parser as lang, parse as crate_parse};
465
466    fn parse(input: &str) -> Turtle {
467        let (result, _) = crate_parse(lang::Rule::new(lang::SyntaxKind::N3Doc), input);
468        let root = result.syntax::<lang::Lang>();
469        convert(&root)
470    }
471
472    // ── helpers ──────────────────────────────────────────────────────────────
473
474    fn prefixed(prefix: &str, value: &str) -> NamedNode {
475        NamedNode::Prefixed {
476            prefix: prefix.to_string(),
477            value: value.to_string(),
478            idx: 0,
479            computed: None,
480        }
481    }
482
483    fn nn_eq(a: &NamedNode, b: &NamedNode) -> bool {
484        match (a, b) {
485            (NamedNode::Full(s1, _), NamedNode::Full(s2, _)) => s1 == s2,
486            (
487                NamedNode::Prefixed {
488                    prefix: p1,
489                    value: v1,
490                    ..
491                },
492                NamedNode::Prefixed {
493                    prefix: p2,
494                    value: v2,
495                    ..
496                },
497            ) => p1 == p2 && v1 == v2,
498            (NamedNode::A(_), NamedNode::A(_)) => true,
499            (NamedNode::Invalid, NamedNode::Invalid) => true,
500            _ => false,
501        }
502    }
503
504    fn term_nn(t: &Term) -> &NamedNode {
505        match t {
506            Term::NamedNode(nn) => nn,
507            other => panic!("expected NamedNode, got {:?}", other),
508        }
509    }
510
511    fn term_lit(t: &Term) -> &Literal {
512        match t {
513            Term::Literal(l) => l,
514            other => panic!("expected Literal, got {:?}", other),
515        }
516    }
517
518    fn term_bn(t: &Term) -> &BlankNode {
519        match t {
520            Term::BlankNode(bn) => bn,
521            other => panic!("expected BlankNode, got {:?}", other),
522        }
523    }
524
525    fn term_var(t: &Term) -> &Variable {
526        match t {
527            Term::Variable(v) => v,
528            other => panic!("expected Variable, got {:?}", other),
529        }
530    }
531
532    // ── prefix directives ────────────────────────────────────────────────────
533
534    #[test]
535    fn test_prefix_directive() {
536        let doc = parse("@prefix ex: <http://example.org/> .");
537        assert_eq!(doc.prefixes.len(), 1);
538        let p = doc.prefixes[0].value();
539        assert_eq!(p.prefix.value(), "ex");
540        assert!(nn_eq(
541            p.value.value(),
542            &NamedNode::Full("http://example.org/".to_string(), 0)
543        ));
544    }
545
546    #[test]
547    fn test_sparql_prefix_directive() {
548        let doc = parse("PREFIX ex: <http://example.org/>\nex:alice ex:knows ex:bob .");
549        assert_eq!(doc.prefixes.len(), 1);
550        assert_eq!(doc.prefixes[0].value().prefix.value(), "ex");
551    }
552
553    // ── base directives ──────────────────────────────────────────────────────
554
555    #[test]
556    fn test_base_directive() {
557        let doc = parse("@base <http://example.org/> .");
558        let base = doc.base.as_ref().expect("base should be present");
559        assert!(nn_eq(
560            base.value().1.value(),
561            &NamedNode::Full("http://example.org/".to_string(), 0)
562        ));
563    }
564
565    #[test]
566    fn test_sparql_base_directive() {
567        let doc = parse("BASE <http://example.org/>\n<alice> <knows> <bob> .");
568        let base = doc.base.as_ref().expect("base should be present");
569        assert!(nn_eq(
570            base.value().1.value(),
571            &NamedNode::Full("http://example.org/".to_string(), 0)
572        ));
573    }
574
575    // ── simple triple ────────────────────────────────────────────────────────
576
577    #[test]
578    fn test_simple_triple() {
579        let doc = parse("@prefix ex: <http://example.org/> .\nex:alice ex:knows ex:bob .");
580        assert_eq!(doc.triples.len(), 1);
581        let t = doc.triples[0].value();
582        assert!(nn_eq(term_nn(t.subject.value()), &prefixed("ex", "alice")));
583        assert_eq!(t.po.len(), 1);
584        assert!(nn_eq(
585            term_nn(t.po[0].predicate.value()),
586            &prefixed("ex", "knows")
587        ));
588        assert!(nn_eq(
589            term_nn(t.po[0].object[0].value()),
590            &prefixed("ex", "bob")
591        ));
592    }
593
594    // ── full IRI triple ──────────────────────────────────────────────────────
595
596    #[test]
597    fn test_full_iri_triple() {
598        let doc = parse(
599            "<http://example.org/alice> <http://example.org/knows> <http://example.org/bob> .",
600        );
601        assert_eq!(doc.triples.len(), 1);
602        let t = doc.triples[0].value();
603        assert!(nn_eq(
604            term_nn(t.subject.value()),
605            &NamedNode::Full("http://example.org/alice".to_string(), 0)
606        ));
607        assert!(nn_eq(
608            term_nn(t.po[0].predicate.value()),
609            &NamedNode::Full("http://example.org/knows".to_string(), 0)
610        ));
611        assert!(nn_eq(
612            term_nn(t.po[0].object[0].value()),
613            &NamedNode::Full("http://example.org/bob".to_string(), 0)
614        ));
615    }
616
617    // ── `a` shorthand ────────────────────────────────────────────────────────
618
619    #[test]
620    fn test_a_shorthand() {
621        let doc = parse(
622            "@prefix foaf: <http://xmlns.com/foaf/0.1/> .\n<http://example.org/alice> a foaf:Person .",
623        );
624        let t = doc.triples[0].value();
625        assert!(matches!(
626            t.po[0].predicate.value(),
627            Term::NamedNode(NamedNode::A(_))
628        ));
629        assert!(nn_eq(
630            term_nn(t.po[0].object[0].value()),
631            &prefixed("foaf", "Person")
632        ));
633    }
634
635    // ── multiple PO pairs with `;` ───────────────────────────────────────────
636
637    #[test]
638    fn test_multiple_po_pairs() {
639        let doc =
640            parse("@prefix ex: <http://example.org/> .\nex:alice ex:name \"Alice\" ; ex:age 30 .");
641        let t = doc.triples[0].value();
642        assert_eq!(t.po.len(), 2);
643        assert!(nn_eq(
644            term_nn(t.po[0].predicate.value()),
645            &prefixed("ex", "name")
646        ));
647        assert!(nn_eq(
648            term_nn(t.po[1].predicate.value()),
649            &prefixed("ex", "age")
650        ));
651    }
652
653    // ── multiple objects with `,` ────────────────────────────────────────────
654
655    #[test]
656    fn test_multiple_objects() {
657        let doc =
658            parse("@prefix ex: <http://example.org/> .\nex:alice ex:knows ex:bob , ex:carol .");
659        let t = doc.triples[0].value();
660        assert_eq!(t.po[0].object.len(), 2);
661        assert!(nn_eq(
662            term_nn(t.po[0].object[0].value()),
663            &prefixed("ex", "bob")
664        ));
665        assert!(nn_eq(
666            term_nn(t.po[0].object[1].value()),
667            &prefixed("ex", "carol")
668        ));
669    }
670
671    // ── string literal ───────────────────────────────────────────────────────
672
673    #[test]
674    fn test_string_literal() {
675        let doc = parse("@prefix ex: <http://example.org/> .\nex:alice ex:name \"Alice\" .");
676        let t = doc.triples[0].value();
677        let lit = term_lit(t.po[0].object[0].value());
678        assert_eq!(lit.plain_string(), "Alice");
679    }
680
681    // ── numeric literal ──────────────────────────────────────────────────────
682
683    #[test]
684    fn test_numeric_literal() {
685        let doc = parse("@prefix ex: <http://example.org/> .\nex:alice ex:age 30 .");
686        let t = doc.triples[0].value();
687        match term_lit(t.po[0].object[0].value()) {
688            Literal::Numeric(n) => assert_eq!(n, "30"),
689            other => panic!("expected Numeric literal, got {:?}", other),
690        }
691    }
692
693    // ── boolean literal ──────────────────────────────────────────────────────
694
695    #[test]
696    fn test_boolean_literal() {
697        let doc = parse("@prefix ex: <http://example.org/> .\nex:alice ex:active true .");
698        let t = doc.triples[0].value();
699        assert_eq!(*term_lit(t.po[0].object[0].value()), Literal::Boolean(true));
700    }
701
702    // ── named blank node ─────────────────────────────────────────────────────
703
704    #[test]
705    fn test_named_blank_node() {
706        let doc = parse("@prefix ex: <http://example.org/> .\n_:b0 ex:knows ex:alice .");
707        let t = doc.triples[0].value();
708        match term_bn(t.subject.value()) {
709            BlankNode::Named(name, _) => assert_eq!(name, "b0"),
710            other => panic!("expected Named blank node, got {:?}", other),
711        }
712    }
713
714    // ── anonymous blank node property list ───────────────────────────────────
715
716    #[test]
717    fn test_anon_blank_node_property_list() {
718        let doc =
719            parse("@prefix ex: <http://example.org/> .\nex:alice ex:knows [ ex:name \"Bob\" ] .");
720        let t = doc.triples[0].value();
721        match term_bn(t.po[0].object[0].value()) {
722            BlankNode::Unnamed(pos, _, _) => {
723                assert_eq!(pos.len(), 1);
724                assert!(nn_eq(
725                    term_nn(pos[0].predicate.value()),
726                    &prefixed("ex", "name")
727                ));
728            }
729            other => panic!("expected Unnamed blank node, got {:?}", other),
730        }
731    }
732
733    // ── string literal with lang and datatype ────────────────────────────────
734
735    #[test]
736    fn test_string_literal_with_lang() {
737        let doc = parse("@prefix ex: <http://example.org/> .\nex:alice ex:name \"Alice\"@en .");
738        let t = doc.triples[0].value();
739        match term_lit(t.po[0].object[0].value()) {
740            Literal::RDF(r) => {
741                assert_eq!(r.value, "Alice");
742                assert_eq!(r.lang.as_ref().map(|l| l.as_str()), Some("en"));
743            }
744            other => panic!("expected RDF literal, got {:?}", other),
745        }
746    }
747
748    #[test]
749    fn test_string_literal_with_datatype() {
750        let doc = parse(
751            "@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n@prefix ex: <http://example.org/> .\nex:alice ex:age \"30\"^^xsd:integer .",
752        );
753        let t = doc.triples[0].value();
754        match term_lit(t.po[0].object[0].value()) {
755            Literal::RDF(r) => {
756                assert_eq!(r.value, "30");
757                assert!(r.ty.is_some());
758                assert!(nn_eq(r.ty.as_ref().unwrap(), &prefixed("xsd", "integer")));
759            }
760            other => panic!("expected RDF literal, got {:?}", other),
761        }
762    }
763
764    // ── blank node as subject with property list ──────────────────────────────
765
766    #[test]
767    fn test_blank_node_subject_property_list() {
768        let doc = parse("@prefix ex: <http://example.org/> .\n[ ex:name \"Alice\" ] ex:age 30 .");
769        let t = doc.triples[0].value();
770        assert!(matches!(
771            t.subject.value(),
772            Term::BlankNode(BlankNode::Unnamed(_, _, _))
773        ));
774        assert_eq!(t.po.len(), 1);
775    }
776
777    // ── multiple triples ──────────────────────────────────────────────────────
778
779    #[test]
780    fn test_multiple_triples() {
781        let doc =
782            parse("@prefix ex: <http://example.org/> .\nex:alice ex:age 30 .\nex:bob ex:age 25 .");
783        assert_eq!(doc.triples.len(), 2);
784        assert!(nn_eq(
785            term_nn(doc.triples[0].value().subject.value()),
786            &prefixed("ex", "alice")
787        ));
788        assert!(nn_eq(
789            term_nn(doc.triples[1].value().subject.value()),
790            &prefixed("ex", "bob")
791        ));
792    }
793
794    // ── fault-tolerant parsing ────────────────────────────────────────────────
795
796    fn parse_raw(input: &str) -> crate::Parse {
797        let (result, _) = crate_parse(lang::Rule::new(lang::SyntaxKind::N3Doc), input);
798        result
799    }
800
801    #[test]
802    fn test_valid_input_has_no_errors() {
803        let p = parse_raw("@prefix ex: <http://example.org/> .\nex:alice ex:age 30 .");
804        assert_eq!(p.errors.len(), 0, "valid input should produce no errors");
805    }
806
807    #[test]
808    fn test_trailing_dot_is_optional() {
809        // In N3, the trailing dot is optional — a statement without it should
810        // parse without errors and still produce the triple.
811        let p = parse_raw("@prefix ex: <http://example.org/> .\nex:alice ex:age 30");
812        assert_eq!(
813            p.errors.len(),
814            0,
815            "N3 trailing dot is optional; should produce no errors"
816        );
817        let doc = parse("@prefix ex: <http://example.org/> .\nex:alice ex:age 30");
818        assert_eq!(doc.triples.len(), 1);
819    }
820
821    #[test]
822    fn test_missing_prefix_iri_reports_error() {
823        let p = parse_raw("@prefix ex: .");
824        assert!(
825            p.errors.len() > 0,
826            "missing prefix IRI should produce an error"
827        );
828    }
829
830    // ── quick variables ──────────────────────────────────────────────────────
831
832    #[test]
833    fn test_quick_variables() {
834        let doc = parse("?x ?y ?z .");
835        assert_eq!(doc.triples.len(), 1);
836        let t = doc.triples[0].value();
837        let v = term_var(t.subject.value());
838        assert_eq!(v.0, "x");
839        let pred_v = term_var(t.po[0].predicate.value());
840        assert_eq!(pred_v.0, "y");
841        let obj_v = term_var(t.po[0].object[0].value());
842        assert_eq!(obj_v.0, "z");
843    }
844
845    // ── collection ───────────────────────────────────────────────────────────
846
847    #[test]
848    fn test_collection() {
849        let doc =
850            parse("@prefix ex: <http://example.org/> .\nex:alice ex:list ( ex:a ex:b ex:c ) .");
851        let t = doc.triples[0].value();
852        match t.po[0].object[0].value() {
853            Term::Collection(items) => {
854                assert_eq!(items.len(), 3);
855                assert!(nn_eq(term_nn(items[0].value()), &prefixed("ex", "a")));
856                assert!(nn_eq(term_nn(items[1].value()), &prefixed("ex", "b")));
857                assert!(nn_eq(term_nn(items[2].value()), &prefixed("ex", "c")));
858            }
859            other => panic!("expected Collection, got {:?}", other),
860        }
861    }
862
863    // ── formula with nested triples ──────────────────────────────────────────
864
865    #[test]
866    fn test_formula_nested_triples() {
867        let doc =
868            parse("@prefix ex: <http://example.org/> .\n{ ex:a ex:b ex:c } ex:implies ex:d .");
869        // The top-level triple has a formula as subject (Term::Invalid), so it's
870        // skipped. Only the nested triple inside the formula is collected.
871        assert_eq!(doc.triples.len(), 1);
872
873        // The nested triple from inside the formula
874        let nested = &doc.triples[0];
875        assert!(
876            matches!(nested.value().subject.value(), Term::NamedNode(nn) if nn_eq(nn, &prefixed("ex", "a")))
877        );
878    }
879
880    // ── `has` verb ───────────────────────────────────────────────────────────
881
882    #[test]
883    fn test_has_verb() {
884        let doc = parse("@prefix ex: <http://example.org/> .\nex:alice has ex:friend ex:bob .");
885        assert_eq!(doc.triples.len(), 1);
886        let t = doc.triples[0].value();
887        assert!(nn_eq(term_nn(t.subject.value()), &prefixed("ex", "alice")));
888        // The predicate from "has ex:friend" should be ex:friend
889        assert!(nn_eq(
890            term_nn(t.po[0].predicate.value()),
891            &prefixed("ex", "friend")
892        ));
893        assert!(nn_eq(
894            term_nn(t.po[0].object[0].value()),
895            &prefixed("ex", "bob")
896        ));
897    }
898
899    // ── `is ... of` verb ─────────────────────────────────────────────────────
900
901    #[test]
902    fn test_is_of_verb() {
903        let doc = parse("@prefix ex: <http://example.org/> .\nex:bob is ex:friend of ex:alice .");
904        assert_eq!(doc.triples.len(), 1);
905        let t = doc.triples[0].value();
906        // The predicate from "is ex:friend of" should be ex:friend
907        assert!(nn_eq(
908            term_nn(t.po[0].predicate.value()),
909            &prefixed("ex", "friend")
910        ));
911    }
912
913    #[test]
914    fn test_n3_rule_formulas() {
915        let input = "@prefix : <http://example.org/> .\n\n\
916            { ?x a :Human . } => { ?x a :Mortal . } .\n\n\
917            :Socrates a :Human .\n";
918        let doc = parse(input);
919        // The formula rule `{ } => { }` is skipped (subject is a formula),
920        // but the inner triples are extracted, plus the regular triple.
921        assert_eq!(doc.triples.len(), 3);
922        // Inner formula triples
923        assert_eq!(format!("{}", doc.triples[0].value().subject.value()), "?x");
924        assert_eq!(format!("{}", doc.triples[1].value().subject.value()), "?x");
925        // Regular triple
926        assert!(nn_eq(
927            term_nn(doc.triples[2].value().subject.value()),
928            &prefixed("", "Socrates")
929        ));
930    }
931
932    // ── incremental re-parse tests ────────────────────────────────────────────
933    //
934    // These tests verify that the A* search finds the correct parse within its
935    // iteration budget after a small edit.  They catch grammar weight errors:
936    // if a token's error_value is set too high the heuristic becomes too
937    // optimistic and the search exhausts its budget before finding the solution.
938
939    use crate::{IncrementalBias, PrevParseInfo, TokenTrait, parse_incremental};
940
941    fn prev_info_incr(text: &str) -> PrevParseInfo {
942        let (_, tokens) = crate_parse(lang::Rule::new(lang::SyntaxKind::N3Doc), text);
943        let mut depth: i32 = 0;
944        PrevParseInfo {
945            tokens: tokens
946                .iter()
947                .map(|t| {
948                    let d = depth.clamp(0, 255) as u8;
949                    depth += t.kind.bracket_delta() as i32;
950                    t.to_prev_token(d)
951                })
952                .collect(),
953            had_errors: false,
954        }
955    }
956
957    fn parse_incr(before: &str, after: &str) -> Turtle {
958        let prev = prev_info_incr(before);
959        let (result, _) = parse_incremental(
960            lang::Rule::new(lang::SyntaxKind::N3Doc),
961            after,
962            Some(&prev),
963            IncrementalBias::default(),
964        );
965        let root = result.syntax::<lang::Lang>();
966        convert(&root)
967    }
968
969    /// Remove the object: `<a> <b> <c> .` → `<a> <b> .`
970    /// The parser should recover and produce one triple.
971    #[test]
972    fn test_incremental_remove_object() {
973        let doc = parse_incr("<a> <b> <c> .", "<a> <b> .");
974        assert_eq!(doc.triples.len(), 1, "should produce one triple");
975        match doc.triples[0].value().subject.value() {
976            Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "a"),
977            other => panic!("expected <a> as subject, got {:?}", other),
978        }
979    }
980
981    /// Add a second triple: `<a> <b> <c> .` → `<a> <b> <c> .\n<d> <e> <f> .`
982    /// The incremental parser should produce two triples.
983    #[test]
984    fn test_incremental_add_triple() {
985        let doc = parse_incr("<a> <b> <c> .", "<a> <b> <c> .\n<d> <e> <f> .");
986        assert_eq!(doc.triples.len(), 2, "should produce two triples");
987        match doc.triples[0].value().subject.value() {
988            Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "a"),
989            other => panic!("expected <a> as first subject, got {:?}", other),
990        }
991        match doc.triples[1].value().subject.value() {
992            Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "d"),
993            other => panic!("expected <d> as second subject, got {:?}", other),
994        }
995    }
996
997    /// Insert an extra IRI inside a formula: `{ <a> <b> <c> . }` → `{ <a> <b> <c> <d> . }`
998    /// The tokens inside shift their roles; the formula bracket weights must be
999    /// low enough for the A* to find the correct parse within budget.
1000    #[test]
1001    fn test_incremental_insert_token_inside_formula() {
1002        let doc = parse_incr("{ <a> <b> <c> . }", "{ <a> <b> <c> <d> . }");
1003        // Two triples are produced via error recovery (the extra <d> forces a
1004        // new statement to start).
1005        assert!(
1006            doc.triples.len() >= 1,
1007            "should produce at least one triple, got {}",
1008            doc.triples.len()
1009        );
1010        // The first triple inside the formula should have <a> as its subject.
1011        match doc.triples[0].value().subject.value() {
1012            Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "a"),
1013            other => panic!("expected <a> as subject inside formula, got {:?}", other),
1014        }
1015    }
1016
1017    /// Remove a triple from a two-triple document:
1018    /// `<a> <b> <c> .\n<d> <e> <f> .` → `<a> <b> <c> .`
1019    /// The incremental parser should produce exactly one triple.
1020    #[test]
1021    fn test_incremental_remove_triple() {
1022        let doc = parse_incr("<a> <b> <c> .\n<d> <e> <f> .", "<a> <b> <c> .");
1023        assert_eq!(doc.triples.len(), 1, "should produce one triple");
1024        match doc.triples[0].value().subject.value() {
1025            Term::NamedNode(NamedNode::Full(iri, _)) => assert_eq!(iri, "a"),
1026            other => panic!("expected <a> as subject, got {:?}", other),
1027        }
1028    }
1029}