Skip to main content

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::{LocatingSlice, Parser, error::ContextError};
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((
54                ParsedContext {
55                    initial,
56                    span,
57                    separator: 0..0,
58                },
59                entry,
60            )),
61        }
62    }
63
64    /// Parses the given `parser` object, separated with `separator`.
65    pub(super) fn parse_repeated<'i, Out, Sep, P, Q, E>(
66        &self,
67        parser: P,
68        separator: Q,
69        input: &'i str,
70    ) -> impl Iterator<Item = Result<(ParsedContext<'i>, Out), ParseError>> + 'i
71    where
72        Out: 'i,
73        Sep: 'i,
74        P: Parser<LocatingSlice<&'i str>, Out, E> + 'i,
75        Q: Parser<LocatingSlice<&'i str>, Sep, E> + 'i,
76        E: winnow::error::ParserError<&'i str, Inner = ContextError> + Debug + 'i,
77    {
78        ParsedIter {
79            parser,
80            separator,
81            initial: input,
82            input: LocatingSlice::new(input),
83            renderer: self.error_style.clone(),
84            _phantom: PhantomData,
85        }
86    }
87}
88
89/// Context information carrying the metadata of the entry.
90#[derive(Debug, PartialEq, Eq)]
91pub struct ParsedContext<'i> {
92    /// Initial input.
93    pub(super) initial: &'i str,
94    /// Span of the separator consumed right before the entry, may be empty.
95    pub(super) separator: Range<usize>,
96    /// Span of the parsed [`LedgerStatement`].
97    pub(super) span: Range<usize>,
98}
99
100impl ParsedContext<'_> {
101    /// Returns `true` if a blank line preceded the entry.
102    ///
103    /// Every statement parser consumes its own trailing line ending, so any
104    /// separator left to consume in between means at least one blank line.
105    pub(super) fn has_blank_line_before(&self) -> bool {
106        !self.separator.is_empty()
107    }
108
109    /// Computes the starting line number from this context.
110    /// Note this function is O(N), not a cheap function.
111    pub fn compute_line_start(&self) -> usize {
112        error::compute_line_number(self.initial, self.span.start)
113    }
114
115    /// Returns the [`str`] slice corresponding to this context.
116    pub fn as_str(&self) -> &str {
117        self.initial
118            .get(self.span.clone())
119            .expect("ParsedContext::span must be a valid UTF-8 boundary")
120    }
121
122    /// Returns the position of the parsed string within the original `&str`,
123    /// which can be used to find the position of the [`Tracked`][syntax::tracked::Tracked] item.
124    pub fn span(&self) -> ParsedSpan {
125        ParsedSpan(self.span.clone())
126    }
127}
128
129/// Range parsed with the given parser within the original input `&str`.
130#[derive(Debug)]
131pub struct ParsedSpan(Range<usize>);
132
133impl ParsedSpan {
134    /// Returns the span of the given span relative to this span.
135    pub fn resolve(&self, span: &syntax::tracked::TrackedSpan) -> Range<usize> {
136        let target = span.as_range();
137        clip(self.0.clone(), target)
138    }
139}
140
141fn clip(parent: Range<usize>, child: Range<usize>) -> Range<usize> {
142    let start = std::cmp::max(parent.start, child.start) - parent.start;
143    let end = std::cmp::min(parent.end, child.end) - parent.start;
144    start..end
145}
146
147/// Iterator to return parsed ledger entry one-by-one.
148struct ParsedIter<'i, Out, Sep, P, Q, E> {
149    parser: P,
150    separator: Q,
151    initial: &'i str,
152    input: LocatingSlice<&'i str>,
153    renderer: annotate_snippets::Renderer,
154    _phantom: PhantomData<(Out, Sep, E)>,
155}
156
157impl<'i, Out, Sep, P, Q, E> Iterator for ParsedIter<'i, Out, Sep, P, Q, E>
158where
159    P: Parser<LocatingSlice<&'i str>, Out, E>,
160    Q: Parser<LocatingSlice<&'i str>, Sep, E>,
161    E: winnow::error::ParserError<&'i str, Inner = ContextError> + Debug + 'i,
162{
163    type Item = Result<(ParsedContext<'i>, Out), ParseError>;
164
165    fn next(&mut self) -> Option<Self::Item> {
166        use winnow::stream::Stream as _;
167        let start = self.input.checkpoint();
168        self.next_impl()
169            .map_err(|e| {
170                ParseError::new(
171                    self.renderer.clone(),
172                    self.initial,
173                    self.input,
174                    start,
175                    e.into_inner()
176                        .expect("ParseIter doesn't work with streaming parse yet"),
177                )
178            })
179            .transpose()
180    }
181}
182
183impl<'i, Out, Sep, P, Q, E> ParsedIter<'i, Out, Sep, P, Q, E>
184where
185    P: Parser<LocatingSlice<&'i str>, Out, E>,
186    Q: Parser<LocatingSlice<&'i str>, Sep, E>,
187    E: winnow::error::ParserError<&'i str> + 'i,
188{
189    fn next_impl(&mut self) -> Result<Option<(ParsedContext<'i>, Out)>, E> {
190        let separator = self.separator.by_ref().span().parse_next(&mut self.input)?;
191        if self.input.is_empty() {
192            return Ok(None);
193        }
194        let (entry, span) = self
195            .parser
196            .by_ref()
197            .with_span()
198            .parse_next(&mut self.input)?;
199        Ok(Some((
200            ParsedContext {
201                initial: self.initial,
202                span,
203                separator,
204            },
205            entry,
206        )))
207    }
208}