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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
21pub struct YesAnd<'input, T> {
22 pub yes: T,
24 pub and: &'input str,
26 pub could_also: Vec<String>,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, Hash)]
32pub enum UpError {
33 Oops { message: String },
35 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
53pub 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 .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}