1use crate::ast::*;
2use crate::error::{LintError, LintResult, LintWarning, Span};
3use crate::lexer::{Token, TokenType};
4
5pub struct ParseResult {
7 pub query: Query,
8 pub warnings: Vec<LintWarning>,
9}
10
11pub struct Parser {
13 tokens: Vec<Token>,
14 current: usize,
15 implicit_and_spans: Vec<Span>,
16}
17
18impl Parser {
19 pub fn new(tokens: Vec<Token>) -> Self {
20 Self {
21 tokens,
22 current: 0,
23 implicit_and_spans: Vec::new(),
24 }
25 }
26
27 pub fn parse(&mut self) -> LintResult<ParseResult> {
29 let expression = self.parse_expression()?;
30 let span = expression.span().clone();
31
32 if !self.is_at_end() && !matches!(self.peek().token_type, TokenType::Eof) {
34 return Err(LintError::UnexpectedToken {
35 span: self.peek().span.clone(),
36 token: self.peek().token_type.to_string(),
37 });
38 }
39
40 let mut warnings = Vec::new();
42 for span in &self.implicit_and_spans {
43 warnings.push(LintWarning::PotentialTypo {
44 span: span.clone(),
45 suggestion: "Consider using explicit 'AND' operator for clarity".to_string(),
46 });
47 }
48
49 Ok(ParseResult {
50 query: Query { expression, span },
51 warnings,
52 })
53 }
54
55 fn parse_expression(&mut self) -> LintResult<Expression> {
56 let mut left = self.parse_and_expression()?;
57
58 while {
59 self.skip_comments();
61 self.match_token(&TokenType::Or)
62 } {
63 let operator = BooleanOperator::Or;
64 let _operator_span = self.previous().span.clone();
65 let right = self.parse_and_expression()?;
66
67 let span = Span::new(left.span().start.clone(), right.span().end.clone());
68 left = Expression::BooleanOp {
69 operator,
70 left: Box::new(left),
71 right: Some(Box::new(right)),
72 span,
73 };
74 }
75
76 Ok(left)
77 }
78
79 fn parse_and_expression(&mut self) -> LintResult<Expression> {
80 let mut left = self.parse_not_expression()?;
81
82 loop {
83 self.skip_comments();
85
86 if self.match_token(&TokenType::And) {
87 let operator = BooleanOperator::And;
89 let _operator_span = self.previous().span.clone();
90 let right = self.parse_not_expression()?;
91
92 let span = Span::new(left.span().start.clone(), right.span().end.clone());
93 left = Expression::BooleanOp {
94 operator,
95 left: Box::new(left),
96 right: Some(Box::new(right)),
97 span,
98 };
99 } else if self.is_implicit_and_candidate() {
100 let right = self.parse_not_expression()?;
102
103 let span = Span::new(left.span().start.clone(), right.span().end.clone());
104 left = Expression::BooleanOp {
105 operator: BooleanOperator::And,
106 left: Box::new(left),
107 right: Some(Box::new(right)),
108 span: span.clone(),
109 };
110
111 self.implicit_and_spans.push(span);
113 } else {
114 break;
115 }
116 }
117
118 Ok(left)
119 }
120
121 fn parse_not_expression(&mut self) -> LintResult<Expression> {
122 if self.match_token(&TokenType::Not) {
124 let operator_span = self.previous().span.clone();
125 let dummy_left = Expression::Term {
126 term: Term::Word {
127 value: "".to_string(),
128 },
129 span: operator_span.clone(),
130 };
131 let right = self.parse_proximity_expression()?;
132
133 let span = Span::new(operator_span.start.clone(), right.span().end.clone());
134 return Ok(Expression::BooleanOp {
135 operator: BooleanOperator::Not,
136 left: Box::new(dummy_left),
137 right: Some(Box::new(right)),
138 span,
139 });
140 }
141
142 let mut left = self.parse_proximity_expression()?;
143
144 while {
145 self.skip_comments();
147 self.match_token(&TokenType::Not)
148 } {
149 let operator = BooleanOperator::Not;
150 let _operator_span = self.previous().span.clone();
151 let right = self.parse_proximity_expression()?;
152
153 let span = Span::new(left.span().start.clone(), right.span().end.clone());
154 left = Expression::BooleanOp {
155 operator,
156 left: Box::new(left),
157 right: Some(Box::new(right)),
158 span,
159 };
160 }
161
162 Ok(left)
163 }
164
165 fn parse_proximity_expression(&mut self) -> LintResult<Expression> {
166 let left = self.parse_primary()?;
167
168 if self.match_token(&TokenType::Tilde) {
170 let tilde_span = self.previous().span.clone();
171 let mut distance = None;
172
173 if let TokenType::Number(num_str) = &self.peek().token_type {
175 distance = num_str.parse::<u32>().ok();
176 self.advance();
177 }
178
179 let is_valid_tilde_context = match &left {
186 Expression::Term {
188 term: Term::Phrase { .. },
189 ..
190 } => true,
191 Expression::Group { .. } => true,
193 Expression::Term { .. } => {
195 self.is_at_end()
198 || matches!(
199 self.peek().token_type,
200 TokenType::And
201 | TokenType::Or
202 | TokenType::Not
203 | TokenType::RightParen
204 | TokenType::LeftParen
205 | TokenType::Eof
206 )
207 }
208 _ => false,
209 };
210
211 if !is_valid_tilde_context {
212 return Err(LintError::ValidationError {
214 span: tilde_span,
215 message: "The ~ character should be used after a search term or quoted phrase (to specify fuzzy matching), or after a sub-query (to specify proximity matching). If this should be part of a search term, it must be quoted (or escaped using the \\ character).".to_string(),
216 });
217 }
218
219 let terms = vec![left];
220 let end_span = tilde_span.end.clone();
221 let span = Span::new(terms[0].span().start.clone(), end_span);
222
223 return Ok(Expression::Proximity {
224 operator: ProximityOperator::Proximity { distance },
225 terms,
226 span,
227 });
228 }
229
230 if let TokenType::Near(distance) = &self.peek().token_type {
232 let distance = *distance;
233 self.advance();
234 let _operator_span = self.previous().span.clone();
235 let right = self.parse_primary()?;
236
237 let span = Span::new(left.span().start.clone(), right.span().end.clone());
238 return Ok(Expression::Proximity {
239 operator: ProximityOperator::Near { distance },
240 terms: vec![left, right],
241 span,
242 });
243 }
244
245 if let TokenType::NearForward(distance) = &self.peek().token_type {
246 let distance = *distance;
247 self.advance();
248 let _operator_span = self.previous().span.clone();
249 let right = self.parse_primary()?;
250
251 let span = Span::new(left.span().start.clone(), right.span().end.clone());
252 return Ok(Expression::Proximity {
253 operator: ProximityOperator::NearForward { distance },
254 terms: vec![left, right],
255 span,
256 });
257 }
258
259 Ok(left)
260 }
261
262 fn parse_primary(&mut self) -> LintResult<Expression> {
263 if self.match_token(&TokenType::LeftParen) {
265 let start_span = self.previous().span.clone();
266 let expr = self.parse_expression()?;
267
268 if !self.match_token(&TokenType::RightParen) {
269 return Err(LintError::ExpectedToken {
270 span: self.peek().span.clone(),
271 expected: ")".to_string(),
272 found: self.peek().token_type.to_string(),
273 });
274 }
275
276 let end_span = self.previous().span.clone();
277 let span = Span::new(start_span.start, end_span.end);
278
279 return Ok(Expression::Group {
280 expression: Box::new(expr),
281 span,
282 });
283 }
284
285 if self.match_token(&TokenType::LeftBrace) {
287 let start_span = self.previous().span.clone();
288
289 if let TokenType::Word(word) = &self.peek().token_type {
290 let word = word.clone();
291 self.advance();
292 let _word_span = self.previous().span.clone();
293
294 if !self.match_token(&TokenType::RightBrace) {
295 return Err(LintError::ExpectedToken {
296 span: self.peek().span.clone(),
297 expected: "}".to_string(),
298 found: self.peek().token_type.to_string(),
299 });
300 }
301
302 let end_span = self.previous().span.clone();
303 let span = Span::new(start_span.start, end_span.end);
304
305 return Ok(Expression::Term {
306 term: Term::CaseSensitive { value: word },
307 span,
308 });
309 } else {
310 return Err(LintError::ExpectedToken {
311 span: self.peek().span.clone(),
312 expected: "word".to_string(),
313 found: self.peek().token_type.to_string(),
314 });
315 }
316 }
317
318 if self.match_token(&TokenType::LeftBracket) {
320 return self.parse_range();
321 }
322
323 if self.match_token(&TokenType::CommentStart) {
325 return self.parse_comment();
326 }
327
328 if let TokenType::Word(word) = &self.peek().token_type {
330 let word = word.clone();
331 let word_span = self.peek().span.clone();
332
333 if self.peek_ahead(1).map(|t| &t.token_type) == Some(&TokenType::Colon) {
335 self.advance(); self.advance(); let value = Box::new(self.parse_primary()?);
339
340 let value = if let Expression::Range {
341 start,
342 end,
343 span: range_span,
344 ..
345 } = value.as_ref()
346 {
347 if let Some(field_type) = FieldType::parse(&word) {
348 Box::new(Expression::Range {
349 field: Some(field_type),
350 start: start.clone(),
351 end: end.clone(),
352 span: range_span.clone(),
353 })
354 } else {
355 value
356 }
357 } else {
358 value
359 };
360
361 let span = Span::new(word_span.start, value.span().end.clone());
362
363 if let Some(field_type) = FieldType::parse(&word) {
364 return Ok(Expression::Field {
365 field: field_type,
366 value,
367 span,
368 });
369 } else {
370 return Ok(Expression::Term {
371 term: Term::Word {
372 value: format!(
373 "{}:{}",
374 word,
375 match value.as_ref() {
376 Expression::Term {
377 term: Term::Word { value },
378 ..
379 } => value.clone(),
380 Expression::Term {
381 term: Term::Phrase { value },
382 ..
383 } => format!("\"{}\"", value),
384 _ => "unknown".to_string(),
385 }
386 ),
387 },
388 span,
389 });
390 }
391 }
392 }
393
394 self.parse_term()
395 }
396
397 fn parse_range(&mut self) -> LintResult<Expression> {
398 let start_span = self.previous().span.clone();
399
400 let start_value = match &self.peek().token_type {
401 TokenType::Word(w) | TokenType::Number(w) => {
402 let val = w.clone();
403 self.advance();
404 val
405 }
406 _ => {
407 return Err(LintError::ExpectedToken {
408 span: self.peek().span.clone(),
409 expected: "number or word".to_string(),
410 found: self.peek().token_type.to_string(),
411 });
412 }
413 };
414
415 if !self.match_token(&TokenType::To) {
416 return Err(LintError::ExpectedToken {
417 span: self.peek().span.clone(),
418 expected: "TO".to_string(),
419 found: self.peek().token_type.to_string(),
420 });
421 }
422
423 let end_value = match &self.peek().token_type {
424 TokenType::Word(w) | TokenType::Number(w) => {
425 let val = w.clone();
426 self.advance();
427 val
428 }
429 _ => {
430 return Err(LintError::ExpectedToken {
431 span: self.peek().span.clone(),
432 expected: "number or word".to_string(),
433 found: self.peek().token_type.to_string(),
434 });
435 }
436 };
437
438 if !self.match_token(&TokenType::RightBracket) {
439 return Err(LintError::ExpectedToken {
440 span: self.peek().span.clone(),
441 expected: "]".to_string(),
442 found: self.peek().token_type.to_string(),
443 });
444 }
445
446 let end_span = self.previous().span.clone();
447 let span = Span::new(start_span.start, end_span.end);
448
449 Ok(Expression::Range {
450 field: None,
451 start: start_value,
452 end: end_value,
453 span,
454 })
455 }
456
457 fn parse_comment(&mut self) -> LintResult<Expression> {
458 let start_span = self.previous().span.clone();
459 let mut comment_text = String::new();
460
461 while !self.is_at_end() && !matches!(self.peek().token_type, TokenType::CommentEnd) {
462 match &self.peek().token_type {
463 TokenType::Word(w) => comment_text.push_str(w),
464 TokenType::QuotedString(s) => comment_text.push_str(&format!("\"{}\"", s)),
465 TokenType::Number(n) => comment_text.push_str(n),
466 TokenType::Whitespace => comment_text.push(' '),
467 _ => comment_text.push_str(&self.peek().raw),
468 }
469 self.advance();
470 }
471
472 if !self.match_token(&TokenType::CommentEnd) {
473 return Err(LintError::ExpectedToken {
474 span: self.peek().span.clone(),
475 expected: ">>>".to_string(),
476 found: self.peek().token_type.to_string(),
477 });
478 }
479
480 let end_span = self.previous().span.clone();
481 let span = Span::new(start_span.start, end_span.end);
482
483 Ok(Expression::Comment {
484 text: comment_text.trim().to_string(),
485 span,
486 })
487 }
488
489 fn parse_term(&mut self) -> LintResult<Expression> {
490 let token = self.peek().clone();
491
492 match &token.token_type {
493 TokenType::Word(word) => {
494 self.advance();
495 let term = if word.contains('*') {
496 Term::Wildcard {
497 value: word.clone(),
498 }
499 } else if word.contains('?') {
500 Term::Replacement {
501 value: word.clone(),
502 }
503 } else {
504 Term::Word {
505 value: word.clone(),
506 }
507 };
508
509 Ok(Expression::Term {
510 term,
511 span: token.span,
512 })
513 }
514 TokenType::QuotedString(string) => {
515 self.advance();
516 Ok(Expression::Term {
517 term: Term::Phrase {
518 value: string.clone(),
519 },
520 span: token.span,
521 })
522 }
523 TokenType::Number(number) => {
524 self.advance();
525 Ok(Expression::Term {
526 term: Term::Word {
527 value: number.clone(),
528 },
529 span: token.span,
530 })
531 }
532 TokenType::Hashtag(hashtag) => {
533 self.advance();
534 Ok(Expression::Term {
535 term: Term::Hashtag {
536 value: hashtag.clone(),
537 },
538 span: token.span,
539 })
540 }
541 TokenType::Mention(mention) => {
542 self.advance();
543 Ok(Expression::Term {
544 term: Term::Mention {
545 value: mention.clone(),
546 },
547 span: token.span,
548 })
549 }
550 _ => Err(LintError::UnexpectedToken {
551 span: token.span,
552 token: token.token_type.to_string(),
553 }),
554 }
555 }
556
557 fn match_token(&mut self, token_type: &TokenType) -> bool {
558 if self.check(token_type) {
559 self.advance();
560 true
561 } else {
562 false
563 }
564 }
565
566 fn check(&self, token_type: &TokenType) -> bool {
567 if self.is_at_end() {
568 false
569 } else {
570 std::mem::discriminant(&self.peek().token_type) == std::mem::discriminant(token_type)
571 }
572 }
573
574 fn advance(&mut self) -> &Token {
575 if !self.is_at_end() {
576 self.current += 1;
577 }
578 self.previous()
579 }
580
581 fn is_at_end(&self) -> bool {
582 matches!(self.peek().token_type, TokenType::Eof) || self.current >= self.tokens.len()
583 }
584
585 fn peek(&self) -> &Token {
586 &self.tokens[self.current]
587 }
588
589 fn peek_ahead(&self, offset: usize) -> Option<&Token> {
590 let index = self.current + offset;
591 if index < self.tokens.len() {
592 Some(&self.tokens[index])
593 } else {
594 None
595 }
596 }
597
598 fn previous(&self) -> &Token {
599 &self.tokens[self.current - 1]
600 }
601
602 fn is_implicit_and_candidate(&self) -> bool {
603 if self.is_at_end() {
604 return false;
605 }
606
607 match &self.peek().token_type {
608 TokenType::Word(_)
609 | TokenType::QuotedString(_)
610 | TokenType::Number(_)
611 | TokenType::Hashtag(_)
612 | TokenType::Mention(_)
613 | TokenType::LeftParen
614 | TokenType::LeftBrace => true,
615
616 TokenType::Or
617 | TokenType::RightParen
618 | TokenType::RightBracket
619 | TokenType::RightBrace
620 | TokenType::Eof => false,
621
622 TokenType::And | TokenType::Not => false,
623
624 _ => false,
625 }
626 }
627
628 fn skip_comments(&mut self) {
630 while self.match_token(&TokenType::CommentStart) {
631 while !self.is_at_end() && !matches!(self.peek().token_type, TokenType::CommentEnd) {
632 self.advance();
633 }
634 self.match_token(&TokenType::CommentEnd);
635 }
636 }
637}
638
639impl Expression {
640 pub fn span(&self) -> &Span {
641 match self {
642 Expression::BooleanOp { span, .. } => span,
643 Expression::Group { span, .. } => span,
644 Expression::Proximity { span, .. } => span,
645 Expression::Field { span, .. } => span,
646 Expression::Range { span, .. } => span,
647 Expression::Term { span, .. } => span,
648 Expression::Comment { span, .. } => span,
649 }
650 }
651}
652
653#[cfg(test)]
654mod tests {
655 use super::*;
656 use crate::lexer::Lexer;
657
658 #[test]
659 fn test_basic_parsing() {
660 let mut lexer = Lexer::new("apple AND juice");
661 let tokens = lexer.tokenize().unwrap();
662 let mut parser = Parser::new(tokens);
663 let result = parser.parse().unwrap();
664
665 match result.query.expression {
666 Expression::BooleanOp { operator, .. } => {
667 assert_eq!(operator, BooleanOperator::And);
668 }
669 _ => panic!("Expected BooleanOp"),
670 }
671 }
672
673 #[test]
674 fn test_quoted_phrase() {
675 let mut lexer = Lexer::new("\"apple juice\"");
676 let tokens = lexer.tokenize().unwrap();
677 let mut parser = Parser::new(tokens);
678 let result = parser.parse().unwrap();
679
680 match result.query.expression {
681 Expression::Term {
682 term: Term::Phrase { value },
683 ..
684 } => {
685 assert_eq!(value, "apple juice");
686 }
687 _ => panic!("Expected Term with Phrase"),
688 }
689 }
690
691 #[test]
692 fn test_field_operation() {
693 let mut lexer = Lexer::new("title:\"apple juice\"");
694 let tokens = lexer.tokenize().unwrap();
695 let mut parser = Parser::new(tokens);
696 let result = parser.parse().unwrap();
697
698 match result.query.expression {
699 Expression::Field { field, .. } => {
700 assert_eq!(field, FieldType::Title);
701 }
702 _ => panic!("Expected Field operation"),
703 }
704 }
705
706 #[test]
707 fn test_implicit_and() {
708 let mut lexer = Lexer::new("apple banana");
709 let tokens = lexer.tokenize().unwrap();
710 let mut parser = Parser::new(tokens);
711 let result = parser.parse().unwrap();
712
713 match result.query.expression {
714 Expression::BooleanOp { operator, .. } => {
715 assert_eq!(operator, BooleanOperator::And);
716 }
717 _ => panic!("Expected BooleanOp with implicit AND"),
718 }
719
720 assert!(!result.warnings.is_empty());
721 }
722}