Skip to main content

rdf_parsers/sparql/
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
27/// Recursively find all descendants of a given kind.
28fn collect_descendants(node: &Node, kind: SyntaxKind, out: &mut Vec<Node>) {
29    for c in node.children() {
30        if c.kind() == kind {
31            out.push(c.clone());
32        }
33        collect_descendants(&c, kind, out);
34    }
35}
36
37pub fn convert(root: &Node) -> Turtle {
38    let mut base = None;
39    let mut prefixes = Vec::new();
40    let mut triples = Vec::new();
41
42    // Extract base and prefix declarations from any Prologue in the tree.
43    let mut prologues = Vec::new();
44    collect_descendants(root, SyntaxKind::Prologue, &mut prologues);
45    for prologue in &prologues {
46        for bd in children(prologue, SyntaxKind::BaseDecl) {
47            base = Some(Spanned(convert_base_decl(&bd), text_range(&bd)));
48        }
49        for pd in children(prologue, SyntaxKind::PrefixDecl) {
50            prefixes.push(Spanned(convert_prefix_decl(&pd), text_range(&pd)));
51        }
52    }
53
54    // Collect all triple patterns from the entire tree.
55    collect_triples(root, &mut triples);
56
57    Turtle::new(base, prefixes, triples)
58}
59
60fn collect_triples(node: &Node, triples: &mut Vec<Spanned<Triple>>) {
61    for child_node in node.children() {
62        let kind = child_node.kind();
63        match kind {
64            SyntaxKind::TriplesSameSubject => {
65                let range = text_range(&child_node);
66                triples.push(Spanned(convert_triples_same_subject(&child_node), range));
67            }
68            SyntaxKind::TriplesSameSubjectPath => {
69                let range = text_range(&child_node);
70                triples.push(Spanned(
71                    convert_triples_same_subject_path(&child_node),
72                    range,
73                ));
74            }
75            _ => {
76                collect_triples(&child_node, triples);
77            }
78        }
79    }
80}
81
82// ── prologue ─────────────────────────────────────────────────────────────────
83
84fn convert_base_decl(node: &Node) -> Base {
85    // BaseDecl ::= 'BASE' IRIREF
86    let range = text_range(node);
87    let nn = child(node, SyntaxKind::Iriref)
88        .map(|n| iri_from_iriref_node(&n))
89        .unwrap_or(NamedNode::Invalid);
90    let nn_range = child(node, SyntaxKind::Iriref)
91        .map(|n| text_range(&n))
92        .unwrap_or(range.clone());
93    Base(range, Spanned(nn, nn_range))
94}
95
96fn convert_prefix_decl(node: &Node) -> TurtlePrefix {
97    // PrefixDecl ::= 'PREFIX' PNAME_NS IRIREF
98    let range = text_range(node);
99    let prefix_text = child(node, SyntaxKind::PnameNs)
100        .map(|n| {
101            let text = terminal_text(&n);
102            let tr = text_range(&n);
103            Spanned(text.trim_end_matches(':').to_string(), tr)
104        })
105        .unwrap_or_else(|| Spanned(String::new(), range.clone()));
106
107    let value = child(node, SyntaxKind::Iriref)
108        .map(|n| {
109            let tr = text_range(&n);
110            Spanned(iri_from_iriref_node(&n), tr)
111        })
112        .unwrap_or_else(|| Spanned(NamedNode::Invalid, range.clone()));
113
114    TurtlePrefix {
115        span: range,
116        prefix: prefix_text,
117        value,
118    }
119}
120
121// ── TriplesSameSubject (CONSTRUCT, TriplesTemplate) ──────────────────────────
122
123fn convert_triples_same_subject(node: &Node) -> Triple {
124    // TriplesSameSubject ::= VarOrTerm PropertyListNotEmpty | TriplesNode PropertyList
125    let (subject, po_node) = if let Some(vot) = child(node, SyntaxKind::VarOrTerm) {
126        let range = text_range(&vot);
127        (
128            Spanned(convert_var_or_term(&vot), range),
129            child(node, SyntaxKind::PropertyListNotEmpty),
130        )
131    } else if let Some(tn) = child(node, SyntaxKind::TriplesNode) {
132        let range = text_range(&tn);
133        (
134            Spanned(convert_triples_node(&tn), range),
135            child(node, SyntaxKind::PropertyList)
136                .and_then(|pl| child(&pl, SyntaxKind::PropertyListNotEmpty)),
137        )
138    } else {
139        (Spanned(Term::Invalid, text_range(node)), None)
140    };
141
142    let po = po_node
143        .map(|n| convert_property_list_not_empty(&n))
144        .unwrap_or_default();
145
146    Triple {
147        subject,
148        po,
149        graph: None,
150    }
151}
152
153// ── TriplesSameSubjectPath (WHERE clause TriplesBlock) ───────────────────────
154
155fn convert_triples_same_subject_path(node: &Node) -> Triple {
156    // TriplesSameSubjectPath ::= Subject1 PropertyListPathNotEmpty | Subject2 PropertyListPath
157    // Subject1 ::= VarOrTerm
158    // Subject2 ::= TriplesNodePath
159    let (subject, po_node) = if let Some(s1) = child(node, SyntaxKind::Subject1) {
160        let range = text_range(&s1);
161        let term = child(&s1, SyntaxKind::VarOrTerm)
162            .map(|v| convert_var_or_term(&v))
163            .unwrap_or(Term::Invalid);
164        (
165            Spanned(term, range),
166            child(node, SyntaxKind::PropertyListPathNotEmpty),
167        )
168    } else if let Some(s2) = child(node, SyntaxKind::Subject2) {
169        let range = text_range(&s2);
170        let term = child(&s2, SyntaxKind::TriplesNodePath)
171            .map(|t| convert_triples_node_path(&t))
172            .unwrap_or(Term::Invalid);
173        (
174            Spanned(term, range),
175            child(node, SyntaxKind::PropertyListPath)
176                .and_then(|pl| child(&pl, SyntaxKind::PropertyListPathNotEmpty)),
177        )
178    } else {
179        (Spanned(Term::Invalid, text_range(node)), None)
180    };
181
182    let po = po_node
183        .map(|n| convert_property_list_path_not_empty(&n))
184        .unwrap_or_default();
185
186    Triple {
187        subject,
188        po,
189        graph: None,
190    }
191}
192
193// ── property lists ───────────────────────────────────────────────────────────
194
195fn convert_property_list_not_empty(node: &Node) -> Vec<Spanned<PO>> {
196    // PropertyListNotEmpty ::= Verb ObjectList (';' (Verb ObjectList)?)*
197    let verbs: Vec<_> = children(node, SyntaxKind::Verb).collect();
198    let object_lists: Vec<_> = children(node, SyntaxKind::ObjectList).collect();
199
200    verbs
201        .into_iter()
202        .zip(object_lists.into_iter())
203        .map(|(v, ol)| {
204            let range = text_range(&v).start..text_range(&ol).end;
205            let pred_range = text_range(&v);
206            let predicate = convert_verb(&v);
207            let objects = convert_object_list(&ol);
208            Spanned(
209                PO {
210                    predicate: Spanned(predicate, pred_range),
211                    object: objects,
212                },
213                range,
214            )
215        })
216        .collect()
217}
218
219fn convert_property_list_path_not_empty(node: &Node) -> Vec<Spanned<PO>> {
220    // PropertyListPathNotEmpty ::=
221    //   (VerbPath | VerbSimple) ObjectListPath
222    //   (';' ((VerbPath | VerbSimple) ObjectList)?)*
223    //
224    // Walk children in order, pairing verbs with their object lists.
225    let mut result = Vec::new();
226    let mut current_verb: Option<(Term, std::ops::Range<usize>)> = None;
227
228    for child_node in node.children() {
229        let kind = child_node.kind();
230        match kind {
231            SyntaxKind::VerbPath => {
232                current_verb = Some((convert_verb_path(&child_node), text_range(&child_node)));
233            }
234            SyntaxKind::VerbSimple => {
235                current_verb = Some((convert_verb_simple(&child_node), text_range(&child_node)));
236            }
237            SyntaxKind::ObjectListPath => {
238                if let Some((pred, pred_range)) = current_verb.take() {
239                    let obj_range = text_range(&child_node);
240                    let range = pred_range.start..obj_range.end;
241                    let objects = convert_object_list_path(&child_node);
242                    result.push(Spanned(
243                        PO {
244                            predicate: Spanned(pred, pred_range),
245                            object: objects,
246                        },
247                        range,
248                    ));
249                }
250            }
251            SyntaxKind::ObjectList => {
252                if let Some((pred, pred_range)) = current_verb.take() {
253                    let obj_range = text_range(&child_node);
254                    let range = pred_range.start..obj_range.end;
255                    let objects = convert_object_list(&child_node);
256                    result.push(Spanned(
257                        PO {
258                            predicate: Spanned(pred, pred_range),
259                            object: objects,
260                        },
261                        range,
262                    ));
263                }
264            }
265            _ => {}
266        }
267    }
268
269    result
270}
271
272// ── verbs ────────────────────────────────────────────────────────────────────
273
274fn convert_verb(node: &Node) -> Term {
275    // Verb ::= VarOrIri | 'a'
276    if let Some(voi) = child(node, SyntaxKind::VarOrIri) {
277        convert_var_or_iri(&voi)
278    } else if let Some(alit) = child(node, SyntaxKind::Alit) {
279        Term::NamedNode(NamedNode::A(alit.text_range().start().into()))
280    } else {
281        Term::Invalid
282    }
283}
284
285fn convert_verb_path(node: &Node) -> Term {
286    // VerbPath ::= Path
287    // Walk: Path → PathAlternative → PathSequence → PathEltOrInverse → PathElt → PathPrimary
288    child(node, SyntaxKind::Path)
289        .and_then(|p| child(&p, SyntaxKind::PathAlternative))
290        .and_then(|pa| child(&pa, SyntaxKind::PathSequence))
291        .and_then(|ps| child(&ps, SyntaxKind::PathEltOrInverse))
292        .and_then(|peoi| child(&peoi, SyntaxKind::PathElt))
293        .and_then(|pe| child(&pe, SyntaxKind::PathPrimary))
294        .map(|pp| convert_path_primary(&pp))
295        .unwrap_or(Term::Invalid)
296}
297
298fn convert_path_primary(node: &Node) -> Term {
299    // PathPrimary ::= iri | 'a' | '!' PathNegatedPropertySet | '(' Path ')'
300    if let Some(iri) = child(node, SyntaxKind::Iri) {
301        Term::NamedNode(convert_iri(&iri))
302    } else if let Some(alit) = child(node, SyntaxKind::Alit) {
303        Term::NamedNode(NamedNode::A(alit.text_range().start().into()))
304    } else {
305        Term::Invalid
306    }
307}
308
309fn convert_verb_simple(node: &Node) -> Term {
310    // VerbSimple ::= Var
311    child(node, SyntaxKind::Var)
312        .map(|v| convert_var(&v))
313        .unwrap_or(Term::Invalid)
314}
315
316// ── object lists ─────────────────────────────────────────────────────────────
317
318fn convert_object_list(node: &Node) -> Vec<Spanned<Term>> {
319    // ObjectList ::= Object (',' Object)*
320    // Object ::= GraphNode
321    children(node, SyntaxKind::Object)
322        .map(|o| {
323            let range = text_range(&o);
324            Spanned(convert_object(&o), range)
325        })
326        .collect()
327}
328
329fn convert_object(node: &Node) -> Term {
330    // Object ::= GraphNode
331    child(node, SyntaxKind::GraphNode)
332        .map(|gn| convert_graph_node(&gn))
333        .unwrap_or(Term::Invalid)
334}
335
336fn convert_object_list_path(node: &Node) -> Vec<Spanned<Term>> {
337    // ObjectListPath ::= ObjectPath (',' ObjectPath)*
338    children(node, SyntaxKind::ObjectPath)
339        .map(|op| {
340            let range = text_range(&op);
341            Spanned(convert_object_path(&op), range)
342        })
343        .collect()
344}
345
346fn convert_object_path(node: &Node) -> Term {
347    // ObjectPath ::= GraphNodePath
348    child(node, SyntaxKind::GraphNodePath)
349        .map(|gnp| convert_graph_node_path(&gnp))
350        .unwrap_or(Term::Invalid)
351}
352
353// ── graph nodes ──────────────────────────────────────────────────────────────
354
355fn convert_graph_node(node: &Node) -> Term {
356    // GraphNode ::= VarOrTerm | TriplesNode
357    if let Some(vot) = child(node, SyntaxKind::VarOrTerm) {
358        convert_var_or_term(&vot)
359    } else if let Some(tn) = child(node, SyntaxKind::TriplesNode) {
360        convert_triples_node(&tn)
361    } else {
362        Term::Invalid
363    }
364}
365
366fn convert_graph_node_path(node: &Node) -> Term {
367    // GraphNodePath ::= VarOrTerm | TriplesNodePath
368    if let Some(vot) = child(node, SyntaxKind::VarOrTerm) {
369        convert_var_or_term(&vot)
370    } else if let Some(tnp) = child(node, SyntaxKind::TriplesNodePath) {
371        convert_triples_node_path(&tnp)
372    } else {
373        Term::Invalid
374    }
375}
376
377// ── terms ────────────────────────────────────────────────────────────────────
378
379fn convert_var_or_term(node: &Node) -> Term {
380    // VarOrTerm ::= Var | GraphTerm
381    if let Some(var) = child(node, SyntaxKind::Var) {
382        convert_var(&var)
383    } else if let Some(gt) = child(node, SyntaxKind::GraphTerm) {
384        convert_graph_term(&gt)
385    } else {
386        Term::Invalid
387    }
388}
389
390fn convert_var_or_iri(node: &Node) -> Term {
391    // VarOrIri ::= Var | iri
392    if let Some(var) = child(node, SyntaxKind::Var) {
393        convert_var(&var)
394    } else if let Some(iri) = child(node, SyntaxKind::Iri) {
395        Term::NamedNode(convert_iri(&iri))
396    } else {
397        Term::Invalid
398    }
399}
400
401fn convert_var(node: &Node) -> Term {
402    // Var ::= VAR1 | VAR2
403    let text = child(node, SyntaxKind::Var1)
404        .or_else(|| child(node, SyntaxKind::Var2))
405        .map(|n| terminal_text(&n));
406
407    if let Some(text) = text {
408        let offset: usize = node.text_range().start().into();
409        let name = text
410            .strip_prefix('?')
411            .or_else(|| text.strip_prefix('$'))
412            .unwrap_or(&text);
413        Term::Variable(Variable(name.to_string(), offset))
414    } else {
415        Term::Invalid
416    }
417}
418
419fn convert_graph_term(node: &Node) -> Term {
420    // GraphTerm ::= iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | NIL
421    if let Some(iri) = child(node, SyntaxKind::Iri) {
422        Term::NamedNode(convert_iri(&iri))
423    } else if let Some(rdf) = child(node, SyntaxKind::Rdfliteral) {
424        Term::Literal(Literal::RDF(convert_rdf_literal(&rdf)))
425    } else if let Some(num) = child(node, SyntaxKind::NumericLiteral) {
426        Term::Literal(convert_numeric_literal(&num))
427    } else if let Some(b) = child(node, SyntaxKind::BooleanLiteral) {
428        Term::Literal(Literal::Boolean(b.text().to_string().trim() == "true"))
429    } else if let Some(bn) = child(node, SyntaxKind::BlankNode) {
430        Term::BlankNode(convert_blank_node(&bn))
431    } else if child(node, SyntaxKind::Nil).is_some() {
432        Term::Collection(Vec::new())
433    } else {
434        Term::Invalid
435    }
436}
437
438// ── compound nodes ───────────────────────────────────────────────────────────
439
440fn convert_triples_node(node: &Node) -> Term {
441    // TriplesNode ::= Collection | BlankNodePropertyList
442    if let Some(coll) = child(node, SyntaxKind::Collection) {
443        Term::Collection(convert_collection(&coll))
444    } else if let Some(bpl) = child(node, SyntaxKind::BlankNodePropertyList) {
445        convert_blank_node_property_list(&bpl)
446    } else {
447        Term::Invalid
448    }
449}
450
451fn convert_triples_node_path(node: &Node) -> Term {
452    // TriplesNodePath ::= CollectionPath | BlankNodePropertyListPath
453    if let Some(coll) = child(node, SyntaxKind::CollectionPath) {
454        Term::Collection(convert_collection_path(&coll))
455    } else if let Some(bplp) = child(node, SyntaxKind::BlankNodePropertyListPath) {
456        convert_blank_node_property_list_path(&bplp)
457    } else {
458        Term::Invalid
459    }
460}
461
462fn convert_collection(node: &Node) -> Vec<Spanned<Term>> {
463    // Collection ::= '(' GraphNode+ ')'
464    children(node, SyntaxKind::GraphNode)
465        .map(|gn| {
466            let range = text_range(&gn);
467            Spanned(convert_graph_node(&gn), range)
468        })
469        .collect()
470}
471
472fn convert_collection_path(node: &Node) -> Vec<Spanned<Term>> {
473    // CollectionPath ::= '(' GraphNodePath+ ')'
474    children(node, SyntaxKind::GraphNodePath)
475        .map(|gnp| {
476            let range = text_range(&gnp);
477            Spanned(convert_graph_node_path(&gnp), range)
478        })
479        .collect()
480}
481
482fn convert_blank_node_property_list(node: &Node) -> Term {
483    // BlankNodePropertyList ::= '[' PropertyListNotEmpty ']'
484    let offset: usize = node.text_range().start().into();
485    let end: usize = node.text_range().end().into();
486    let po = child(node, SyntaxKind::PropertyListNotEmpty)
487        .map(|n| convert_property_list_not_empty(&n))
488        .unwrap_or_default();
489    Term::BlankNode(BlankNode::Unnamed(po, offset, end))
490}
491
492fn convert_blank_node_property_list_path(node: &Node) -> Term {
493    // BlankNodePropertyListPath ::= '[' PropertyListPathNotEmpty ']'
494    let offset: usize = node.text_range().start().into();
495    let end: usize = node.text_range().end().into();
496    let po = child(node, SyntaxKind::PropertyListPathNotEmpty)
497        .map(|n| convert_property_list_path_not_empty(&n))
498        .unwrap_or_default();
499    Term::BlankNode(BlankNode::Unnamed(po, offset, end))
500}
501
502// ── atomic terms ─────────────────────────────────────────────────────────────
503
504fn convert_iri(node: &Node) -> NamedNode {
505    if let Some(iriref_node) = child(node, SyntaxKind::Iriref) {
506        iri_from_iriref_node(&iriref_node)
507    } else if let Some(pn) = child(node, SyntaxKind::PrefixedName) {
508        convert_prefixed_name(&pn)
509    } else {
510        NamedNode::Invalid
511    }
512}
513
514fn iri_from_iriref_node(node: &Node) -> NamedNode {
515    let text = terminal_text(node);
516    let offset: usize = node.text_range().start().into();
517    let inner = text.trim_start_matches('<').trim_end_matches('>');
518    NamedNode::Full(inner.to_string(), offset)
519}
520
521fn convert_prefixed_name(node: &Node) -> NamedNode {
522    if let Some(pname_ln) = child(node, SyntaxKind::PnameLn) {
523        let text = terminal_text(&pname_ln);
524        let offset: usize = pname_ln.text_range().start().into();
525        let (prefix, value) = text.split_once(':').unwrap_or((&text, ""));
526        NamedNode::Prefixed {
527            prefix: prefix.to_string(),
528            value: value.to_string(),
529            idx: offset,
530            computed: None,
531        }
532    } else if let Some(pname_ns) = child(node, SyntaxKind::PnameNs) {
533        let text = terminal_text(&pname_ns);
534        let offset: usize = pname_ns.text_range().start().into();
535        let prefix = text.trim_end_matches(':');
536        NamedNode::Prefixed {
537            prefix: prefix.to_string(),
538            value: String::new(),
539            idx: offset,
540            computed: None,
541        }
542    } else {
543        NamedNode::Invalid
544    }
545}
546
547fn convert_blank_node(node: &Node) -> BlankNode {
548    if let Some(label) = child(node, SyntaxKind::BlankNodeLabel) {
549        let text = terminal_text(&label);
550        let offset: usize = label.text_range().start().into();
551        let name = text.strip_prefix("_:").unwrap_or(&text);
552        BlankNode::Named(name.to_string(), offset)
553    } else {
554        let offset: usize = node.text_range().start().into();
555        BlankNode::Unnamed(Vec::new(), offset, offset)
556    }
557}
558
559fn convert_numeric_literal(node: &Node) -> Literal {
560    // NumericLiteral ::= NumericLiteralUnsigned | NumericLiteralPositive | NumericLiteralNegative
561    Literal::Numeric(node.text().to_string().trim().to_string())
562}
563
564fn convert_rdf_literal(node: &Node) -> RDFLiteral {
565    // RDFLiteral ::= String (LANGTAG | '^^' iri)?
566    let offset: usize = node.text_range().start().into();
567
568    let (value, quote_style, len) = if let Some(str_node) = child(node, SyntaxKind::MyString) {
569        let string_node = str_node.children().next();
570        if let Some(sn) = string_node {
571            let text = terminal_text(&sn);
572            let (inner, style) = strip_string_delimiters(&text);
573            (inner.to_string(), style, 1)
574        } else {
575            (String::new(), StringStyle::Double, 0)
576        }
577    } else {
578        (String::new(), StringStyle::Double, 0)
579    };
580
581    let lang = child(node, SyntaxKind::Langtag).map(|n| {
582        let span = text_range(&n);
583        let text = terminal_text(&n);
584        let value = text.strip_prefix('@').unwrap_or(&text).to_string();
585        Spanned(value, span)
586    });
587
588    let ty = child(node, SyntaxKind::Iri).map(|n| Spanned(convert_iri(&n), text_range(&n)));
589
590    RDFLiteral {
591        value,
592        quote_style,
593        lang,
594        ty,
595        idx: offset,
596        len,
597    }
598}
599
600fn strip_string_delimiters(text: &str) -> (&str, StringStyle) {
601    if let Some(inner) = text
602        .strip_prefix("\"\"\"")
603        .and_then(|s| s.strip_suffix("\"\"\""))
604    {
605        (inner, StringStyle::DoubleLong)
606    } else if let Some(inner) = text.strip_prefix("'''").and_then(|s| s.strip_suffix("'''")) {
607        (inner, StringStyle::SingleLong)
608    } else if let Some(inner) = text.strip_prefix('"').and_then(|s| s.strip_suffix('"')) {
609        (inner, StringStyle::Double)
610    } else if let Some(inner) = text.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
611        (inner, StringStyle::Single)
612    } else {
613        (text, StringStyle::Double)
614    }
615}
616
617#[cfg(test)]
618mod tests {
619    use super::*;
620    use crate::{parse as crate_parse, sparql::parser as lang};
621
622    fn parse(input: &str) -> Turtle {
623        let (result, _) = crate_parse(lang::Rule::new(lang::SyntaxKind::QueryUnit), input);
624        let root = result.syntax::<lang::Lang>();
625        convert(&root)
626    }
627
628    fn parse_update(input: &str) -> Turtle {
629        let (result, _) = crate_parse(lang::Rule::new(lang::SyntaxKind::UpdateUnit), input);
630        let root = result.syntax::<lang::Lang>();
631        convert(&root)
632    }
633
634    // ── helpers ──────────────────────────────────────────────────────────────
635
636    fn prefixed(prefix: &str, value: &str) -> NamedNode {
637        NamedNode::Prefixed {
638            prefix: prefix.to_string(),
639            value: value.to_string(),
640            idx: 0,
641            computed: None,
642        }
643    }
644
645    fn nn_eq(a: &NamedNode, b: &NamedNode) -> bool {
646        match (a, b) {
647            (NamedNode::Full(s1, _), NamedNode::Full(s2, _)) => s1 == s2,
648            (
649                NamedNode::Prefixed {
650                    prefix: p1,
651                    value: v1,
652                    ..
653                },
654                NamedNode::Prefixed {
655                    prefix: p2,
656                    value: v2,
657                    ..
658                },
659            ) => p1 == p2 && v1 == v2,
660            (NamedNode::A(_), NamedNode::A(_)) => true,
661            (NamedNode::Invalid, NamedNode::Invalid) => true,
662            _ => false,
663        }
664    }
665
666    fn term_nn(t: &Term) -> &NamedNode {
667        match t {
668            Term::NamedNode(nn) => nn,
669            other => panic!("expected NamedNode, got {:?}", other),
670        }
671    }
672
673    fn term_var(t: &Term) -> &str {
674        match t {
675            Term::Variable(Variable(name, _)) => name.as_str(),
676            other => panic!("expected Variable, got {:?}", other),
677        }
678    }
679
680    fn term_lit(t: &Term) -> &Literal {
681        match t {
682            Term::Literal(l) => l,
683            other => panic!("expected Literal, got {:?}", other),
684        }
685    }
686
687    fn term_bn(t: &Term) -> &BlankNode {
688        match t {
689            Term::BlankNode(bn) => bn,
690            other => panic!("expected BlankNode, got {:?}", other),
691        }
692    }
693
694    // ── prologue ─────────────────────────────────────────────────────────────
695
696    #[test]
697    fn test_prefix_decl() {
698        let doc = parse("PREFIX ex: <http://example.org/> SELECT * WHERE { ?s ?p ?o }");
699        assert_eq!(doc.prefixes.len(), 1);
700        assert_eq!(doc.prefixes[0].prefix.value(), "ex");
701        assert!(nn_eq(
702            doc.prefixes[0].value.value(),
703            &NamedNode::Full("http://example.org/".to_string(), 0)
704        ));
705    }
706
707    #[test]
708    fn test_base_decl() {
709        let doc = parse("BASE <http://example.org/> SELECT * WHERE { ?s ?p ?o }");
710        let base = doc.base.as_ref().expect("base should be present");
711        assert!(nn_eq(
712            base.value().1.value(),
713            &NamedNode::Full("http://example.org/".to_string(), 0)
714        ));
715    }
716
717    #[test]
718    fn test_multiple_prefixes() {
719        let doc = parse(
720            "PREFIX ex: <http://example.org/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT * WHERE { ?s ?p ?o }",
721        );
722        assert_eq!(doc.prefixes.len(), 2);
723        assert_eq!(doc.prefixes[0].prefix.value(), "ex");
724        assert_eq!(doc.prefixes[1].prefix.value(), "foaf");
725    }
726
727    // ── basic triple pattern with variables ──────────────────────────────────
728
729    #[test]
730    fn test_select_basic_triple_pattern() {
731        let doc = parse("SELECT * WHERE { ?s ?p ?o }");
732        assert_eq!(doc.triples.len(), 1);
733        let t = doc.triples[0].value();
734        assert_eq!(term_var(t.subject.value()), "s");
735        assert_eq!(term_var(t.po[0].predicate.value()), "p");
736        assert_eq!(term_var(t.po[0].object[0].value()), "o");
737    }
738
739    // ── triple pattern with IRIs ─────────────────────────────────────────────
740
741    #[test]
742    fn test_triple_with_iris() {
743        let doc =
744            parse("PREFIX ex: <http://example.org/> SELECT * WHERE { ex:alice ex:knows ex:bob }");
745        assert_eq!(doc.triples.len(), 1);
746        let t = doc.triples[0].value();
747        assert!(nn_eq(term_nn(t.subject.value()), &prefixed("ex", "alice")));
748        assert!(nn_eq(
749            term_nn(t.po[0].predicate.value()),
750            &prefixed("ex", "knows")
751        ));
752        assert!(nn_eq(
753            term_nn(t.po[0].object[0].value()),
754            &prefixed("ex", "bob")
755        ));
756    }
757
758    // ── `a` shorthand ────────────────────────────────────────────────────────
759
760    #[test]
761    fn test_a_shorthand() {
762        let doc =
763            parse("PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT * WHERE { ?x a foaf:Person }");
764        let t = doc.triples[0].value();
765        assert!(matches!(
766            t.po[0].predicate.value(),
767            Term::NamedNode(NamedNode::A(_))
768        ));
769    }
770
771    // ── multiple triple patterns ─────────────────────────────────────────────
772
773    #[test]
774    fn test_multiple_triple_patterns() {
775        let doc = parse("SELECT * WHERE { ?s ?p ?o . ?a ?b ?c }");
776        assert_eq!(doc.triples.len(), 2);
777        assert_eq!(term_var(doc.triples[0].subject.value()), "s");
778        assert_eq!(term_var(doc.triples[1].subject.value()), "a");
779    }
780
781    // ── semicolon for multiple predicates ────────────────────────────────────
782
783    #[test]
784    fn test_semicolon_multiple_po() {
785        let doc = parse(
786            "PREFIX ex: <http://example.org/> SELECT * WHERE { ?x ex:name ?name ; ex:age ?age }",
787        );
788        let t = doc.triples[0].value();
789        assert_eq!(t.po.len(), 2);
790        assert!(nn_eq(
791            term_nn(t.po[0].predicate.value()),
792            &prefixed("ex", "name")
793        ));
794        assert!(nn_eq(
795            term_nn(t.po[1].predicate.value()),
796            &prefixed("ex", "age")
797        ));
798    }
799
800    // ── comma for multiple objects ───────────────────────────────────────────
801
802    #[test]
803    fn test_comma_multiple_objects() {
804        let doc = parse(
805            "PREFIX ex: <http://example.org/> SELECT * WHERE { ?x ex:knows ex:alice , ex:bob }",
806        );
807        let t = doc.triples[0].value();
808        assert_eq!(t.po[0].object.len(), 2);
809    }
810
811    // ── literals ─────────────────────────────────────────────────────────────
812
813    #[test]
814    fn test_string_literal() {
815        let doc = parse("PREFIX ex: <http://example.org/> SELECT * WHERE { ?x ex:name \"Alice\" }");
816        let t = doc.triples[0].value();
817        assert_eq!(term_lit(t.po[0].object[0].value()).plain_string(), "Alice");
818    }
819
820    #[test]
821    fn test_numeric_literal() {
822        let doc = parse("PREFIX ex: <http://example.org/> SELECT * WHERE { ?x ex:age 30 }");
823        let t = doc.triples[0].value();
824        match term_lit(t.po[0].object[0].value()) {
825            Literal::Numeric(n) => assert_eq!(n, "30"),
826            other => panic!("expected Numeric, got {:?}", other),
827        }
828    }
829
830    #[test]
831    fn test_boolean_literal() {
832        let doc = parse("PREFIX ex: <http://example.org/> SELECT * WHERE { ?x ex:active true }");
833        let t = doc.triples[0].value();
834        assert_eq!(*term_lit(t.po[0].object[0].value()), Literal::Boolean(true));
835    }
836
837    // ── blank nodes ──────────────────────────────────────────────────────────
838
839    #[test]
840    fn test_blank_node_property_list() {
841        let doc = parse(
842            "PREFIX ex: <http://example.org/> SELECT * WHERE { ?x ex:knows [ ex:name \"Bob\" ] }",
843        );
844        let t = doc.triples[0].value();
845        match term_bn(t.po[0].object[0].value()) {
846            BlankNode::Unnamed(pos, _, _) => {
847                assert_eq!(pos.len(), 1);
848                assert!(nn_eq(
849                    term_nn(pos[0].predicate.value()),
850                    &prefixed("ex", "name")
851                ));
852            }
853            other => panic!("expected Unnamed blank node, got {:?}", other),
854        }
855    }
856
857    // ── CONSTRUCT queries ────────────────────────────────────────────────────
858
859    #[test]
860    fn test_construct_template_triples() {
861        let doc = parse(
862            "PREFIX ex: <http://example.org/> CONSTRUCT { ?s ex:knows ?o } WHERE { ?s ex:knows ?o }",
863        );
864        // Both the CONSTRUCT template and WHERE clause produce triples
865        assert!(doc.triples.len() >= 2);
866    }
867
868    // ── OPTIONAL / UNION (triples in nested patterns) ────────────────────────
869
870    #[test]
871    fn test_optional_triples_collected() {
872        let doc = parse(
873            "PREFIX ex: <http://example.org/> SELECT * WHERE { ?x a ex:Person . OPTIONAL { ?x ex:name ?name } }",
874        );
875        // Main triple + OPTIONAL triple
876        assert_eq!(doc.triples.len(), 2);
877    }
878
879    #[test]
880    fn test_union_triples_collected() {
881        let doc = parse(
882            "PREFIX ex: <http://example.org/> SELECT * WHERE { { ?x ex:name ?n } UNION { ?x ex:label ?n } }",
883        );
884        assert_eq!(doc.triples.len(), 2);
885    }
886
887    // ── ASK query ────────────────────────────────────────────────────────────
888
889    #[test]
890    fn test_ask_query() {
891        let doc = parse("PREFIX ex: <http://example.org/> ASK { ex:alice ex:knows ex:bob }");
892        assert_eq!(doc.triples.len(), 1);
893    }
894
895    // ── mixed variables and IRIs ─────────────────────────────────────────────
896
897    #[test]
898    fn test_mixed_variables_and_iris() {
899        let doc =
900            parse("PREFIX ex: <http://example.org/> SELECT ?name WHERE { ex:alice ex:name ?name }");
901        assert_eq!(doc.triples.len(), 1);
902        let t = doc.triples[0].value();
903        assert!(nn_eq(term_nn(t.subject.value()), &prefixed("ex", "alice")));
904        assert_eq!(term_var(t.po[0].object[0].value()), "name");
905    }
906
907    // ── UPDATE queries ───────────────────────────────────────────────────────
908
909    #[test]
910    fn test_insert_data() {
911        let doc = parse_update(
912            "PREFIX ex: <http://example.org/> INSERT DATA { ex:alice ex:knows ex:bob }",
913        );
914        assert_eq!(doc.triples.len(), 1);
915        let t = doc.triples[0].value();
916        assert!(nn_eq(term_nn(t.subject.value()), &prefixed("ex", "alice")));
917    }
918
919    // ── incremental parsing ───────────────────────────────────────────────────
920
921    use crate::{IncrementalBias, PrevParseInfo, TokenTrait, parse_incremental};
922
923    fn prev_info(text: &str) -> PrevParseInfo {
924        let (_, tokens) = crate_parse(lang::Rule::new(lang::SyntaxKind::QueryUnit), text);
925        let mut depth: i32 = 0;
926        PrevParseInfo {
927            tokens: tokens
928                .iter()
929                .map(|t| {
930                    let d = depth.clamp(0, 255) as u8;
931                    depth += t.kind.bracket_delta() as i32;
932                    t.to_prev_token(d)
933                })
934                .collect(),
935            had_errors: false,
936        }
937    }
938
939    /// Moving FILTER out of the top-level WHERE and into an OPTIONAL block.
940    ///
941    /// Before:
942    /// ```sparql
943    /// SELECT * WHERE {
944    ///   OPTIONAL { ?person foaf:age ?age . }
945    ///   FILTER(?name != "")
946    /// }
947    /// ```
948    ///
949    /// After:
950    /// ```sparql
951    /// SELECT * WHERE {
952    ///   OPTIONAL { ?person foaf:age ?age .
953    ///     FILTER(?name != "")
954    ///   }
955    /// }
956    /// ```
957    ///
958    /// The after form is valid SPARQL. The test verifies the incremental parser
959    /// does not get stuck preferring the old parse (where FILTER was a sibling
960    /// of OPTIONAL at the top level) when the closing `}` of OPTIONAL now
961    /// appears after FILTER.
962    #[test]
963    fn test_suggest_filter_moved_into_optional() {
964        let before =
965            "SELECT * WHERE {\n  OPTIONAL { ?person foaf:age ?age . }\n  FILTER(?name != \"\")\n}";
966        let after = "SELECT * WHERE {\n  OPTIONAL { ?person foaf:age ?age .\n    FILTER(?name != \"\")\n  }\n}";
967
968        let prev = prev_info(before);
969        let bias = IncrementalBias::default();
970        let (parse, _) = parse_incremental(
971            lang::Rule::new(lang::SyntaxKind::QueryUnit),
972            after,
973            Some(&prev),
974            bias,
975        );
976
977        let errors: Vec<_> = parse.errors.iter().collect();
978        assert_eq!(
979            errors.len(),
980            0,
981            "valid SPARQL should parse without errors; got: {:?}",
982            errors
983        );
984
985        let root = parse.syntax::<lang::Lang>();
986        let doc = convert(&root);
987        assert_eq!(
988            doc.triples.len(),
989            1,
990            "should produce one triple (?person foaf:age ?age)"
991        );
992    }
993}
994
995#[cfg(test)]
996mod demo_sim_tests {
997    use crate::sparql::parser as lang;
998    use crate::{IncrementalBias, parse_incremental};
999
1000    // Simulate demo flow: use parse_incremental's returned prev (not crate_parse)
1001    #[test]
1002    fn test_suggest_filter_moved_demo_flow() {
1003        let before =
1004            "SELECT * WHERE {\n  OPTIONAL { ?person foaf:age ?age . }\n  FILTER(?name != \"\")\n}";
1005        let after = "SELECT * WHERE {\n  OPTIONAL { ?person foaf:age ?age .\n    FILTER(?name != \"\")\n  }\n}";
1006
1007        // First parse: no prev (as in demo on initial load)
1008        let (_, prev) = parse_incremental(
1009            lang::Rule::new(lang::SyntaxKind::QueryUnit),
1010            before,
1011            None,
1012            IncrementalBias::default(),
1013        );
1014
1015        // Second parse: use the prev from the first parse (as in demo on edit)
1016        let (parse, _) = parse_incremental(
1017            lang::Rule::new(lang::SyntaxKind::QueryUnit),
1018            after,
1019            Some(&prev),
1020            IncrementalBias::default(),
1021        );
1022
1023        let errors: Vec<_> = parse.errors.iter().collect();
1024        assert_eq!(
1025            errors.len(),
1026            0,
1027            "demo flow: valid SPARQL should parse without errors; got: {:?}",
1028            errors
1029        );
1030    }
1031}
1032
1033#[cfg(test)]
1034mod real_demo_tests {
1035    use crate::sparql::parser as lang;
1036    use crate::{IncrementalBias, parse_incremental};
1037
1038    #[test]
1039    fn test_suggest_filter_moved_real_demo() {
1040        let before = r#"
1041PREFIX ex: <http://example.org/>
1042PREFIX foaf: <http://xmlns.com/foaf/0.1/>
1043
1044SELECT ?person ?name ?age
1045WHERE {
1046  ?person a foaf:Person ;
1047          foaf:name ?name .
1048  OPTIONAL { ?person foaf:age ?age . }
1049  FILTER(?name != "")
1050}
1051ORDER BY ?name"#;
1052
1053        let after_1 = r#"
1054PREFIX ex: <http://example.org/>
1055PREFIX foaf: <http://xmlns.com/foaf/0.1/>
1056
1057SELECT ?person ?name ?age
1058WHERE {
1059  ?person a foaf:Person ;
1060          foaf:name ?name .
1061  OPTIONAL { ?person foaf:age ?age . 
1062  FILTER(?name != "")
1063}
1064ORDER BY ?name"#;
1065
1066        let after_2 = r#"
1067PREFIX ex: <http://example.org/>
1068PREFIX foaf: <http://xmlns.com/foaf/0.1/>
1069
1070SELECT ?person ?name ?age
1071WHERE {
1072  ?person a foaf:Person ;
1073          foaf:name ?name .
1074  OPTIONAL { ?person foaf:age ?age . 
1075  FILTER(?name != "")}
1076}
1077ORDER BY ?name"#;
1078
1079        // Simulate demo: first parse with no prev, then incremental
1080        let (_, prev) = parse_incremental(
1081            lang::Rule::new(lang::SyntaxKind::QueryUnit),
1082            before,
1083            None,
1084            IncrementalBias::default(),
1085        );
1086
1087        let (_, prev) = parse_incremental(
1088            lang::Rule::new(lang::SyntaxKind::QueryUnit),
1089            after_1,
1090            Some(&prev),
1091            IncrementalBias::default(),
1092        );
1093        let (parse, _) = parse_incremental(
1094            lang::Rule::new(lang::SyntaxKind::QueryUnit),
1095            after_2,
1096            Some(&prev),
1097            IncrementalBias::default(),
1098        );
1099
1100        let errors: Vec<_> = parse.errors.iter().collect();
1101        assert_eq!(
1102            errors.len(),
1103            0,
1104            "real demo: valid SPARQL should parse without errors; got: {:?}",
1105            errors
1106        );
1107    }
1108}
1109
1110#[cfg(test)]
1111mod multi_step_tests {
1112    use crate::sparql::parser as lang;
1113    use crate::{IncrementalBias, parse_incremental};
1114
1115    #[test]
1116    fn test_filter_move_via_intermediate_delete() {
1117        // Step 1: the valid "before" state
1118        let step1 = "\
1119PREFIX ex: <http://example.org/>
1120PREFIX foaf: <http://xmlns.com/foaf/0.1/>
1121
1122SELECT ?person ?name ?age
1123WHERE {
1124  ?person a foaf:Person ;
1125          foaf:name ?name .
1126  OPTIONAL { ?person foaf:age ?age . }
1127  FILTER(?name != \"\")
1128}
1129ORDER BY ?name";
1130
1131        // Step 2: user removes the OPTIONAL closing `}` (invalid intermediate)
1132        let step2 = "\
1133PREFIX ex: <http://example.org/>
1134PREFIX foaf: <http://xmlns.com/foaf/0.1/>
1135
1136SELECT ?person ?name ?age
1137WHERE {
1138  ?person a foaf:Person ;
1139          foaf:name ?name .
1140  OPTIONAL { ?person foaf:age ?age . 
1141  FILTER(?name != \"\")
1142}
1143ORDER BY ?name";
1144
1145        // Step 3: user places `}` after FILTER (valid final state)
1146        let step3 = "\
1147PREFIX ex: <http://example.org/>
1148PREFIX foaf: <http://xmlns.com/foaf/0.1/>
1149
1150SELECT ?person ?name ?age
1151WHERE {
1152  ?person a foaf:Person ;
1153          foaf:name ?name .
1154  OPTIONAL { ?person foaf:age ?age . 
1155  FILTER(?name != \"\")}
1156}
1157ORDER BY ?name";
1158
1159        let bias = IncrementalBias::default();
1160        let rule = || lang::Rule::new(lang::SyntaxKind::QueryUnit);
1161
1162        let (parse1, prev1) = parse_incremental(rule(), step1, None, bias);
1163        eprintln!("Step 1 errors: {:?}", parse1.errors);
1164
1165        let (parse2, prev2) = parse_incremental(rule(), step2, Some(&prev1), bias);
1166        eprintln!("Step 2 errors: {:?}", parse2.errors);
1167
1168        // Show what depths the intermediate parse assigned to FILTER tokens
1169        for tok in &prev2.tokens {
1170            if tok.text.contains("FILTER") || tok.text == "(" {
1171                eprintln!(
1172                    "  token {:?} fp={:?} depth={}",
1173                    tok.text,
1174                    tok.fingerprint.is_some(),
1175                    tok.depth
1176                );
1177            }
1178        }
1179
1180        let (parse3, _) = parse_incremental(rule(), step3, Some(&prev2), bias);
1181        let errors: Vec<_> = parse3.errors.iter().collect();
1182        eprintln!("Step 3 errors: {:?}", errors);
1183        assert_eq!(
1184            errors.len(),
1185            0,
1186            "three-step edit: valid SPARQL should parse without errors; got: {:?}",
1187            errors
1188        );
1189    }
1190}