okane_core/parse/
adaptor.rs

1//! Provides utilities to use winnow parser easily with [`ParseError`].
2
3use std::{fmt::Debug, marker::PhantomData, ops::Range};
4
5use winnow::{error::ContextError, LocatingSlice, Parser};
6
7use crate::syntax;
8
9use super::error::{self, ParseError};
10
11/// Options to control parse behavior.
12#[derive(Debug, Clone)]
13pub struct ParseOptions {
14    error_style: annotate_snippets::Renderer,
15}
16
17impl Default for ParseOptions {
18    fn default() -> Self {
19        Self {
20            error_style: annotate_snippets::Renderer::plain(),
21        }
22    }
23}
24
25impl ParseOptions {
26    /// Sets the given [`annotate_snippets::Renderer`] to `self`.
27    pub fn with_error_style(mut self, error_style: annotate_snippets::Renderer) -> Self {
28        self.error_style = error_style;
29        self
30    }
31
32    pub(super) fn parse_single<'i, Out, P>(
33        &self,
34        parser: P,
35        input: &'i str,
36    ) -> Result<(ParsedContext<'i>, Out), ParseError>
37    where
38        Out: 'i,
39        P: Parser<LocatingSlice<&'i str>, Out, ContextError> + 'i,
40    {
41        use winnow::stream::Stream as _;
42        let initial = input;
43        let input = LocatingSlice::new(input);
44        let start = input.checkpoint();
45        match parser.with_span().parse(input) {
46            Err(e) => Err(ParseError::new(
47                self.error_style.clone(),
48                initial,
49                input,
50                start,
51                e.into_inner(),
52            )),
53            Ok((entry, span)) => Ok((ParsedContext { initial, span }, entry)),
54        }
55    }
56
57    /// Parses the given `parser` object, separated with `separator`.
58    pub(super) fn parse_repeated<'i, Out, Sep, P, Q, E>(
59        &self,
60        parser: P,
61        separator: Q,
62        input: &'i str,
63    ) -> impl Iterator<Item = Result<(ParsedContext<'i>, Out), ParseError>> + 'i
64    where
65        Out: 'i,
66        Sep: 'i,
67        P: Parser<LocatingSlice<&'i str>, Out, E> + 'i,
68        Q: Parser<LocatingSlice<&'i str>, Sep, E> + 'i,
69        E: winnow::error::ParserError<&'i str, Inner = ContextError> + Debug + 'i,
70    {
71        ParsedIter {
72            parser,
73            separator,
74            initial: input,
75            input: LocatingSlice::new(input),
76            renderer: self.error_style.clone(),
77            _phantom: PhantomData,
78        }
79    }
80}
81
82/// Context information carrying the metadata of the entry.
83#[derive(Debug, PartialEq, Eq)]
84pub struct ParsedContext<'i> {
85    pub(super) initial: &'i str,
86    pub(super) span: Range<usize>,
87}
88
89impl ParsedContext<'_> {
90    /// Computes the starting line number from this context.
91    /// Note this function is O(N), not a cheap function.
92    pub fn compute_line_start(&self) -> usize {
93        error::compute_line_number(self.initial, self.span.start)
94    }
95
96    /// Returns the [`str`] slice corresponding to this context.
97    pub fn as_str(&self) -> &str {
98        self.initial
99            .get(self.span.clone())
100            .expect("ParsedContext::span must be a valid UTF-8 boundary")
101    }
102
103    /// Returns the position of the parsed string within the original `&str`,
104    /// which can be used to find the position of the [`Tracked`][syntax::tracked::Tracked] item.
105    pub fn span(&self) -> ParsedSpan {
106        ParsedSpan(self.span.clone())
107    }
108}
109
110/// Range parsed with the given parser within the original input `&str`.
111#[derive(Debug)]
112pub struct ParsedSpan(Range<usize>);
113
114impl ParsedSpan {
115    /// Returns the span of the given span relative to this span.
116    pub fn resolve(&self, span: &syntax::tracked::TrackedSpan) -> Range<usize> {
117        let target = span.as_range();
118        clip(self.0.clone(), target)
119    }
120}
121
122fn clip(parent: Range<usize>, child: Range<usize>) -> Range<usize> {
123    let start = std::cmp::max(parent.start, child.start) - parent.start;
124    let end = std::cmp::min(parent.end, child.end) - parent.start;
125    start..end
126}
127
128/// Iterator to return parsed ledger entry one-by-one.
129struct ParsedIter<'i, Out, Sep, P, Q, E> {
130    parser: P,
131    separator: Q,
132    initial: &'i str,
133    input: LocatingSlice<&'i str>,
134    renderer: annotate_snippets::Renderer,
135    _phantom: PhantomData<(Out, Sep, E)>,
136}
137
138impl<'i, Out, Sep, P, Q, E> Iterator for ParsedIter<'i, Out, Sep, P, Q, E>
139where
140    P: Parser<LocatingSlice<&'i str>, Out, E>,
141    Q: Parser<LocatingSlice<&'i str>, Sep, E>,
142    E: winnow::error::ParserError<&'i str, Inner = ContextError> + Debug + 'i,
143{
144    type Item = Result<(ParsedContext<'i>, Out), ParseError>;
145
146    fn next(&mut self) -> Option<Self::Item> {
147        use winnow::stream::Stream as _;
148        let start = self.input.checkpoint();
149        self.next_impl()
150            .map_err(|e| {
151                ParseError::new(
152                    self.renderer.clone(),
153                    self.initial,
154                    self.input,
155                    start,
156                    e.into_inner()
157                        .expect("ParseIter doesn't work with streaming parse yet"),
158                )
159            })
160            .transpose()
161    }
162}
163
164impl<'i, Out, Sep, P, Q, E> ParsedIter<'i, Out, Sep, P, Q, E>
165where
166    P: Parser<LocatingSlice<&'i str>, Out, E>,
167    Q: Parser<LocatingSlice<&'i str>, Sep, E>,
168    E: winnow::error::ParserError<&'i str> + 'i,
169{
170    fn next_impl(&mut self) -> Result<Option<(ParsedContext<'i>, Out)>, E> {
171        self.separator.parse_next(&mut self.input)?;
172        if self.input.is_empty() {
173            return Ok(None);
174        }
175        let (entry, span) = self
176            .parser
177            .by_ref()
178            .with_span()
179            .parse_next(&mut self.input)?;
180        Ok(Some((
181            ParsedContext {
182                initial: self.initial,
183                span,
184            },
185            entry,
186        )))
187    }
188}