parse_fmt_str/
lib.rs

1use nom::{
2    bytes::complete::{tag, take, take_till, take_while},
3    character::complete::{anychar, digit1, one_of},
4    combinator::{opt, peek},
5    Err, IResult, Needed,
6};
7use std::num::NonZeroUsize;
8use unicode_xid::UnicodeXID;
9
10fn parse_count(input: &str) -> IResult<&str, Count> {
11    let error = Err(Err::Error(nom::error::Error {
12        input: "",
13        code: nom::error::ErrorKind::Fail,
14    }));
15    if input.is_empty() {
16        return error;
17    }
18    if let (new_input, Some(arg)) = opt(parse_identifier)(input)? {
19        if let (new_new_input, Some(_)) = opt(tag("$"))(new_input)? {
20            Ok((new_new_input, Count::Parameter(Argument::Identifier(arg))))
21        } else {
22            Err(Err::Error(nom::error::Error {
23                input: new_input,
24                code: nom::error::ErrorKind::Fail,
25            }))
26        }
27    } else if let (new_input, Some(count)) = opt(digit1)(input)? {
28        Ok((new_input, Count::Integer(count.parse::<usize>().unwrap())))
29    } else {
30        error
31    }
32}
33
34fn parse_identifier(input: &str) -> IResult<&str, String> {
35    let (car, cdr) = (
36        input
37            .chars()
38            .nth(0)
39            .ok_or(Err::Incomplete(Needed::Size(unsafe {
40                NonZeroUsize::new_unchecked(1)
41            })))?,
42        &input[1..],
43    );
44    if !UnicodeXID::is_xid_start(car) {
45        return Err(Err::Error(nom::error::Error {
46            input: "",
47            code: nom::error::ErrorKind::Fail,
48        }));
49    }
50    let out: IResult<&str, &str> = take_while(UnicodeXID::is_xid_continue)(cdr);
51    let (input, cdr) = out?;
52    let out = format!("{}{}", car, cdr);
53    return Ok((input, out));
54}
55
56fn parse_argument(input: &str) -> IResult<&str, Argument> {
57    if let (new_input, Some(arg)) = opt(parse_identifier)(input)? {
58        Ok((new_input, Argument::Identifier(arg)))
59    } else if let (new_input, Some(arg)) = opt(digit1)(input)? {
60        Ok((new_input, Argument::Integer(arg.parse::<usize>().unwrap())))
61    } else {
62        Err(Err::Error(nom::error::Error {
63            input: "",
64            code: nom::error::ErrorKind::Fail,
65        }))
66    }
67}
68
69pub fn parse_fmt_spec(input: &str) -> IResult<&str, FormatSlot> {
70    // NOTE: macros, maybe?
71    let (input, arg) = if let (input, Some(ident)) = opt(parse_argument)(input)? {
72        (input, Some(ident))
73    } else {
74        (input, None)
75    };
76    let (input, fmt_spec) = if let (input, Some(_)) = opt(tag(":"))(input)? {
77        let (input, fill) = if let (input, Some(fill)) = opt(anychar)(input)? {
78            (input, Some(fill))
79        } else {
80            (input, None)
81        };
82        let (input, align) =
83            if let (input, Some(align)) = opt(one_of("<^>"))(input)? {
84                (
85                    input,
86                    Some(match align {
87                        '<' => Align::Left,
88                        '^' => Align::Center,
89                        '>' => Align::Right,
90                        _ => {
91                            unreachable!()
92                        }
93                    }),
94                )
95            } else {
96                (input, None)
97            };
98        let (input, sign) = if let (input, Some(sign)) = opt(one_of("+-"))(input)? {
99            (
100                input,
101                Some(match sign {
102                    '+' => Sign::Positive,
103                    '-' => Sign::Negative,
104                    _ => {
105                        unreachable!()
106                    }
107                }),
108            )
109        } else {
110            (input, None)
111        };
112        let (input, alternate) = if let (input, Some(_)) = opt(tag("#"))(input)? {
113            (input, true)
114        } else {
115            (input, false)
116        };
117        let (input, pad_with_zeros) =
118            if let (input, Some(_)) = opt(tag("0"))(input)? {
119                (input, true)
120            } else {
121                (input, false)
122            };
123        let (input, width) = if let (input, Some(width)) = opt(parse_count)(input)?
124        {
125            (input, Some(width))
126        } else {
127            (input, None)
128        };
129        let (input, percision) = if let (input, Some(_)) = opt(tag("."))(input)? {
130            let input = input;
131            if let (input, Some(percision)) = opt(parse_count)(input)? {
132                (input, Some(Precision::Count(percision)))
133            } else if let (input, Some(_)) = opt(tag("*"))(input)? {
134                (input, Some(Precision::SpecifiedPrecision))
135            } else {
136                (input, None)
137            }
138        } else {
139            (input, None)
140        };
141        let (input, kind) =
142            if let (input, Some(kind)) = opt(one_of("?oxXpbeE"))(input)? {
143                match kind {
144                    'x' => {
145                        if let (input, Some(_)) = opt(tag("?"))(input)? {
146                            (input, Type::DebugLowerHex)
147                        } else {
148                            (input, Type::LowerHex)
149                        }
150                    }
151                    'X' => {
152                        if let (input, Some(_)) = opt(tag("?"))(input)? {
153                            (input, Type::DebugUpperHex)
154                        } else {
155                            (input, Type::UpperHex)
156                        }
157                    }
158                    _ => (
159                        input,
160                        match kind {
161                            '?' => Type::Debug,
162                            'o' => Type::Octal,
163                            'p' => Type::Pointer,
164                            'b' => Type::Binary,
165                            'e' => Type::LowerExp,
166                            'E' => Type::UpperExp,
167                            _ => {
168                                unreachable!()
169                            }
170                        },
171                    ),
172                }
173            } else {
174                (input, Type::None)
175            };
176        (
177            input,
178            Some(FormatSpec {
179                fill,
180                align,
181                sign,
182                alternate,
183                pad_with_zeros,
184                width,
185                percision,
186                kind,
187            }),
188        )
189    } else {
190        (input, None)
191    };
192    Ok((input, FormatSlot { arg, fmt_spec }))
193}
194
195#[derive(Debug, PartialEq, Eq)]
196enum State {
197    Text,
198    MaybeFormat,
199}
200
201pub fn parse_fmt_str(input: &str) -> Result<FormatString, &str> {
202    let mut strings: Vec<String> = vec![];
203    let mut slots: Vec<PossibleFormatSlot> = vec![];
204    let mut input: String = input.to_string();
205    let mut state: State = State::MaybeFormat;
206    loop {
207        println!(
208            "INPUT: {:?}, strings: {:?}, state: {:?}",
209            input, strings, state
210        );
211        if input.is_empty() {
212            break;
213        }
214        let part1: IResult<&str, &str> = peek(tag("{{"))(&input);
215        let part2: IResult<&str, &str> = peek(tag("}}"))(&input);
216        if !part1.is_err() || !part2.is_err() {
217            if state == State::MaybeFormat {
218                strings.push("".to_string());
219            }
220            let out: IResult<&str, &str> = take(1usize)(&input);
221            let (input_str, push2str) = out.unwrap();
222            let mut input_str = input_str.to_string();
223            slots.push(match push2str {
224                "{" => PossibleFormatSlot::LeftBrace,
225                "}" => PossibleFormatSlot::RightBrace,
226                _ => {
227                    unreachable!()
228                }
229            });
230            input_str.remove(0);
231            input = input_str.to_string();
232            state = State::MaybeFormat;
233            continue;
234        }
235        // *cracks knuckles*
236        if input.starts_with("{") {
237            if state == State::MaybeFormat {
238                strings.push("".to_string());
239                state = State::Text;
240            }
241            let out: IResult<&str, &str> = take_till(|chr| chr == '}')(&input);
242            let (input_str, format) = out.unwrap();
243            let mut input_str = input_str.to_string();
244            let mut format = format.to_string();
245            if format.is_empty() {
246                strings.push(input_str);
247                break;
248            }
249            if !input_str.is_empty() {
250                input_str.remove(0);
251            }
252            format.remove(0);
253            if format.is_empty() {
254                strings.push(input_str);
255                break;
256            }
257            input = input_str.to_string();
258            let next = parse_fmt_spec(&format);
259            if next.is_err() {
260                return Err("Invalid format string. (slot didn't parse)");
261            }
262            let (left, slot) = next.unwrap();
263            if !left.is_empty() {
264                return Err("Invalid format string. (slot had additional data)");
265            }
266            slots.push(PossibleFormatSlot::FormatSlot(slot));
267            state = State::MaybeFormat;
268        } else {
269            assert_eq!(state, State::MaybeFormat);
270            let cloned_input = input.clone();
271            let out: IResult<&str, &str> = take_till(|chr| chr == '{' || chr == '}')(&cloned_input);
272            let (input_str, push2str) = out.unwrap();
273            input = input_str.to_string();
274            strings.push(push2str.to_string());
275            state = State::Text;
276        }
277    }
278    if state == State::MaybeFormat {
279        strings.push("".to_string());
280    }
281    Ok(FormatString {
282        text: strings,
283        maybe_fmt: slots,
284    })
285}
286
287pub type Fill = char;
288
289#[derive(Debug, PartialEq, Eq)]
290pub enum Align {
291    Left,
292    Center,
293    Right,
294}
295
296#[derive(Debug, PartialEq, Eq)]
297pub enum Sign {
298    Positive,
299    Negative,
300}
301
302#[derive(Debug, PartialEq, Eq)]
303pub enum Count {
304    Parameter(Argument),
305    Integer(usize),
306}
307
308#[derive(Debug, PartialEq, Eq)]
309pub enum Precision {
310    Count(Count),
311    SpecifiedPrecision,
312}
313
314#[derive(Debug, PartialEq, Eq)]
315pub enum Type {
316    Debug,
317    DebugLowerHex,
318    DebugUpperHex,
319    Octal,
320    LowerHex,
321    UpperHex,
322    Pointer,
323    Binary,
324    LowerExp,
325    UpperExp,
326    None,
327}
328
329#[derive(Debug, PartialEq, Eq)]
330pub struct FormatSpec {
331    pub fill: Option<Fill>,
332    pub align: Option<Align>,
333    pub sign: Option<Sign>,
334    pub alternate: bool,
335    pub pad_with_zeros: bool,
336    pub width: Option<Count>,
337    pub percision: Option<Precision>,
338    pub kind: Type,
339}
340
341#[derive(Debug, PartialEq, Eq)]
342pub enum Argument {
343    Identifier(String),
344    Integer(usize),
345}
346
347#[derive(Debug, PartialEq, Eq)]
348pub struct FormatSlot {
349    pub arg: Option<Argument>,
350    pub fmt_spec: Option<FormatSpec>,
351}
352
353#[derive(Debug, PartialEq, Eq)]
354pub enum PossibleFormatSlot {
355    FormatSlot(FormatSlot),
356    LeftBrace,
357    RightBrace,
358}
359
360#[derive(Debug, PartialEq, Eq)]
361pub struct FormatString {
362    pub text: Vec<String>,
363    pub maybe_fmt: Vec<PossibleFormatSlot>,
364}