1#![allow(clippy::result_large_err)]
8
9use std::str::FromStr;
10
11use pest::{Parser, Span, error::ErrorVariant, iterators::Pair};
12
13use super::message::{
14 ArgumentReference, Message, PaddingSpecifier, Part, Placeholder, TypeSpecifier,
15};
16
17#[derive(pest_derive::Parser)]
18#[grammar = "sprintf/grammar.pest"]
19struct SprintfParser;
20
21pub type Error = pest::error::Error<Rule>;
22type Result<T, E = Error> = std::result::Result<T, E>;
23
24fn unexpected_rule_error(pair: &Pair<Rule>) -> Error {
25 Error::new_from_span(
26 ErrorVariant::CustomError {
27 message: format!("Unexpected rule: {:?}", pair.as_rule()),
28 },
29 pair.as_span(),
30 )
31}
32
33fn ensure_end_of_pairs(pairs: &mut pest::iterators::Pairs<Rule>, span: Span<'_>) -> Result<()> {
34 if pairs.next().is_none() {
35 Ok(())
36 } else {
37 Err(Error::new_from_span(
38 ErrorVariant::CustomError {
39 message: String::from("Expected end of pairs"),
40 },
41 span,
42 ))
43 }
44}
45
46fn next_pair<'i>(
47 pairs: &mut pest::iterators::Pairs<'i, Rule>,
48 span: Span<'i>,
49) -> Result<Pair<'i, Rule>> {
50 pairs.next().ok_or_else(|| {
51 Error::new_from_span(
52 ErrorVariant::CustomError {
53 message: String::from("Expected pair"),
54 },
55 span,
56 )
57 })
58}
59
60fn ensure_rule_type(pair: &Pair<Rule>, rule: Rule) -> Result<()> {
61 if pair.as_rule() == rule {
62 Ok(())
63 } else {
64 Err(unexpected_rule_error(pair))
65 }
66}
67
68fn interpret_ident(pair: &Pair<Rule>) -> Result<String> {
69 ensure_rule_type(pair, Rule::ident)?;
70 Ok(pair.as_str().to_owned())
71}
72
73fn interpret_number(pair: &Pair<Rule>) -> Result<usize> {
74 ensure_rule_type(pair, Rule::number)?;
75 pair.as_str().parse().map_err(|e| {
76 Error::new_from_span(
77 ErrorVariant::CustomError {
78 message: format!("Failed to parse number: {e}"),
79 },
80 pair.as_span(),
81 )
82 })
83}
84
85fn interpret_arg_named(pair: Pair<Rule>) -> Result<ArgumentReference> {
86 ensure_rule_type(&pair, Rule::arg_named)?;
87 let span = pair.as_span();
88 let mut pairs = pair.into_inner();
89
90 let ident = next_pair(&mut pairs, span)?;
91 let ident = interpret_ident(&ident)?;
92
93 ensure_end_of_pairs(&mut pairs, span)?;
94 Ok(ArgumentReference::Named(ident))
95}
96
97fn interpret_arg_indexed(pair: Pair<Rule>) -> Result<ArgumentReference> {
98 ensure_rule_type(&pair, Rule::arg_indexed)?;
99 let span = pair.as_span();
100 let mut pairs = pair.into_inner();
101
102 let number = next_pair(&mut pairs, span)?;
103 let number = interpret_number(&number)?;
104
105 ensure_end_of_pairs(&mut pairs, span)?;
106 Ok(ArgumentReference::Indexed(number))
107}
108
109fn interpret_padding_specifier(pair: &Pair<Rule>) -> Result<PaddingSpecifier> {
110 ensure_rule_type(pair, Rule::padding_specifier)?;
111 let specifier: Vec<char> = pair.as_str().chars().collect();
112
113 let specifier = match specifier[..] {
114 ['0'] => PaddingSpecifier::Zero,
115 ['\'', c] => PaddingSpecifier::Char(c),
116 ref specifier => {
117 return Err(Error::new_from_span(
118 ErrorVariant::CustomError {
119 message: format!("Unexpected padding specifier: {specifier:?}"),
120 },
121 pair.as_span(),
122 ));
123 }
124 };
125
126 Ok(specifier)
127}
128
129fn interpret_width(pair: Pair<Rule>) -> Result<usize> {
130 ensure_rule_type(&pair, Rule::width)?;
131 let span = pair.as_span();
132 let mut pairs = pair.into_inner();
133
134 let number = next_pair(&mut pairs, span)?;
135 let number = interpret_number(&number)?;
136
137 ensure_end_of_pairs(&mut pairs, span)?;
138 Ok(number)
139}
140
141fn interpret_precision(pair: Pair<Rule>) -> Result<usize> {
142 ensure_rule_type(&pair, Rule::precision)?;
143 let span = pair.as_span();
144 let mut pairs = pair.into_inner();
145
146 let number = next_pair(&mut pairs, span)?;
147 let number = interpret_number(&number)?;
148
149 ensure_end_of_pairs(&mut pairs, span)?;
150 Ok(number)
151}
152
153fn interpret_type_specifier(pair: &Pair<Rule>) -> Result<TypeSpecifier> {
154 ensure_rule_type(pair, Rule::type_specifier)?;
155 let specifier: Vec<char> = pair.as_str().chars().collect();
156
157 let type_specifier = match specifier[..] {
158 ['b'] => TypeSpecifier::BinaryNumber,
159 ['c'] => TypeSpecifier::CharacterAsciiValue,
160 ['d'] => TypeSpecifier::DecimalNumber,
161 ['i'] => TypeSpecifier::IntegerNumber,
162 ['e'] => TypeSpecifier::ScientificNotation,
163 ['u'] => TypeSpecifier::UnsignedDecimalNumber,
164 ['f'] => TypeSpecifier::FloatingPointNumber,
165 ['g'] => TypeSpecifier::FloatingPointNumberWithSignificantDigits,
166 ['o'] => TypeSpecifier::OctalNumber,
167 ['s'] => TypeSpecifier::String,
168 ['t'] => TypeSpecifier::TrueOrFalse,
169 ['T'] => TypeSpecifier::TypeOfArgument,
170 ['v'] => TypeSpecifier::PrimitiveValue,
171 ['x'] => TypeSpecifier::HexadecimalNumberLowercase,
172 ['X'] => TypeSpecifier::HexadecimalNumberUppercase,
173 ['j'] => TypeSpecifier::Json,
174 _ => {
175 return Err(Error::new_from_span(
176 ErrorVariant::CustomError {
177 message: String::from("Unexpected type specifier"),
178 },
179 pair.as_span(),
180 ));
181 }
182 };
183
184 Ok(type_specifier)
185}
186
187fn interpret_placeholder(pair: Pair<Rule>) -> Result<Placeholder> {
188 ensure_rule_type(&pair, Rule::placeholder)?;
189 let span = pair.as_span();
190 let mut pairs = pair.into_inner();
191 let mut current_pair = next_pair(&mut pairs, span)?;
192
193 let argument = if current_pair.as_rule() == Rule::arg_named {
194 let argument = interpret_arg_named(current_pair)?;
195 current_pair = next_pair(&mut pairs, span)?;
196 Some(argument)
197 } else if current_pair.as_rule() == Rule::arg_indexed {
198 let argument = interpret_arg_indexed(current_pair)?;
199 current_pair = next_pair(&mut pairs, span)?;
200 Some(argument)
201 } else {
202 None
203 };
204
205 let plus_sign = if current_pair.as_rule() == Rule::plus_sign {
206 current_pair = next_pair(&mut pairs, span)?;
207 true
208 } else {
209 false
210 };
211
212 let padding_specifier = if current_pair.as_rule() == Rule::padding_specifier {
213 let padding_specifier = interpret_padding_specifier(¤t_pair)?;
214 current_pair = next_pair(&mut pairs, span)?;
215 Some(padding_specifier)
216 } else {
217 None
218 };
219
220 let left_align = if current_pair.as_rule() == Rule::left_align {
221 current_pair = next_pair(&mut pairs, span)?;
222 true
223 } else {
224 false
225 };
226
227 let width = if current_pair.as_rule() == Rule::width {
228 let width = interpret_width(current_pair)?;
229 current_pair = next_pair(&mut pairs, span)?;
230 Some(width)
231 } else {
232 None
233 };
234
235 let precision = if current_pair.as_rule() == Rule::precision {
236 let precision = interpret_precision(current_pair)?;
237 current_pair = next_pair(&mut pairs, span)?;
238 Some(precision)
239 } else {
240 None
241 };
242
243 let type_specifier = interpret_type_specifier(¤t_pair)?;
244
245 ensure_end_of_pairs(&mut pairs, span)?;
246
247 Ok(Placeholder {
248 type_specifier,
249 requested_argument: argument,
250 plus_sign,
251 padding_specifier,
252 left_align,
253 width,
254 precision,
255 })
256}
257
258impl FromStr for Message {
259 type Err = Error;
260
261 fn from_str(input: &str) -> Result<Self, Self::Err> {
262 SprintfParser::parse(Rule::message, input)?
263 .filter(|pair| pair.as_rule() != Rule::EOI)
265 .map(|pair| match pair.as_rule() {
266 Rule::text => Ok(pair.as_str().to_owned().into()),
267 Rule::percent => Ok(Part::Percent),
268 Rule::placeholder => Ok(interpret_placeholder(pair)?.into()),
269 _ => Err(unexpected_rule_error(&pair)),
270 })
271 .collect()
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn test_parser() {
281 let cases = [
283 "%%",
284 "%'_-5s",
285 "%'_5s",
286 "%+'_10d",
287 "%+.1f",
288 "%+010d",
289 "%+d",
290 "%+f",
291 "%+i",
292 "%-5s",
293 "%.1f",
294 "%.1g",
295 "%.1t",
296 "%.3g",
297 "%.6g",
298 "%0-5s",
299 "%02u",
300 "%05d",
301 "%05i",
302 "%05s",
303 "%2$s %3$s a %1$s",
304 "%2j",
305 "%5.1s",
306 "%5.5s",
307 "%5s",
308 "%8.3f",
309 "%T",
310 "%X",
311 "%b",
312 "%c",
313 "%d",
314 "%e",
315 "%f",
316 "%f %f",
317 "%f %s",
318 "%g",
319 "%i",
320 "%j",
321 "%o",
322 "%s",
323 "%t",
324 "%u",
325 "%v",
326 "%x",
327 "Hello %(who)s!",
328 ];
329
330 for case in cases {
331 let result: Result<Message> = case.parse();
332 assert!(result.is_ok(), "Failed to parse: {case}");
333 let message = result.unwrap();
334 assert_eq!(message.to_string(), *case);
335 }
336 }
337}