1use std::{borrow::Borrow, cell::Cell, fmt, result};
3
4use thiserror::Error;
5
6use super::{
7 ast::{Action, Ast, Condition, Description, Root},
8 tokenizer::{Token, TokenKind},
9};
10use crate::{
11 error::FrontendError,
12 span::Span,
13 utils::{repeat_str, sanitize},
14};
15
16type Result<T> = result::Result<T, Error>;
17
18#[derive(Error, Clone, Debug, Eq, PartialEq)]
21pub struct Error {
22 #[source]
24 kind: ErrorKind,
25 text: String,
28 span: Span,
30}
31
32impl FrontendError<ErrorKind> for Error {
33 fn kind(&self) -> &ErrorKind {
35 &self.kind
36 }
37
38 fn text(&self) -> &str {
40 &self.text
41 }
42
43 fn span(&self) -> &Span {
45 &self.span
46 }
47}
48
49impl fmt::Display for Error {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 self.format_error(f)
52 }
53}
54
55type Lexeme = String;
56
57#[derive(Error, Clone, Debug, Eq, PartialEq)]
59#[non_exhaustive]
60pub enum ErrorKind {
61 #[error("unexpected token '{0}'")]
66 TokenUnexpected(Lexeme),
67
68 #[error("unexpected token in description '{0}'")]
70 DescriptionTokenUnexpected(Lexeme),
71
72 #[error("unexpected `when` keyword")]
74 WhenUnexpected,
75
76 #[error("unexpected `given` keyword")]
78 GivenUnexpected,
79
80 #[error("unexpected `it` keyword")]
82 ItUnexpected,
83
84 #[error("unexpected `word` '{0}'")]
86 WordUnexpected(Lexeme),
87
88 #[error("unexpected end of file")]
90 EofUnexpected,
91
92 #[error("found an empty tree")]
94 TreeEmpty,
95
96 #[error("found a condition/action without a title")]
98 TitleMissing,
99
100 #[error("missing a root")]
102 TreeRootless,
103
104 #[error("a `Corner` must be the last child")]
106 CornerNotLastChild,
107
108 #[error("a `Tee` must not be the last child")]
110 TeeLastChild,
111}
112
113#[derive(Clone, Default)]
118pub struct Parser {
119 current: Cell<usize>,
121}
122
123impl Parser {
124 #[must_use]
126 pub const fn new() -> Self {
127 Self { current: Cell::new(0) }
128 }
129
130 pub fn parse(&mut self, text: &str, tokens: &[Token]) -> Result<Ast> {
135 ParserI::new(self, text, tokens).parse()
136 }
137
138 fn reset(&self) {
140 self.current.set(0);
141 }
142}
143
144struct ParserI<'t, P> {
146 text: &'t str,
148 tokens: &'t [Token],
150 parser: P,
152}
153
154impl<'t, P: Borrow<Parser>> ParserI<'t, P> {
155 const fn new(parser: P, text: &'t str, tokens: &'t [Token]) -> Self {
157 Self { text, tokens, parser }
158 }
159
160 fn parser(&self) -> &Parser {
162 self.parser.borrow()
163 }
164
165 fn error(&self, span: Span, kind: ErrorKind) -> Error {
167 Error { kind, text: self.text.to_owned(), span }
168 }
169
170 fn is_eof(&self) -> bool {
173 self.parser().current.get() == self.tokens.len()
174 }
175
176 fn current(&self) -> Option<&Token> {
181 self.tokens.get(self.parser().current.get())
182 }
183
184 fn peek(&self) -> Option<&Token> {
189 let current_index = self.parser().current.get();
190 self.tokens.get(current_index + 1)
191 }
192
193 fn previous(&self) -> Option<&Token> {
198 match self.parser().current.get() {
199 0 => None,
200 current => self.tokens.get(current - 1),
201 }
202 }
203
204 fn consume(&self) -> Option<&Token> {
208 if self.is_eof() {
209 return None;
210 }
211 self.parser().current.set(self.parser().current.get() + 1);
212 self.tokens.get(self.parser().current.get())
213 }
214
215 pub(crate) fn parse(&self) -> Result<Ast> {
221 self.parser().reset();
222
223 let root_token = self
224 .current()
225 .ok_or(self.error(Span::default(), ErrorKind::TreeEmpty))?;
226
227 match root_token.kind {
228 TokenKind::Word => self.parse_root(root_token),
229 _ => Err(self.error(root_token.span, ErrorKind::TreeRootless)),
230 }
231 }
232
233 fn parse_root(&self, token: &Token) -> Result<Ast> {
244 assert!(matches!(token.kind, TokenKind::Word));
245 self.consume();
246
247 let mut children = vec![];
250 while let Some(current_token) = self.current() {
251 let child =
252 match current_token.kind {
253 TokenKind::Corner | TokenKind::Tee => {
254 self.parse_branch(current_token)?
255 }
256 TokenKind::Word => Err(self.error(
257 current_token.span,
258 ErrorKind::WordUnexpected(current_token.lexeme.clone()),
259 ))?,
260 TokenKind::When => Err(self
261 .error(current_token.span, ErrorKind::WhenUnexpected))?,
262 TokenKind::Given => Err(self.error(
263 current_token.span,
264 ErrorKind::GivenUnexpected,
265 ))?,
266 TokenKind::It => Err(
267 self.error(current_token.span, ErrorKind::ItUnexpected)
268 )?,
269 };
270
271 children.push(child);
272 }
273
274 let last_span = if children.is_empty() {
275 &token.span
276 } else {
277 children.iter().last().unwrap().span()
278 };
279
280 Ok(Ast::Root(Root {
281 span: Span::new(token.span.start, last_span.end),
282 children,
283 contract_name: token.lexeme.clone(),
284 }))
285 }
286
287 fn parse_branch(&self, token: &Token) -> Result<Ast> {
295 assert!(matches!(token.kind, TokenKind::Tee | TokenKind::Corner));
296
297 let first_token = self.peek().ok_or(self.error(
298 token.span.with_start(token.span.end),
299 ErrorKind::EofUnexpected,
300 ))?;
301
302 let ast = match first_token.kind {
303 TokenKind::When | TokenKind::Given => {
304 self.parse_condition(token)?
305 }
306 TokenKind::It => self.parse_action(token)?,
307 _ => Err(self.error(
308 first_token.span,
309 ErrorKind::TokenUnexpected(first_token.lexeme.clone()),
310 ))?,
311 };
312
313 if matches!(token.kind, TokenKind::Tee) && self.is_eof() {
314 return Err(self.error(
315 token.span.with_start(token.span.end),
316 ErrorKind::TeeLastChild,
317 ));
318 } else if matches!(token.kind, TokenKind::Corner) && !self.is_eof() {
319 return Err(self.error(
320 token.span.with_start(token.span.end),
321 ErrorKind::CornerNotLastChild,
322 ));
323 };
324
325 Ok(ast)
326 }
327
328 fn parse_condition(&self, token: &Token) -> Result<Ast> {
340 assert!(matches!(token.kind, TokenKind::Tee | TokenKind::Corner));
341
342 let start_token = self.peek().ok_or(self.error(
343 token.span.with_start(token.span.end),
344 ErrorKind::EofUnexpected,
345 ))?;
346 let title = self.parse_string(start_token);
347
348 if title.len() == start_token.lexeme.len() {
349 return Err(self.error(start_token.span, ErrorKind::TitleMissing));
350 };
351
352 let mut children = vec![];
353 while self
354 .current()
355 .is_some_and(|t| t.span.start.column > token.span.start.column)
358 {
359 let next_token = self.peek().ok_or(self.error(
360 token.span.with_start(token.span.end),
361 ErrorKind::EofUnexpected,
362 ))?;
363
364 let current_token = self.current().unwrap();
365 let ast = match next_token.kind {
366 TokenKind::When | TokenKind::Given => {
367 self.parse_condition(current_token)?
368 }
369 TokenKind::It => self.parse_action(current_token)?,
370 _ => Err(self.error(
371 next_token.span,
372 ErrorKind::TokenUnexpected(next_token.lexeme.clone()),
373 ))?,
374 };
375
376 children.push(ast);
377 }
378
379 let previous = self.previous().unwrap();
380 Ok(Ast::Condition(Condition {
381 title: sanitize(&title),
382 children,
383 span: Span::new(token.span.start, previous.span.end),
384 }))
385 }
386
387 fn parse_action(&self, token: &Token) -> Result<Ast> {
399 assert!(matches!(token.kind, TokenKind::Tee | TokenKind::Corner));
400
401 let start_token = self.peek().ok_or(self.error(
402 token.span.with_start(token.span.end),
403 ErrorKind::EofUnexpected,
404 ))?;
405 let title = self.parse_string(start_token);
406
407 let mut children = vec![];
408 while self
409 .current()
410 .is_some_and(|t| t.span.start.column > token.span.start.column)
413 {
414 let next_token = self.peek().ok_or(self.error(
415 token.span.with_start(token.span.end),
416 ErrorKind::EofUnexpected,
417 ))?;
418
419 let current_token = self.current().unwrap();
420 let ast = match next_token.kind {
421 TokenKind::Word => self.parse_description(
422 current_token,
423 current_token.span.start.column - token.span.start.column,
424 )?,
425 _ => Err(self.error(
426 next_token.span,
427 ErrorKind::DescriptionTokenUnexpected(
428 next_token.lexeme.clone(),
429 ),
430 ))?,
431 };
432
433 children.push(ast);
434 }
435
436 let previous = self.previous().unwrap();
437 Ok(Ast::Action(Action {
438 title,
439 children,
440 span: Span::new(token.span.start, previous.span.end),
441 }))
442 }
443
444 fn parse_description(
469 &self,
470 token: &Token,
471 column_delta: usize,
472 ) -> Result<Ast> {
473 assert!(matches!(token.kind, TokenKind::Tee | TokenKind::Corner));
474
475 let start_token = self.peek().ok_or(self.error(
476 token.span.with_start(token.span.end),
477 ErrorKind::EofUnexpected,
478 ))?;
479 let text = self.parse_string(start_token);
480
481 let previous = self.previous().unwrap();
482 Ok(Ast::ActionDescription(Description {
483 text: format!("{}{}", repeat_str(" ", column_delta), text),
484 span: Span::new(token.span.start, previous.span.end),
485 }))
486 }
487
488 fn parse_string(&self, start_token: &Token) -> String {
495 self.consume();
496 let mut string = String::from(&start_token.lexeme);
497
498 while let Some(token) = self.consume() {
500 match token.kind {
501 TokenKind::Word
502 | TokenKind::It
503 | TokenKind::When
504 | TokenKind::Given => {
505 string = string + " " + &token.lexeme;
506 }
507 _ => break,
508 }
509 }
510
511 string
512 }
513}
514
515#[cfg(test)]
516mod tests {
517 use indoc::indoc;
518 use pretty_assertions::assert_eq;
519
520 use crate::{
521 ast::{Action, Ast, Condition, Description, Root},
522 parser::{self, ErrorKind, Parser},
523 span::Span,
524 test_utils::{p, s, TestError},
525 tokenizer::Tokenizer,
526 };
527
528 impl PartialEq<parser::Error> for TestError<parser::ErrorKind> {
529 fn eq(&self, other: &parser::Error) -> bool {
530 self.span == other.span && self.kind == other.kind
531 }
532 }
533
534 impl PartialEq<TestError<parser::ErrorKind>> for parser::Error {
535 fn eq(&self, other: &TestError<parser::ErrorKind>) -> bool {
536 self.span == other.span && self.kind == other.kind
537 }
538 }
539
540 fn e<K>(kind: K, span: Span) -> TestError<K> {
541 TestError { kind, span }
542 }
543
544 fn parse(file_contents: &str) -> parser::Result<Ast> {
545 let tokens = Tokenizer::new().tokenize(file_contents).unwrap();
546 Parser::new().parse(file_contents, &tokens)
547 }
548
549 #[test]
550 fn empty_tree() {
551 assert_eq!(
552 parse("").unwrap_err(),
553 e(ErrorKind::TreeEmpty, Span::default())
554 );
555 }
556
557 #[test]
558 fn rootless_tree() {
559 assert_eq!(
560 parse("└── It should never revert.").unwrap_err(),
561 e(ErrorKind::TreeRootless, Span::default())
562 );
563 assert_eq!(
564 parse("├── It should revert.").unwrap_err(),
565 e(ErrorKind::TreeRootless, Span::default())
566 );
567 assert_eq!(
568 parse("└── When stuff happens").unwrap_err(),
569 e(ErrorKind::TreeRootless, Span::default())
570 );
571 assert_eq!(
572 parse("├── When stuff happens").unwrap_err(),
573 e(ErrorKind::TreeRootless, Span::default())
574 );
575 assert_eq!(
576 parse("└── this is a description").unwrap_err(),
577 e(ErrorKind::TreeRootless, Span::default())
578 );
579 }
580
581 #[test]
582 fn tee_last_child_errors() {
583 let input = indoc! {"
584 Foo_Test
585 ├── when something bad happens
586 "};
587
588 assert_eq!(
589 parse(input).unwrap_err(),
590 e(ErrorKind::TeeLastChild, Span::splat(p(9, 2, 1))),
591 "Using a tee (├──) for the last child should result in a TeeLastChild error"
592 );
593 }
594
595 #[test]
596 fn corner_not_last_child_errors() {
597 let input = indoc! {"
598 Foo_Test
599 └── when something bad happens
600 └── it should revert
601 └── when something happens
602 └── it should not revert
603 "};
604 assert_eq!(
605 parse(input).unwrap_err(),
606 e(ErrorKind::CornerNotLastChild, Span::splat(p(9, 2, 1)))
607 );
608 }
609
610 #[test]
611 fn only_contract_name() {
612 assert_eq!(
613 parse("FooTest").unwrap(),
614 Ast::Root(Root {
615 span: s(p(0, 1, 1), p(6, 1, 7)),
616 children: vec![],
617 contract_name: String::from("FooTest"),
618 })
619 );
620 }
621
622 #[test]
623 fn one_child() {
624 let input = indoc! {"
625 Foo_Test
626 └── when something bad happens
627 └── it should revert
628 "};
629 assert_eq!(
630 parse(input).unwrap(),
631 Ast::Root(Root {
632 contract_name: String::from("Foo_Test"),
633 span: s(p(0, 1, 1), p(74, 3, 23)),
634 children: vec![Ast::Condition(Condition {
635 span: s(p(9, 2, 1), p(74, 3, 23)),
636 title: String::from("when something bad happens"),
637 children: vec![Ast::Action(Action {
638 span: s(p(49, 3, 4), p(74, 3, 23)),
639 title: String::from("it should revert"),
640 children: vec![]
641 })],
642 })],
643 })
644 );
645 }
646
647 #[test]
648 fn one_action_description() {
649 let input = indoc! {"
650 Foo_Test
651 └── when something bad happens
652 └── it should revert
653 └── because _bad_
654 "};
655 assert_eq!(
656 parse(input).unwrap(),
657 Ast::Root(Root {
658 contract_name: String::from("Foo_Test"),
659 span: s(p(0, 1, 1), p(104, 4, 23)),
660 children: vec![Ast::Condition(Condition {
661 span: s(p(9, 2, 1), p(104, 4, 23)),
662 title: String::from("when something bad happens"),
663 children: vec![Ast::Action(Action {
664 span: s(p(49, 3, 4), p(104, 4, 23)),
665 title: String::from("it should revert"),
666 children: vec![Ast::ActionDescription(Description {
667 span: s(p(82, 4, 7), p(104, 4, 23)),
668 text: String::from(" because _bad_"),
669 })]
670 })],
671 })],
672 })
673 );
674 }
675
676 #[test]
677 fn nested_action_descriptions() {
678 let input = indoc! {"
679 Foo_Test
680 └── when something bad happens
681 └── it should revert
682 ├── some stuff happened
683 │ └── and that stuff
684 └── was very _bad_
685 "};
686 assert_eq!(
687 parse(input).unwrap(),
688 Ast::Root(Root {
689 contract_name: String::from("Foo_Test"),
690 span: s(p(0, 1, 1), p(177, 6, 24)),
691 children: vec![Ast::Condition(Condition {
692 span: s(p(9, 2, 1), p(177, 6, 24)),
693 title: String::from("when something bad happens"),
694 children: vec![Ast::Action(Action {
695 span: s(p(49, 3, 4), p(177, 6, 24)),
696 title: String::from("it should revert"),
697 children: vec![
698 Ast::ActionDescription(Description {
699 span: s(p(82, 4, 7), p(110, 4, 29)),
700 text: String::from(" some stuff happened"),
701 }),
702 Ast::ActionDescription(Description {
703 span: s(p(123, 5, 10), p(146, 5, 27)),
704 text: String::from(" and that stuff"),
705 }),
706 Ast::ActionDescription(Description {
707 span: s(p(154, 6, 7), p(177, 6, 24)),
708 text: String::from(" was very _bad_"),
709 }),
710 ]
711 })],
712 })],
713 })
714 );
715 }
716
717 #[test]
718 fn unexpected_tokens() {
719 use ErrorKind::*;
720 assert_eq!(
721 parse(r"a └ └").unwrap_err(),
722 e(TokenUnexpected("└".to_owned()), Span::splat(p(6, 1, 5)))
723 );
724 assert_eq!(
725 parse(r"a ├ ├").unwrap_err(),
726 e(TokenUnexpected("├".to_owned()), Span::splat(p(6, 1, 5)))
727 );
728 assert_eq!(
729 parse(r"a └").unwrap_err(),
730 e(EofUnexpected, Span::splat(p(2, 1, 3)))
731 );
732 assert_eq!(
733 parse(r"a └ when").unwrap_err(),
734 e(TitleMissing, s(p(6, 1, 5), p(9, 1, 8)))
735 );
736 assert_eq!(
737 parse(r"a ├").unwrap_err(),
738 e(EofUnexpected, Span::splat(p(2, 1, 3)))
739 );
740 assert_eq!(
741 parse(r"a when").unwrap_err(),
742 e(WhenUnexpected, s(p(2, 1, 3), p(5, 1, 6)))
743 );
744 assert_eq!(
745 parse(r"a given").unwrap_err(),
746 e(GivenUnexpected, s(p(2, 1, 3), p(6, 1, 7)))
747 );
748 assert_eq!(
749 parse(r"a it").unwrap_err(),
750 e(ItUnexpected, s(p(2, 1, 3), p(3, 1, 4)))
751 );
752 assert_eq!(
753 parse(r"a b").unwrap_err(),
754 e(WordUnexpected("b".to_owned()), Span::splat(p(2, 1, 3)))
755 );
756 }
757
758 #[test]
759 fn descriptions_are_the_only_action_children() {
760 let input = indoc! {"
761 Foo_Test
762 └── when something bad happens
763 └── it should revert
764 └── it because _bad_
765 "};
766
767 assert_eq!(
768 parse(input).unwrap_err(),
769 e(
770 ErrorKind::DescriptionTokenUnexpected("it".to_owned()),
771 s(p(92, 4, 11), p(93, 4, 12))
772 )
773 );
774 }
775
776 #[test]
777 fn two_children() {
778 let input = indoc! {"
779 FooBarTheBest_Test
780 ├── when stuff called
781 │ └── it should revert
782 └── given not stuff called
783 └── it should revert
784 "};
785
786 assert_eq!(
787 parse(input).unwrap(),
788 Ast::Root(Root {
789 contract_name: String::from("FooBarTheBest_Test"),
790 span: s(p(0, 1, 1), p(140, 5, 23)),
791 children: vec![
792 Ast::Condition(Condition {
793 title: String::from("when stuff called"),
794 span: s(p(19, 2, 1), p(77, 3, 23)),
795 children: vec![Ast::Action(Action {
796 title: String::from("it should revert"),
797 span: s(p(52, 3, 4), p(77, 3, 23)),
798 children: vec![]
799 })],
800 }),
801 Ast::Condition(Condition {
802 title: String::from("given not stuff called"),
803 span: s(p(79, 4, 1), p(140, 5, 23)),
804 children: vec![Ast::Action(Action {
805 title: String::from("it should revert"),
806 span: s(p(115, 5, 4), p(140, 5, 23)),
807 children: vec![]
808 })],
809 }),
810 ],
811 })
812 );
813 }
814
815 #[test]
817 fn parses_top_level_actions() {
818 let input = indoc! {r#"
819 Foo
820 └── It reverts when X.
821 "#};
822
823 assert_eq!(
824 parse(input).unwrap(),
825 Ast::Root(Root {
826 contract_name: String::from("Foo"),
827 span: s(p(0, 1, 1), p(31, 2, 22)),
828 children: vec![Ast::Action(Action {
829 title: String::from("It reverts when X."),
830 span: s(p(4, 2, 1), p(31, 2, 22)),
831 children: vec![]
832 })],
833 })
834 );
835 }
836
837 #[test]
838 fn unsanitized_input() {
839 let input = indoc! {r#"
840 FooB-rTheBestOf_Test
841 └── when st-ff "all'd
842 └── it should revert
843 "#};
844
845 assert_eq!(
846 parse(input).unwrap(),
847 Ast::Root(Root {
848 contract_name: String::from("FooB-rTheBestOf_Test"),
849 span: s(p(0, 1, 1), p(77, 3, 23)),
850 children: vec![Ast::Condition(Condition {
851 title: String::from("when st_ff alld"),
852 span: s(p(21, 2, 1), p(77, 3, 23)),
853 children: vec![Ast::Action(Action {
854 title: String::from("it should revert"),
855 span: s(p(52, 3, 4), p(77, 3, 23)),
856 children: vec![]
857 })],
858 })],
859 })
860 );
861 }
862}