1pub(crate) mod error;
2pub(crate) mod lexer;
3pub mod util;
4
5use chumsky::{
6 error::{Rich, RichReason},
7 extra,
8 input::ValueInput,
9 prelude::*,
10 span::SimpleSpan,
11 Parser as ChumskyParser,
12};
13
14pub(crate) trait TokenSource<'src>:
25 ValueInput<'src, Token = lexer::Token<'src>, Span = SimpleSpan>
26{
27}
28
29impl<'src, I> TokenSource<'src> for I where
31 I: ValueInput<'src, Token = lexer::Token<'src>, Span = SimpleSpan>
32{
33}
34
35pub(crate) type TokenError<'src> = extra::Err<Rich<'src, lexer::Token<'src>>>;
36
37pub(crate) trait InternalParser<'src>: Sized {
49 fn parser<I>() -> impl ChumskyParser<'src, I, Self, TokenError<'src>>
50 where
51 I: TokenSource<'src>;
52}
53
54#[allow(private_bounds)]
61pub trait Parser<'src>: InternalParser<'src> {
62 fn parse(
63 src: impl TokenSource<'src>,
64 ) -> Result<Self, Vec<RichReason<'src, lexer::Token<'src>>>> {
65 let result = <Self as InternalParser<'src>>::parser::<_>().parse(src);
66 if result.has_errors() {
67 Err(result.errors().map(|e| e.reason().clone()).collect())
68 } else {
69 Ok(result.unwrap())
70 }
71 }
72}
73
74pub(crate) fn number<'a, T, I>() -> impl ChumskyParser<'a, I, T, TokenError<'a>>
76where
77 T: std::str::FromStr,
78 T::Err: std::fmt::Debug,
79 I: TokenSource<'a>,
80{
81 select! { lexer::Token::QuotedText(s) => s }.try_map(|s: &str, span| {
82 s.parse::<T>()
83 .map_err(|_| Rich::custom(span, "integer out of range"))
84 })
85}
86
87pub(crate) fn boolean<'a, I>() -> impl ChumskyParser<'a, I, bool, TokenError<'a>>
89where
90 I: TokenSource<'a>,
91{
92 quoted_string("1").or(quoted_string("0")).map(|v| match v {
93 "1" => true,
94 "0" => false,
95 _ => unreachable!(),
96 })
97}
98
99pub(crate) fn any_quoted_string<'src, I>(
101) -> impl ChumskyParser<'src, I, &'src str, TokenError<'src>>
102where
103 I: TokenSource<'src>,
104{
105 select! { lexer::Token::QuotedText(s) => s }
106}
107
108pub(crate) fn quoted_string<'src, I>(
111 input: &'src str,
112) -> impl ChumskyParser<'src, I, &'src str, TokenError<'src>>
113where
114 I: TokenSource<'src>,
115{
116 select! {
117 lexer::Token::QuotedText(s) if s == input => s
118 }
119}
120
121pub(crate) fn key_value<'src, I>(
124 key: &'src str,
125) -> impl ChumskyParser<'src, I, &'src str, TokenError<'src>>
126where
127 I: TokenSource<'src>,
128{
129 quoted_string(key).ignore_then(any_quoted_string())
130}
131
132pub(crate) fn key_value_numeric<'src, T, I>(
135 key: &'src str,
136) -> impl ChumskyParser<'src, I, T, TokenError<'src>>
137where
138 T: std::str::FromStr,
139 T::Err: std::fmt::Debug,
140 I: TokenSource<'src>,
141{
142 quoted_string(key).ignore_then(number::<T, I>())
143}
144
145pub(crate) fn key_value_boolean<'src, I>(
148 key: &'src str,
149) -> impl ChumskyParser<'src, I, bool, TokenError<'src>>
150where
151 I: TokenSource<'src>,
152{
153 quoted_string(key).ignore_then(boolean())
154}
155
156pub(crate) fn open_block<'src, I>(
163 block: &'src str,
164) -> impl ChumskyParser<'src, I, (), TokenError<'src>>
165where
166 I: TokenSource<'src>,
167{
168 just(lexer::Token::Ident(block))
169 .ignore_then(just(lexer::Token::LBracket))
170 .ignored()
171}
172
173pub(crate) fn close_block<'src, I>() -> impl ChumskyParser<'src, I, (), TokenError<'src>>
175where
176 I: TokenSource<'src>,
177{
178 just(lexer::Token::RBracket).ignored()
179}
180
181pub(crate) fn skip_unknown_block<'src, I>() -> impl ChumskyParser<'src, I, (), TokenError<'src>>
184where
185 I: TokenSource<'src>,
186{
187 recursive(|skip_block| {
188 any()
189 .filter(|tok| matches!(tok, lexer::Token::Ident(_)))
190 .ignore_then(just(lexer::Token::LBracket))
191 .ignore_then(
192 none_of([lexer::Token::LBracket, lexer::Token::RBracket])
193 .ignored()
194 .or(skip_block)
195 .repeated(),
196 )
197 .then_ignore(just(lexer::Token::RBracket))
198 .ignored()
199 })
200}
201
202#[cfg(test)]
203mod tests {
204 use crate::util::lex;
205
206 use super::*;
207 use chumsky::Parser;
208
209 #[test]
210 fn test_number() {
211 let stream = lex("\"12345\"");
212
213 let result = number::<u32, _>().parse(stream);
214 for e in result.errors() {
215 println!("error: {:?}", e.reason());
216 }
217 assert!(!result.has_errors());
218 assert_eq!(result.unwrap(), 12345);
219 }
220
221 #[test]
222 fn test_boolean() {
223 let stream = lex(r#""1""#);
224
225 let result = boolean::<_>().parse(stream);
226 assert!(!result.has_errors());
227 assert!(result.unwrap());
228 }
229
230 #[test]
231 fn test_key_value_numeric() {
232 let stream = lex(r#""num" "42""#);
233 let result = key_value_numeric::<u32, _>("num").parse(stream);
234 assert!(!result.has_errors());
235 assert_eq!(result.unwrap(), 42);
236 }
237
238 #[test]
239 fn test_open_close_block() {
240 let stream = lex("blk {");
241 let r1 = open_block("blk").parse(stream);
242 for e in r1.errors() {
243 println!("error: {:?}", e.reason());
244 }
245 assert!(!r1.has_errors());
246
247 let stream = lex("}");
248 let r2 = close_block().parse(stream);
249 for e in r1.errors() {
250 println!("error: {:?}", e.reason());
251 }
252 assert!(!r2.has_errors());
253 }
254}