1use {
2 super::join_query::{Predicate, Term},
3 crate::JoinQuery,
4 winnow::{
5 ascii::multispace0,
6 combinator::{delimited, separated},
7 error::{ContextError, ErrMode},
8 token::take_while,
9 Parser,
10 },
11};
12
13type PResult<T> = Result<T, winnow::error::ErrMode<ContextError>>;
14
15fn ws(input: &mut &str) -> PResult<()> {
16 let _: &str = multispace0.parse_next(input)?;
17 Ok(())
18}
19
20fn ident(input: &mut &str) -> PResult<String> {
21 let start = *input;
22 take_while(1.., |c: char| c.is_ascii_alphabetic()).parse_next(input)?;
23 take_while(0.., |c: char| c.is_ascii_alphanumeric() || c == '_').parse_next(input)?;
24 let end = *input;
25 let len = start.len() - end.len();
26 Ok(start[..len].to_string())
27}
28
29fn comma(input: &mut &str) -> PResult<char> { delimited(ws, ',', ws).parse_next(input) }
30
31fn dot(input: &mut &str) -> PResult<char> { delimited(ws, '.', ws).parse_next(input) }
32
33fn term(input: &mut &str) -> PResult<Term> {
35 if input.starts_with('_')
36 && (input.len() == 1 || !input.chars().nth(1).unwrap().is_ascii_alphanumeric())
37 {
38 let _ = '_'.parse_next(input)?;
39 return Ok(Term::Placeholder);
40 }
41
42 let name = ident.parse_next(input)?;
43
44 let is_var = name
45 .chars()
46 .next()
47 .map(|c| c.is_ascii_uppercase())
48 .unwrap_or(false);
49
50 Ok(if is_var {
51 Term::Var(name)
52 } else {
53 Term::Atom(name)
54 })
55}
56
57fn term_list(input: &mut &str) -> PResult<Vec<Term>> {
58 delimited(
59 delimited(ws, '(', ws),
60 separated(1.., term, comma),
61 delimited(ws, ')', ws),
62 )
63 .parse_next(input)
64}
65
66fn predicate(input: &mut &str) -> PResult<Predicate> {
67 ws.parse_next(input)?;
68 let name = ident.parse_next(input)?;
69 let terms = term_list.parse_next(input)?;
70 Ok(Predicate {
71 name,
72 terms,
73 })
74}
75
76fn query(input: &mut &str) -> PResult<JoinQuery> {
77 let head = predicate.parse_next(input)?;
78 let _ = delimited(ws, ":-", ws).parse_next(input)?;
80 let body = separated(1.., predicate, comma).parse_next(input)?;
81 let _ = dot.parse_next(input)?;
82 Ok(JoinQuery {
83 head,
84 body,
85 })
86}
87
88impl std::str::FromStr for JoinQuery {
89 type Err = ErrMode<ContextError>;
90
91 fn from_str(s: &str) -> Result<Self, Self::Err> {
92 let mut input = s;
93 query.parse_next(&mut input)
94 }
95}
96
97#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn test_query_parser() {
105 let mut src = "P(A, C) :- Q(A, B), R(B, C).";
106 match query.parse_next(&mut src) {
107 | Ok(ast) => {
108 println!("{ast:#?}");
109 let _ = ws.parse_next(&mut src);
111 if !src.is_empty() {
112 eprintln!("Unparsed tail: {src:?}");
113 }
114 assert_eq!(ast.head.name, "P");
115 assert_eq!(ast.head.terms.len(), 2);
116 assert_eq!(ast.body.len(), 2);
117 },
118 | Err(e) => panic!("Parse error: {e:?}"),
119 }
120 }
121
122 #[test]
123 fn test_query_from_str_simple() {
124 let query_str = "P(X) :- Q(X).";
125 let query: JoinQuery = query_str.parse().expect("Failed to parse query");
126
127 assert_eq!(query.head.name, "P");
128 assert_eq!(query.head.terms.len(), 1);
129 assert_eq!(query.head.terms[0], Term::Var("X".to_string()));
130
131 assert_eq!(query.body.len(), 1);
132 assert_eq!(query.body[0].name, "Q");
133 assert_eq!(query.body[0].terms.len(), 1);
134 assert_eq!(query.body[0].terms[0], Term::Var("X".to_string()));
135 }
136
137 #[test]
138 fn test_query_from_str_multiple_body_predicates() {
139 let query_str = "ancestor(X, Z) :- parent(X, Y), parent(Y, Z).";
140 let query: JoinQuery = query_str.parse().expect("Failed to parse query");
141
142 assert_eq!(query.head.name, "ancestor");
143 assert_eq!(query.head.terms.len(), 2);
144 assert_eq!(query.head.terms[0], Term::Var("X".to_string()));
145 assert_eq!(query.head.terms[1], Term::Var("Z".to_string()));
146
147 assert_eq!(query.body.len(), 2);
148 assert_eq!(query.body[0].name, "parent");
149 assert_eq!(query.body[1].name, "parent");
150 }
151
152 #[test]
153 fn test_query_from_str_with_atoms() {
154 let query_str = "likes(alice, X):- food(X), healthy(X).";
155 let query: JoinQuery = query_str.parse().expect("Failed to parse query");
156
157 assert_eq!(query.head.name, "likes");
158 assert_eq!(query.head.terms[0], Term::Atom("alice".to_string()));
159 assert_eq!(query.head.terms[1], Term::Var("X".to_string()));
160
161 assert_eq!(query.body.len(), 2);
162 }
163
164 #[test]
165 fn test_query_from_str_with_whitespace() {
166 let query_str = " P(X,Y) :- Q(X),R(Y) . ";
167 let query: JoinQuery = query_str
168 .parse()
169 .expect("Failed to parse query with whitespace");
170
171 assert_eq!(query.head.name, "P");
172 assert_eq!(query.head.terms.len(), 2);
173 assert_eq!(query.body.len(), 2);
174 }
175
176 #[test]
177 fn test_query_from_str_minimal_whitespace() {
178 let query_str = "P(X,Y):-Q(X),R(Y).";
179 let query: JoinQuery = query_str
180 .parse()
181 .expect("Failed to parse query with minimal whitespace");
182
183 assert_eq!(query.head.name, "P");
184 assert_eq!(query.head.terms.len(), 2);
185 assert_eq!(query.body.len(), 2);
186 }
187
188 #[test]
189 fn test_query_from_str_spaces_after_commas() {
190 let query_str = "result(X, Y, Z) :- relation1(X, Y), relation2(Y, Z).";
191 let query: JoinQuery = query_str
192 .parse()
193 .expect("Failed to parse query with spaces after commas");
194
195 assert_eq!(query.head.name, "result");
196 assert_eq!(query.head.terms.len(), 3);
197 assert_eq!(query.body.len(), 2);
198 assert_eq!(query.body[0].terms.len(), 2);
199 assert_eq!(query.body[1].terms.len(), 2);
200 }
201
202 #[test]
203 fn test_query_from_str_complex() {
204 let query_str = "path(X, Z) :- edge(X, Y), edge(Y, Z), vertex(X), vertex(Y), vertex(Z).";
205 let query: JoinQuery = query_str.parse().expect("Failed to parse complex query");
206
207 assert_eq!(query.head.name, "path");
208 assert_eq!(query.head.terms.len(), 2);
209 assert_eq!(query.body.len(), 5);
210
211 assert_eq!(query.body[0].name, "edge");
212 assert_eq!(query.body[1].name, "edge");
213 assert_eq!(query.body[2].name, "vertex");
214 assert_eq!(query.body[3].name, "vertex");
215 assert_eq!(query.body[4].name, "vertex");
216 }
217
218 #[test]
219 fn test_query_from_str_invalid_no_dot() {
220 let query_str = "P(X) :- Q(X)";
221 let result: Result<JoinQuery, _> = query_str.parse();
222 assert!(result.is_err());
223 }
224
225 #[test]
226 fn test_query_from_str_invalid_no_arrow() {
227 let query_str = "P(X) Q(X).";
228 let result: Result<JoinQuery, _> = query_str.parse();
229 assert!(result.is_err());
230 }
231
232 #[test]
233 fn test_query_from_str_invalid_empty_body() {
234 let query_str = "P(X) :- .";
235 let result: Result<JoinQuery, _> = query_str.parse();
236 assert!(result.is_err());
237 }
238
239 #[test]
240 fn test_query_from_str_with_placeholder() {
241 let query_str = "result(X, _) :- relation(X, _).";
242 let query: JoinQuery = query_str
243 .parse()
244 .expect("Failed to parse query with placeholder");
245
246 assert_eq!(query.head.name, "result");
247 assert_eq!(query.head.terms.len(), 2);
248 assert_eq!(query.head.terms[0], Term::Var("X".to_string()));
249 assert_eq!(query.head.terms[1], Term::Placeholder);
250
251 assert_eq!(query.body.len(), 1);
252 assert_eq!(query.body[0].terms[0], Term::Var("X".to_string()));
253 assert_eq!(query.body[0].terms[1], Term::Placeholder);
254 }
255
256 #[test]
257 fn test_query_from_str_multiple_placeholders() {
258 let query_str = "query(_, _, Z) :- data(_, _, Z).";
259 let query: JoinQuery = query_str
260 .parse()
261 .expect("Failed to parse query with multiple placeholders");
262
263 assert_eq!(query.head.terms[0], Term::Placeholder);
264 assert_eq!(query.head.terms[1], Term::Placeholder);
265 assert_eq!(query.head.terms[2], Term::Var("Z".to_string()));
266
267 assert_eq!(query.body[0].terms[0], Term::Placeholder);
268 assert_eq!(query.body[0].terms[1], Term::Placeholder);
269 assert_eq!(query.body[0].terms[2], Term::Var("Z".to_string()));
270 }
271
272 #[test]
273 fn test_query_from_str_placeholder_with_atoms() {
274 let query_str = "match(alice, _) :- person(alice, _), active(_).";
275 let query: JoinQuery = query_str
276 .parse()
277 .expect("Failed to parse query with placeholders and atoms");
278
279 assert_eq!(query.head.terms[0], Term::Atom("alice".to_string()));
280 assert_eq!(query.head.terms[1], Term::Placeholder);
281
282 assert_eq!(query.body[0].name, "person");
283 assert_eq!(query.body[0].terms[0], Term::Atom("alice".to_string()));
284 assert_eq!(query.body[0].terms[1], Term::Placeholder);
285
286 assert_eq!(query.body[1].name, "active");
287 assert_eq!(query.body[1].terms[0], Term::Placeholder);
288 }
289
290 #[test]
291 fn test_query_from_str_only_placeholders() {
292 let query_str = "any(_,_) :- data(_,_).";
293 let query: JoinQuery = query_str
294 .parse()
295 .expect("Failed to parse query with only placeholders");
296
297 assert_eq!(query.head.terms.len(), 2);
298 assert_eq!(query.head.terms[0], Term::Placeholder);
299 assert_eq!(query.head.terms[1], Term::Placeholder);
300
301 assert_eq!(query.body[0].terms.len(), 2);
302 assert_eq!(query.body[0].terms[0], Term::Placeholder);
303 assert_eq!(query.body[0].terms[1], Term::Placeholder);
304 }
305
306 #[test]
307 fn test_query_from_str_placeholder_mixed() {
308 let query_str = "filter(X, _, bob, Y) :- source(X, _, bob, Y), check(X, Y).";
309 let query: JoinQuery = query_str
310 .parse()
311 .expect("Failed to parse complex query with placeholders");
312
313 assert_eq!(query.head.terms.len(), 4);
314 assert_eq!(query.head.terms[0], Term::Var("X".to_string()));
315 assert_eq!(query.head.terms[1], Term::Placeholder);
316 assert_eq!(query.head.terms[2], Term::Atom("bob".to_string()));
317 assert_eq!(query.head.terms[3], Term::Var("Y".to_string()));
318
319 assert_eq!(query.body[0].terms.len(), 4);
320 assert_eq!(query.body[0].terms[1], Term::Placeholder);
321 assert_eq!(query.body[1].terms.len(), 2);
322 }
323}