chumsky_branch/
branch.rs

1use crate::not_containing;
2use chumsky::prelude::*;
3
4/// The return type of [branch].
5#[allow(clippy::type_complexity)]
6pub struct Branch<'a, T, E> {
7	branches: Vec<(&'a str, Box<dyn Parser<char, T, Error = E> + 'a>)>
8}
9
10/// Branch into a parser when and only when encountering the `begin` pattern.
11pub fn branch<'a, P, T>(begin: &'a str, parser: P) -> Branch<'a, T, P::Error>
12where
13	P: Parser<char, T> + 'a
14{
15	Branch {
16		branches: vec![(begin, Box::new(parser))]
17	}
18}
19
20impl<'a, T, E> Branch<'a, T, E>
21where
22	E: chumsky::Error<char> + 'a
23{
24	/// Branch into another parser if the first branch didn't match.
25	pub fn or_branch<P>(mut self, begin: &'a str, parser: P) -> Self
26	where
27		P: Parser<char, T, Error = E> + 'a
28	{
29		self.branches.push((begin, Box::new(parser)));
30		self
31	}
32
33	/// Add an else branch if non of the branches succeeded.
34	pub fn or_else<B>(self, branch: B) -> Box<dyn Parser<char, T, Error = E> + 'a>
35	where
36		B: Fn(String) -> T + 'a,
37		T: 'a
38	{
39		let mut parser: Box<dyn Parser<char, T, Error = E> + 'a> = Box::new(
40			not_containing(
41				self.branches
42					.iter()
43					.map(|(pattern, _)| *pattern)
44					.collect::<Vec<_>>()
45			)
46			.map(branch)
47		);
48
49		for (pattern, branch) in self.branches {
50			parser = Box::new(choice((parser, just(pattern).ignore_then(branch))));
51		}
52
53		parser
54	}
55}
56
57#[cfg(test)]
58mod tests {
59	use super::*;
60
61	#[derive(Debug, Eq, PartialEq)]
62	enum Token {
63		Foo,
64		Bar,
65		Comment,
66		Verbatim(String)
67	}
68
69	fn test_lexer() -> impl Parser<char, Token, Error = Simple<char>> {
70		branch(
71			"{{",
72			just("foo").then_ignore(just("}}")).map(|_| Token::Foo)
73		)
74		.or_branch(
75			"{%",
76			just("bar").then_ignore(just("%}")).map(|_| Token::Bar)
77		)
78		.or_branch(
79			"/*",
80			just("TODO").then_ignore(just("*/")).map(|_| Token::Comment)
81		)
82		.or_else(Token::Verbatim)
83		.then_ignore(end())
84	}
85
86	#[test]
87	fn parse_foo() {
88		let token = test_lexer().parse("{{foo}}");
89		assert_eq!(token, Ok(Token::Foo));
90	}
91
92	#[test]
93	fn parse_bar() {
94		let token = test_lexer().parse("{%bar%}");
95		assert_eq!(token, Ok(Token::Bar));
96	}
97
98	#[test]
99	fn parse_comment() {
100		let token = test_lexer().parse("/*TODO*/");
101		assert_eq!(token, Ok(Token::Comment));
102	}
103
104	#[test]
105	fn parse_verbatim() {
106		let token = test_lexer().parse("just some random text");
107		assert_eq!(
108			token,
109			Ok(Token::Verbatim("just some random text".to_owned()))
110		);
111	}
112
113	#[test]
114	fn parse_foo_unclosed() {
115		let token = test_lexer().parse("{{foo}");
116		assert_matches!(token, Err(_));
117	}
118
119	#[test]
120	fn parse_bar_unclosed() {
121		let token = test_lexer().parse("{%foo%");
122		assert_matches!(token, Err(_));
123	}
124
125	#[test]
126	fn parse_comment_unclosed() {
127		let token = test_lexer().parse("/*TODO//");
128		assert_matches!(token, Err(_));
129	}
130
131	#[test]
132	fn parse_invalid() {
133		let token = test_lexer().parse("foo{{bar");
134		assert_matches!(token, Err(_));
135	}
136}