1use logos::Logos;
4use winnow::{
5 ModalResult, Parser,
6 combinator::{alt, opt, repeat, separated, preceded, terminated, delimited},
7 token::take_while,
8};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13pub struct Iri(pub String);
14
15impl std::fmt::Display for Iri {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 write!(f, "{}", self.0)
18 }
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, Hash)]
23pub struct Literal {
24 pub value: String,
25 pub datatype: Option<Iri>,
26 pub language: Option<String>,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
31pub struct Variable(pub String);
32
33#[derive(Logos, Debug, PartialEq)]
35#[logos(skip r"[ \t\n\f]+")] pub enum Token<'a> {
37 #[token("SELECT")]
38 Select,
39
40 #[token("CONSTRUCT")]
41 Construct,
42
43 #[token("ASK")]
44 Ask,
45
46 #[token("DESCRIBE")]
47 Describe,
48
49 #[token("WHERE")]
50 Where,
51
52 #[token("FILTER")]
53 Filter,
54
55 #[token("OPTIONAL")]
56 Optional,
57
58 #[token("UNION")]
59 Union,
60
61 #[token("GRAPH")]
62 Graph,
63
64 #[token("ORDER")]
65 Order,
66
67 #[token("BY")]
68 By,
69
70 #[token("LIMIT")]
71 Limit,
72
73 #[token("OFFSET")]
74 Offset,
75
76 #[token("DISTINCT")]
77 Distinct,
78
79 #[token("REDUCED")]
80 Reduced,
81
82 #[token("FROM")]
83 From,
84
85 #[token("NAMED")]
86 Named,
87
88 #[regex(r"\?[a-zA-Z_][a-zA-Z0-9_]*")]
89 Variable(&'a str),
90
91 #[regex(r"<[^>]*>")]
92 Iri(&'a str),
93
94 #[regex(r#""([^"\\]|\\.)*""#)]
95 StringLiteral(&'a str),
96
97 #[regex(r"[a-zA-Z_][a-zA-Z0-9_]*:[a-zA-Z_][a-zA-Z0-9_]*")]
98 PrefixedName(&'a str),
99
100 #[regex(r"[0-9]+")]
101 Integer(&'a str),
102
103 #[regex(r"[0-9]*\.[0-9]+")]
104 Decimal(&'a str),
105
106 #[token("{")]
107 LBrace,
108
109 #[token("}")]
110 RBrace,
111
112 #[token("(")]
113 LParen,
114
115 #[token(")")]
116 RParen,
117
118 #[token("[")]
119 LBracket,
120
121 #[token("]")]
122 RBracket,
123
124 #[token(".")]
125 Dot,
126
127 #[token(",")]
128 Comma,
129
130 #[token(";")]
131 Semicolon,
132
133 #[token(":")]
134 Colon,
135
136 #[token("^^")]
137 DoubleCaret,
138
139 #[token("@")]
140 At,
141
142 #[token("a")]
143 A,
144
145 #[token("^")]
146 Caret,
147
148 #[token("/")]
149 Slash,
150
151 #[token("|")]
152 Pipe,
153
154 #[token("*")]
155 Star,
156
157 #[token("+")]
158 Plus,
159
160 #[token("?")]
161 Question,
162
163 #[token("=")]
164 Equals,
165
166 #[token("!=")]
167 NotEquals,
168
169 #[token("<")]
170 LessThan,
171
172 #[token("<=")]
173 LessEqual,
174
175 #[token(">")]
176 GreaterThan,
177
178 #[token(">=")]
179 GreaterEqual,
180
181 #[token("&&")]
182 And,
183
184 #[token("||")]
185 Or,
186
187 #[token("!")]
188 Not,
189
190 #[token("ASC")]
191 Asc,
192
193 #[token("DESC")]
194 Desc,
195
196 #[token("COUNT")]
197 Count,
198
199 #[token("SUM")]
200 Sum,
201
202 #[token("AVG")]
203 Avg,
204
205 #[token("MIN")]
206 Min,
207
208 #[token("MAX")]
209 Max,
210
211 #[token("GROUP")]
212 Group,
213
214 #[token("HAVING")]
215 Having,
216
217 #[token("AS")]
218 As,
219
220 #[token("BASE")]
221 Base,
222
223 #[token("PREFIX")]
224 Prefix,
225}
226
227#[derive(Debug, Clone, PartialEq, Eq, Hash)]
229pub enum Term {
230 Iri(Iri),
231 Literal(Literal),
232 Variable(Variable),
233 BlankNode(String),
234 PrefixedName(String, String), }
236
237#[derive(Debug, Clone, PartialEq)]
239pub struct TriplePattern {
240 pub subject: Term,
241 pub predicate: Term,
242 pub object: Term,
243}
244
245#[derive(Debug, Clone, PartialEq)]
247pub enum PropertyPath {
248 Predicate(Iri),
249 Inverse(Box<PropertyPath>),
250 Sequence(Vec<PropertyPath>),
251 Alternative(Vec<PropertyPath>),
252 ZeroOrMore(Box<PropertyPath>),
253 OneOrMore(Box<PropertyPath>),
254 ZeroOrOne(Box<PropertyPath>),
255}
256
257#[derive(Debug, Clone, PartialEq)]
259pub enum GraphPattern {
260 Bgp(Vec<TriplePattern>),
261 Optional(Box<GraphPattern>),
262 Union(Vec<GraphPattern>),
263 Filter(Expression, Box<GraphPattern>),
264 Graph(VarOrIri, Box<GraphPattern>),
265 Minus(Box<GraphPattern>, Box<GraphPattern>),
266 Service(VarOrIri, Box<GraphPattern>, bool), }
268
269#[derive(Debug, Clone, PartialEq)]
271pub enum Expression {
272 Variable(Variable),
273 Iri(Iri),
274 Literal(Literal),
275 Add(Box<Expression>, Box<Expression>),
277 Subtract(Box<Expression>, Box<Expression>),
278 Multiply(Box<Expression>, Box<Expression>),
279 Divide(Box<Expression>, Box<Expression>),
280 Equal(Box<Expression>, Box<Expression>),
282 NotEqual(Box<Expression>, Box<Expression>),
283 LessThan(Box<Expression>, Box<Expression>),
284 LessThanOrEqual(Box<Expression>, Box<Expression>),
285 GreaterThan(Box<Expression>, Box<Expression>),
286 GreaterThanOrEqual(Box<Expression>, Box<Expression>),
287 And(Box<Expression>, Box<Expression>),
289 Or(Box<Expression>, Box<Expression>),
290 Not(Box<Expression>),
291 Bound(Variable),
293 IsIri(Box<Expression>),
294 IsLiteral(Box<Expression>),
295 IsBlank(Box<Expression>),
296 Str(Box<Expression>),
297 Lang(Box<Expression>),
298 Datatype(Box<Expression>),
299 IriFunc(Box<Expression>), Uri(Box<Expression>),
301 Bnode(Box<Expression>),
302 Regex(Box<Expression>, Box<Expression>, Option<Box<Expression>>),
304 Exists(Box<GraphPattern>),
306 NotExists(Box<GraphPattern>),
307}
308
309#[derive(Debug, Clone, PartialEq)]
311pub enum VarOrIri {
312 Var(Variable),
313 Iri(Iri),
314}
315
316#[derive(Debug, Clone, PartialEq)]
318pub enum OrderCondition {
319 Asc(Expression),
320 Desc(Expression),
321}
322
323#[derive(Debug, Clone, PartialEq)]
325pub struct SolutionModifier {
326 pub group: Option<Vec<Expression>>,
327 pub having: Option<Vec<Expression>>,
328 pub order: Option<Vec<OrderCondition>>,
329 pub limit: Option<u64>,
330 pub offset: Option<u64>,
331 pub distinct: bool,
332 pub reduced: bool,
333}
334
335#[derive(Debug, Clone, PartialEq)]
337pub struct SparqlQuery {
338 pub query_type: QueryType,
339 pub variables: Vec<Variable>,
340 pub dataset: Vec<GraphRef>,
341 pub where_clause: GraphPattern,
342 pub solution_modifier: SolutionModifier,
343 pub values: Option<Vec<Bindings>>,
344 pub base_iri: Option<Iri>,
345 pub prefixes: HashMap<String, Iri>,
346}
347
348#[derive(Debug, Clone, PartialEq)]
350pub enum QueryType {
351 Select,
352 Construct(Vec<TriplePattern>),
353 Ask,
354 Describe(Vec<VarOrIri>),
355}
356
357pub type Bindings = HashMap<Variable, Term>;
359
360#[derive(Debug, Clone, PartialEq)]
362pub enum GraphRef {
363 Named(Iri),
364 Default(Iri),
365}
366
367use crate::SparqlError;
368
369pub trait SparqlParser {
371 fn parse(&self, query: &str) -> Result<SparqlQuery, crate::SparqlError>;
372 fn parse_query(&self, query: &str) -> Result<SparqlQuery, crate::SparqlError>;
373}
374
375pub struct DefaultSparqlParser;
377
378impl DefaultSparqlParser {
379 fn parse_iri(input: &mut &str) -> winnow::ModalResult<Iri> {
381 winnow::combinator::delimited(
382 '<',
383 winnow::token::take_while(1.., |c: char| c != '>'),
384 '>'
385 ).parse_next(input)
386 .map(|s: &str| Iri(s.to_string()))
387 }
388
389 fn parse_variable(input: &mut &str) -> winnow::ModalResult<Variable> {
391 winnow::combinator::preceded(
392 '?',
393 winnow::token::take_while(1.., |c: char| c.is_alphanumeric() || c == '_')
394 ).parse_next(input)
395 .map(|s: &str| Variable(s.to_string()))
396 }
397
398 fn parse_prefixed_name(input: &mut &str) -> winnow::ModalResult<Term> {
400 let prefix = winnow::token::take_while(1.., |c: char| c.is_alphanumeric() || c == '_');
401 let local = winnow::combinator::preceded(
402 ':',
403 winnow::token::take_while(1.., |c: char| c.is_alphanumeric() || c == '_' || c == '-')
404 );
405
406 (prefix, local).parse_next(input)
407 .map(|(p, l): (&str, &str)| Term::PrefixedName(p.to_string(), l.to_string()))
408 }
409
410 fn parse_term(input: &mut &str) -> winnow::ModalResult<Term> {
412 winnow::combinator::alt((
413 Self::parse_variable.map(Term::Variable),
414 Self::parse_iri.map(Term::Iri),
415 Self::parse_prefixed_name,
416 )).parse_next(input)
417 }
418
419 fn parse_triple_pattern(input: &mut &str) -> winnow::ModalResult<TriplePattern> {
421 let subject = Self::parse_term;
422 let predicate = Self::parse_term;
423 let object = Self::parse_term;
424
425 (subject, predicate, object).parse_next(input)
426 .map(|(s, p, o)| TriplePattern { subject: s, predicate: p, object: o })
427 }
428
429 fn parse_select_clause(input: &mut &str) -> winnow::ModalResult<(bool, Vec<Variable>)> {
431 let distinct = winnow::combinator::opt("DISTINCT").map(|d| d.is_some());
432 let star_or_vars = winnow::combinator::alt((
433 "*".map(|_| vec![]), winnow::combinator::repeat(1.., Self::parse_variable)
435 ));
436
437 winnow::combinator::preceded("SELECT", (distinct, star_or_vars)).parse_next(input)
438 }
439
440 fn parse_where_clause(input: &mut &str) -> winnow::ModalResult<GraphPattern> {
442 winnow::combinator::preceded(
443 "WHERE",
444 winnow::combinator::delimited(
445 '{',
446 Self::parse_group_graph_pattern,
447 '}'
448 )
449 ).parse_next(input)
450 }
451
452 fn parse_group_graph_pattern(input: &mut &str) -> winnow::ModalResult<GraphPattern> {
454 let mut triples = winnow::combinator::repeat(
455 0..,
456 winnow::combinator::terminated(
457 Self::parse_triple_pattern,
458 winnow::combinator::opt('.')
459 )
460 );
461
462 triples.parse_next(input).map(|ts| GraphPattern::Bgp(ts))
463 }
464
465 fn parse_prefix_declaration(input: &mut &str) -> winnow::ModalResult<(String, Iri)> {
467 let prefix = winnow::token::take_while(1.., |c: char| c.is_alphanumeric() || c == '_');
468 let iri = winnow::combinator::delimited(
469 '<',
470 winnow::token::take_while(1.., |c: char| c != '>'),
471 '>'
472 );
473
474 winnow::combinator::preceded(
475 "PREFIX",
476 (prefix, ':', iri)
477 ).parse_next(input)
478 .map(|(p, _, i): (&str, char, &str)| (p.to_string(), Iri(i.to_string())))
479 }
480}
481
482impl SparqlParser for DefaultSparqlParser {
483 fn parse(&self, query: &str) -> Result<SparqlQuery, crate::SparqlError> {
484 let mut prefixes = HashMap::new();
486 let mut variables = Vec::new();
487 let mut query_type = QueryType::Select;
488 let mut in_where = false;
489 let mut in_construct = false;
490 let mut triples = Vec::new();
491 let mut construct_triples = Vec::new();
492
493 for line in query.lines() {
494 let line = line.trim();
495 if line.is_empty() {
496 continue;
497 }
498
499 if line.starts_with("PREFIX") {
500 if let Some(prefix_def) = line.strip_prefix("PREFIX") {
502 let parts: Vec<&str> = prefix_def.trim().split_whitespace().collect();
503 if parts.len() >= 2 {
504 let prefix = parts[0].trim_end_matches(':');
505 let iri_str = parts[1].trim_matches('<').trim_matches('>');
506 prefixes.insert(prefix.to_string(), Iri(iri_str.to_string()));
507 }
508 }
509 } else if line.starts_with("SELECT") {
510 if let Some(var_part) = line.strip_prefix("SELECT") {
512 let var_part = var_part.trim();
513 if var_part.starts_with("DISTINCT") {
514 continue;
516 }
517 if var_part == "*" {
518 continue;
520 }
521 for part in var_part.split_whitespace() {
522 if part.starts_with('?') {
523 variables.push(Variable(part[1..].to_string()));
524 }
525 }
526 }
527 } else if line.starts_with("ASK") {
528 query_type = QueryType::Ask;
530 in_where = true;
531 } else if line.starts_with("CONSTRUCT") {
532 query_type = QueryType::Construct(vec![]);
534 in_construct = true;
535 } else if line.starts_with("WHERE") {
536 in_where = true;
537 in_construct = false; } else if in_construct && line.trim().ends_with('.') {
539 let line = line.trim();
541 let line = &line[..line.len()-1]; let parts: Vec<&str> = line.split_whitespace().collect();
543 if parts.len() >= 3 {
544 let subject = if parts[0].starts_with('?') {
545 Term::Variable(Variable(parts[0][1..].to_string()))
546 } else if parts[0].starts_with('<') {
547 Term::Iri(Iri(parts[0].trim_matches('<').trim_matches('>').to_string()))
548 } else {
549 continue; };
551
552 let predicate = if parts[1] == "a" {
553 Term::PrefixedName("rdf".to_string(), "type".to_string())
555 } else if parts[1].starts_with('<') {
556 Term::Iri(Iri(parts[1].trim_matches('<').trim_matches('>').to_string()))
557 } else if parts[1].contains(':') {
558 let colon_parts: Vec<&str> = parts[1].split(':').collect();
559 if colon_parts.len() == 2 {
560 Term::PrefixedName(colon_parts[0].to_string(), colon_parts[1].to_string())
561 } else {
562 continue;
563 }
564 } else {
565 continue;
566 };
567
568 let object = if parts[2].starts_with('?') {
569 Term::Variable(Variable(parts[2][1..].to_string()))
570 } else if parts[2].starts_with('<') {
571 Term::Iri(Iri(parts[2].trim_matches('<').trim_matches('>').to_string()))
572 } else if parts[2].starts_with('"') && parts[2].ends_with('"') {
573 let value = parts[2].trim_matches('"').to_string();
574 Term::Literal(Literal {
575 value,
576 datatype: None,
577 language: None,
578 })
579 } else if parts[2].contains(':') {
580 let colon_parts: Vec<&str> = parts[2].split(':').collect();
581 if colon_parts.len() == 2 {
582 Term::PrefixedName(colon_parts[0].to_string(), colon_parts[1].to_string())
583 } else {
584 continue;
585 }
586 } else {
587 continue;
588 };
589
590 println!("DEBUG: Adding construct triple: subject={:?}, predicate={:?}, object={:?}", subject, predicate, object);
591 construct_triples.push(TriplePattern {
592 subject,
593 predicate,
594 object,
595 });
596 }
597 } else if in_where && line.trim().ends_with('.') {
598 let line = line.trim();
600 let line = &line[..line.len()-1]; let parts: Vec<&str> = line.split_whitespace().collect();
602 if parts.len() >= 3 {
603 let subject = if parts[0].starts_with('?') {
604 Term::Variable(Variable(parts[0][1..].to_string()))
605 } else if parts[0].starts_with('<') {
606 Term::Iri(Iri(parts[0].trim_matches('<').trim_matches('>').to_string()))
607 } else {
608 continue; };
610
611 let predicate = if parts[1] == "a" {
612 Term::PrefixedName("rdf".to_string(), "type".to_string())
614 } else if parts[1].starts_with('<') {
615 Term::Iri(Iri(parts[1].trim_matches('<').trim_matches('>').to_string()))
616 } else if parts[1].contains(':') {
617 let colon_parts: Vec<&str> = parts[1].split(':').collect();
618 if colon_parts.len() == 2 {
619 Term::PrefixedName(colon_parts[0].to_string(), colon_parts[1].to_string())
620 } else {
621 continue;
622 }
623 } else {
624 continue;
625 };
626
627 let object = if parts[2].starts_with('?') {
628 Term::Variable(Variable(parts[2][1..].to_string()))
629 } else if parts[2].starts_with('<') {
630 Term::Iri(Iri(parts[2].trim_matches('<').trim_matches('>').to_string()))
631 } else if parts[2].starts_with('"') && parts[2].ends_with('"') {
632 let value = parts[2].trim_matches('"').to_string();
634 Term::Literal(Literal {
635 value,
636 datatype: None,
637 language: None,
638 })
639 } else if parts[2].contains(':') {
640 let colon_parts: Vec<&str> = parts[2].split(':').collect();
641 if colon_parts.len() == 2 {
642 Term::PrefixedName(colon_parts[0].to_string(), colon_parts[1].to_string())
643 } else {
644 continue;
645 }
646 } else {
647 continue;
648 };
649
650 triples.push(TriplePattern {
651 subject,
652 predicate,
653 object,
654 });
655 }
656 }
657 }
658
659 println!("DEBUG: Parsed query type: {:?}", query_type);
660 println!("DEBUG: Parsed variables: {:?}", variables);
661 println!("DEBUG: Parsed triples: {:?}", triples);
662 println!("DEBUG: Parsed construct triples: {:?}", construct_triples);
663 println!("DEBUG: Parsed prefixes: {:?}", prefixes);
664
665 let final_query_type = match query_type {
667 QueryType::Construct(_) => QueryType::Construct(construct_triples),
668 _ => query_type,
669 };
670
671 Ok(SparqlQuery {
672 query_type: final_query_type,
673 variables,
674 dataset: vec![],
675 where_clause: GraphPattern::Bgp(triples),
676 solution_modifier: SolutionModifier {
677 group: None,
678 having: None,
679 order: None,
680 limit: None,
681 offset: None,
682 distinct: false,
683 reduced: false,
684 },
685 values: None,
686 base_iri: None,
687 prefixes,
688 })
689 }
690
691
692 fn parse_query(&self, query: &str) -> Result<SparqlQuery, crate::SparqlError> {
693 self.parse(query)
694 }
695}