Skip to main content

alpm_parsers/error/
layer.rs

1//! A single [`Layer`] of a [`ParseStack`] error.
2
3#[cfg(doc)]
4use crate::error::ParseStack;
5use crate::error::context::StringContext;
6
7/// One level of nesting in our parsing stack.
8///
9/// A [`Layer`] is used as part of the [`ParseStack`] struct and created when any kind of parsing
10/// error backtracks/bubbles up past a [`LayerParser`].
11///
12/// All context messages are stored as owned [`String`]s (see [`StringContext`]), so they may be
13/// created at runtime, e.g. by our translation system.
14///
15/// [`LayerParser`]: crate::error::LayerParser
16#[derive(Clone, Debug)]
17pub(crate) struct Layer {
18    /// Name passed to [`LayerExt::layer`](crate::error::LayerExt).
19    pub(crate) name: String,
20    /// Absolute byte offset where this layer's parser began.
21    ///
22    /// [`ParseStack::at`] represents the respective end to this pointer.
23    pub(crate) start: usize,
24    /// Every context call collected within this layer, in order they were attached.
25    // Idea: If the string allocations turn out to be too performance intensive, we could think
26    // about live-concatenating any context strings. I.e. Layer would have three optional strings,
27    // one respective string for layer, description and expected items.
28    // This would move some of the formatting to the Layer, but would save **lots** of string
29    // allocatins.
30    pub(crate) contexts: Vec<StringContext>,
31}
32
33/// A small wrapper around [`Layer`], which allows us to conveniently handle anonymous layers.
34///
35/// Anonymous layers are [`ParseStack::pending`] context that hasn't been added to a layer yet.
36/// This means that the outermost parser layer has not been closed.
37///
38/// Since we still want to show this context to users, we need some form of uniform representation
39/// for it.
40#[derive(Clone, Copy, Debug)]
41pub(crate) enum LayerRef<'layer> {
42    Anonymous {
43        start: usize,
44        contexts: &'layer [StringContext],
45    },
46    Named(&'layer Layer),
47}
48
49impl<'a> LayerRef<'a> {
50    /// The byte offset where this layer begins.
51    pub(crate) fn start(self) -> usize {
52        match self {
53            LayerRef::Anonymous { start, .. } => start,
54            LayerRef::Named(layer) => layer.start,
55        }
56    }
57
58    /// The layer's name.
59    ///
60    /// `None` in case of an anonymous layer.
61    pub(crate) fn name(self) -> Option<&'a str> {
62        match self {
63            LayerRef::Anonymous { .. } => None,
64            LayerRef::Named(layer) => Some(&layer.name),
65        }
66    }
67
68    /// Returns the optional [`StringContext::Label`] messages, joined with commas.
69    ///
70    /// Returns [`None`] if there are no [`StringContext::Label`] items.
71    pub(crate) fn label_message(self) -> Option<String> {
72        let message: Vec<&str> = self
73            .contexts()
74            .iter()
75            .filter_map(StringContext::label)
76            .collect();
77
78        if message.is_empty() {
79            None
80        } else {
81            Some(message.join(", "))
82        }
83    }
84
85    /// All [`StringContext::Description`] items joined in a [`String`], without delimiter.
86    ///
87    /// Returns [`None`], if no descriptions exist or if they are all empty.
88    pub(crate) fn descriptions(self) -> Option<String> {
89        let mut description = String::new();
90        for context in self.contexts() {
91            let StringContext::Description(desc) = context else {
92                continue;
93            };
94            description.push_str(desc);
95        }
96
97        Some(description).filter(|d| !d.is_empty())
98    }
99
100    /// Returns a list of all [`StringContext`] variants with literal values.
101    pub(crate) fn expected_literals(self) -> Vec<String> {
102        self.contexts()
103            .iter()
104            .filter_map(StringContext::expected)
105            .collect()
106    }
107
108    /// Returns the very first [`StringContext::Label`] on this layer.
109    pub(crate) fn label(self) -> Option<&'a str> {
110        self.contexts().iter().find_map(StringContext::label)
111    }
112
113    /// Returns a list of all [`StringContext`] values.
114    fn contexts(self) -> &'a [StringContext] {
115        match self {
116            LayerRef::Anonymous { contexts, .. } => contexts,
117            LayerRef::Named(layer) => &layer.contexts,
118        }
119    }
120}