Skip to main content

alpm_parsers/error/
parse_stack.rs

1//! The [`ParseStack`] error type.
2
3use std::sync::Arc;
4
5use winnow::{
6    error::{AddContext, FromExternalError, ParserError, StrContext},
7    stream::{Location, Stream},
8};
9
10#[cfg(doc)]
11use crate::error::{ContextExt, LayerExt, LayerParser};
12use crate::error::{
13    Input,
14    context::StringContext,
15    layer::{Layer, LayerRef},
16};
17
18/// Get a reference to the full original source from an [`Input`].
19///
20/// Internally, [`Input`] stores the full initial input. By copying its reference and
21/// resetting the pointer to the start, we get the the original string slice that is being parsed.
22///
23/// This is needed, as the offsets of each layer are relative to the start of the full string
24/// slice.
25fn full_source<'i>(input: &Input<'i>) -> &'i str {
26    let mut full = *input;
27    full.reset_to_start();
28    *full
29}
30
31/// A custom nested and span-aware parser error.
32///
33/// This error type is used across the ALPM project and provides well-formated and detailed parsing
34/// errors. It allows addition of context per layer/type. For more information on the formatting see
35/// the `Display` impl of `ParseStack`.
36#[derive(Clone, Debug)]
37pub struct ParseStack<'i> {
38    /// Byte offset of the deepest failure within the input.
39    pub(crate) at: usize,
40    /// An external error, for the case that the final parser failure happened outside of winnow.
41    pub external: Option<Arc<dyn std::error::Error + Send + Sync>>,
42    /// The [`StringContext`] instances that are not yet captured by a layer boundary.
43    pub(crate) pending: Vec<StringContext>,
44    /// All closed nesting layers, innermost first.
45    pub(crate) layers: Vec<Layer>,
46    /// The full original source text. Required for rendering.
47    pub(crate) source: &'i str,
48}
49
50impl<'i> ParseStack<'i> {
51    /// Moves all pending context calls into a new layer named `name`.
52    ///
53    /// This is called by the [`LayerParser`] in case an error unwinds.
54    pub(crate) fn close_layer(mut self, name: String, start: usize) -> Self {
55        let contexts = std::mem::take(&mut self.pending);
56        self.layers.push(Layer {
57            name,
58            start,
59            contexts,
60        });
61        self
62    }
63
64    /// Returns the innermost layer.
65    ///
66    /// Pending context belongs to the outermost "anonymous" layer that's not yet named.
67    /// If no named layer can be found, but there's some pending context, we return the pending
68    /// context as an anonymous layer as a fallback.
69    pub(crate) fn innermost(&self) -> LayerRef<'_> {
70        if let Some(layer) = self.layers.first() {
71            LayerRef::Named(layer)
72        } else {
73            self.anonymous_layer()
74        }
75    }
76
77    /// Returns the stack from outermost to innermost.
78    ///
79    /// Pending context is positioned first as an anonymous outermost layer.
80    pub(crate) fn layer_stack(&self) -> Vec<LayerRef<'_>> {
81        let mut layers = Vec::new();
82        if !self.pending.is_empty() {
83            layers.push(self.anonymous_layer());
84        }
85        layers.extend(self.layers.iter().rev().map(LayerRef::Named));
86        layers
87    }
88
89    /// Returns all "expected" style contexts of the innermost layer that has any.
90    ///
91    /// The layers are searched from the innermost outwards, with pending context being the
92    /// outermost anonymous layer. If there's no hit on the innermost layer, we just take the next
93    /// best layer with such context to at least provide some information to the user.
94    ///
95    /// Returns an empty list if no layer has any "expected" style context.
96    pub(crate) fn innermost_expected(&self) -> Vec<String> {
97        self.layer_stack()
98            .into_iter()
99            .rev()
100            .map(LayerRef::expected_literals)
101            .find(|literals| !literals.is_empty())
102            .unwrap_or_default()
103    }
104
105    /// Returns the "headline" for the error.
106    ///
107    /// Tries to get the best match available in the following order:
108    /// - [`StringContext::Label`] name of the innermost layer
109    /// - Name of first non-anonymous string
110    /// - A simple fallback to the static string `"invalid"`.
111    pub(crate) fn headline(&self) -> String {
112        self.innermost()
113            .label()
114            .map(ToString::to_string)
115            .or_else(|| self.layers.first().map(|layer| layer.name.clone()))
116            .unwrap_or_else(|| "input".to_owned())
117    }
118
119    /// Takes the current pending context and return it as an anonymous layer.
120    fn anonymous_layer(&self) -> LayerRef<'_> {
121        LayerRef::Anonymous {
122            start: self.at,
123            contexts: &self.pending,
124        }
125    }
126}
127
128// The following trait implementations are wiring logic that's necessary to make our error type a
129// valid winnow error.
130
131impl<'i> ParserError<Input<'i>> for ParseStack<'i> {
132    type Inner = Self;
133
134    fn from_input(input: &Input<'i>) -> Self {
135        ParseStack {
136            at: input.current_token_start(),
137            external: None,
138            pending: Vec::new(),
139            layers: Vec::new(),
140            source: full_source(input),
141        }
142    }
143
144    /// When handling multiple parsing branches (`alt`), keep the failure that reached further into
145    /// the input.
146    // Note: This is somewhat of an experiment, but I assume that that branch should usually carry
147    // the more useful message, as it progressed further.
148    fn or(self, other: Self) -> Self {
149        if other.at >= self.at { other } else { self }
150    }
151
152    fn into_inner(self) -> Result<Self::Inner, Self> {
153        Ok(self)
154    }
155}
156
157// TODO: Remove this, once we fully migrated over to the new error type.
158impl<'i> AddContext<Input<'i>, StrContext> for ParseStack<'i> {
159    /// Add a new context entry to the current layer.
160    ///
161    /// This provides backwards compatibility with winnow's [`Parser::context`] API.
162    /// The [`StrContext`] is internally converted to our owned `StringContext` representation.
163    ///
164    /// Entries are staged inside `Self` until the current layer is closed.
165    ///
166    /// [`Parser::context`]: winnow::Parser::context
167    fn add_context(
168        mut self,
169        _input: &Input<'i>,
170        _token_start: &<Input<'i> as Stream>::Checkpoint,
171        context: StrContext,
172    ) -> Self {
173        self.pending.push(context.into());
174        self
175    }
176}
177
178impl<'i, E: std::error::Error + Send + Sync + 'static> FromExternalError<Input<'i>, E>
179    for ParseStack<'i>
180{
181    fn from_external_error(input: &Input<'i>, e: E) -> Self {
182        ParseStack {
183            at: input.current_token_start(),
184            external: Some(Arc::new(e)),
185            pending: Vec::new(),
186            layers: Vec::new(),
187            source: full_source(input),
188        }
189    }
190}
191
192impl std::error::Error for ParseStack<'_> {}