parse_up/
lib.rs

1use std::fmt::Display;
2
3use itertools::Itertools;
4
5use util::{chars_needed_to_complete, go_on, oops, yes_and};
6
7pub mod util;
8
9macro_rules! oops {
10    ($tt:tt) => {
11        Err(UpError::Oops {
12            message: format!($tt),
13        })
14    };
15}
16
17pub type UpResult<'input, T> = Result<YesAnd<'input, T>, UpError>;
18
19/// Successful parse so far.
20#[derive(Debug, Clone, PartialEq, Eq, Hash)]
21pub struct YesAnd<'input, T> {
22    /// The value.
23    pub yes: T,
24    /// The remaining input.
25    pub and: &'input str,
26    /// Other valid options.
27    pub could_also: Vec<String>,
28}
29
30/// Parsing couldn't continue.
31#[derive(Debug, Clone, PartialEq, Eq, Hash)]
32pub enum UpError {
33    /// Parse error.
34    Oops { message: String },
35    /// Suggestions to append to the current input which would make parsing succeed.
36    /// You should only return this when you're at the end of the input.
37    GoOn { go_on: Vec<String> },
38}
39
40pub trait UpParser<'input, T> {
41    fn parse(&self, input: &'input str) -> UpResult<'input, T>;
42}
43
44impl<'input, T, F> UpParser<'input, T> for F
45where
46    F: Fn(&'input str) -> UpResult<'input, T>,
47{
48    fn parse(&self, input: &'input str) -> UpResult<'input, T> {
49        self(input)
50    }
51}
52
53/// Takes the string `tag` from the input.
54/// ```
55/// use parse_up::{UpParser as _, tag, util::{yes_and, go_on, oops}};
56/// assert_eq!(
57///     tag("hello").parse(""),
58///     go_on(["hello"]),
59/// );
60/// assert_eq!(
61///     tag("hello").parse("hell"),
62///     go_on(["o"]),
63/// );
64/// assert_eq!(
65///     tag("hello").parse("hello"),
66///     yes_and("hello", ""),
67/// );
68/// assert_eq!(
69///     tag("hello").parse("hello, world!"),
70///     yes_and("hello", ", world!"),
71/// );
72/// assert_eq!(
73///     tag("hello").parse("world"),
74///     oops("expected hello, not world"),
75/// );
76/// ```
77pub fn tag<'input, 'tag>(tag: &'tag str) -> impl UpParser<'input, &'input str> + 'tag {
78    move |input: &'input str| match input.strip_prefix(tag) {
79        Some(rest) => yes_and(&input[..tag.len()], rest),
80        None => match chars_needed_to_complete(tag, input) {
81            Some("") => unreachable!("would've been caught in prefix"),
82            Some(suggestion) => go_on([suggestion]),
83            None => oops!("expected {tag}, not {input}"),
84        },
85    }
86}
87
88pub fn dictionary<'input, KeyT, ValueT>(
89    items: impl IntoIterator<Item = (KeyT, ValueT)>,
90) -> impl UpParser<'input, ValueT>
91where
92    KeyT: Display,
93    ValueT: Clone,
94{
95    let pairs = items
96        .into_iter()
97        .map(|(k, v)| (k.to_string(), v))
98        // largest keys first
99        .sorted_by_key(|(k, _v)| std::cmp::Reverse(k.clone()))
100        .collect::<Vec<_>>();
101    move |input: &str| todo!()
102}
103
104pub fn and_then<'input, L, R>(
105    left: impl UpParser<'input, L>,
106    right: impl UpParser<'input, R>,
107) -> impl UpParser<'input, (L, R)> {
108    move |input| todo!()
109}