esl_compiler/
cursor.rs

1use std::borrow::Borrow;
2use std::rc::Rc;
3
4use snafu::{OptionExt, Snafu};
5use strum::EnumMessage;
6
7use crate::diagnostics::DiagnosticError;
8use crate::location::{FileLocation, Uri};
9use crate::parser::Rule;
10
11/// Cursor error.
12#[derive(Clone, Debug, PartialEq, strum::EnumMessage, Snafu)]
13#[snafu(visibility(pub))]
14pub enum Error {
15    /// Expected an additional cursor, but the input was exhausted.
16    Exhausted { loc: FileLocation },
17    /// Expected a rule here, but it didn't show.
18    MissingRule { rule: Rule, loc: FileLocation },
19    /// Unexpected rule here.
20    UnexpectedRule { rule: Rule, loc: FileLocation },
21}
22impl Error {
23    /// Location of this error.
24    pub fn as_location(&self) -> FileLocation {
25        match self {
26            Self::Exhausted { loc } => loc.clone(),
27            Self::MissingRule { rule: _, loc } => loc.clone(),
28            Self::UnexpectedRule { rule: _, loc } => loc.clone(),
29        }
30    }
31    /// Documentation of each error.
32    pub fn as_documentation(&self) -> String {
33        self.get_documentation().unwrap_or_default().into()
34    }
35    /// Related information for this error variant.
36    pub fn as_related_information(&self) -> Vec<lsp_types::DiagnosticRelatedInformation> {
37        let mut info = vec![];
38        let location: lsp_types::Location = self.as_location().into();
39        info.push(lsp_types::DiagnosticRelatedInformation {
40            message: format!("Builder error: {}", self.as_documentation()),
41            location: location.clone(),
42        });
43        match self {
44            Self::MissingRule { loc: _, rule } => {
45                info.push(lsp_types::DiagnosticRelatedInformation {
46                    message: format!("Missing rule: {:?}", rule),
47                    location,
48                })
49            }
50            Self::UnexpectedRule { loc: _, rule } => {
51                info.push(lsp_types::DiagnosticRelatedInformation {
52                    message: format!("Unexpected rule: {:?}", rule),
53                    location,
54                })
55            }
56            _ => (),
57        };
58        info
59    }
60    /// Diagnostic to publish for this error.
61    pub fn as_diagnostic(&self) -> lsp_types::Diagnostic {
62        match self {
63            Self::UnexpectedRule { rule, loc } => {
64                if rule == &Rule::syntax_error {
65                    DiagnosticError::Syntax.diagnostic(loc.range, None)
66                } else {
67                    DiagnosticError::Internal.diagnostic(
68                        self.as_location().range,
69                        Some(self.as_related_information()),
70                    )
71                }
72            }
73            _ => DiagnosticError::Internal.diagnostic(
74                self.as_location().range,
75                Some(self.as_related_information()),
76            ),
77        }
78    }
79}
80impl MissingRuleSnafu<Rule, FileLocation> {
81    pub fn from<'a, T: Borrow<Cursors<'a>>>(cs: T, rule: Rule) -> Self {
82        let cs = cs.borrow();
83        MissingRuleSnafu {
84            rule,
85            loc: cs.as_location(),
86        }
87    }
88}
89impl<'a, T: Borrow<Cursor<'a>>> From<T> for ExhaustedSnafu<FileLocation> {
90    fn from(value: T) -> Self {
91        let value = value.borrow();
92        ExhaustedSnafu {
93            loc: value.as_location(),
94        }
95    }
96}
97impl<'a, T: Borrow<Cursor<'a>>> From<T> for UnexpectedRuleSnafu<Rule, FileLocation> {
98    fn from(value: T) -> Self {
99        let value = value.borrow();
100        UnexpectedRuleSnafu {
101            rule: value.as_rule(),
102            loc: value.as_location(),
103        }
104    }
105}
106
107/// Cursors over pairs.
108#[derive(Clone, Debug, PartialEq)]
109pub struct Cursors<'a> {
110    /// Pest Pairs iterator that holds the content.
111    pairs: pest::iterators::Pairs<'a, Rule>,
112    /// Outermost range over which these Cursors were created in the input.
113    range: lsp_types::Range,
114    /// Input URI.
115    uri: Uri,
116}
117impl<'a> Cursors<'a> {
118    /// Create a new cursors instance from a cursor's inner contents.
119    pub fn new(cursor: Cursor<'a>) -> Self {
120        Self {
121            range: cursor.as_range(),
122            pairs: cursor.pair.into_inner(),
123            uri: cursor.uri,
124        }
125    }
126    /// Location of this entire cursors object at creation.
127    pub fn as_location(&self) -> FileLocation {
128        FileLocation {
129            range: self.range,
130            uri: Rc::clone(&self.uri),
131        }
132    }
133    /// Require the next non-comment cursor.
134    pub fn req_next(&mut self) -> Result<Cursor<'a>, Error> {
135        self.next().with_context(|| ExhaustedSnafu {
136            loc: self.as_location(),
137        })
138    }
139    /// Find the optional first match for a given rule.
140    pub fn find_first(&mut self, rule: Rule) -> Option<Cursor<'a>> {
141        self.pairs
142            .find(|p| p.as_rule() == rule)
143            .map(|p| Cursor::new(p, Rc::clone(&self.uri)))
144    }
145    // Check whether these cursors contain any occurrence of a rule.
146    pub fn contains(&mut self, rule: Rule) -> bool {
147        self.find_first(rule).is_some()
148    }
149    /// Require the first occurrence of a rule and fail if it can't be found.
150    pub fn req_first(&mut self, rule: Rule) -> Result<Cursor<'a>, Error> {
151        self.find_first(rule)
152            .with_context(|| MissingRuleSnafu::from(self, rule))
153    }
154    /// Get all of a certain rule and consume self.
155    pub fn get_all(self, rule: Rule) -> impl Iterator<Item = Cursor<'a>> {
156        self.pairs
157            .filter(move |p| p.as_rule() == rule)
158            .map(move |p| Cursor::new(p, Rc::clone(&self.uri)))
159    }
160}
161impl<'a> From<Cursor<'a>> for Cursors<'a> {
162    fn from(value: Cursor<'a>) -> Self {
163        value.into_inner()
164    }
165}
166impl<'a> ExactSizeIterator for Cursors<'a> {
167    #[inline]
168    fn len(&self) -> usize {
169        self.pairs.len()
170    }
171}
172impl<'a> Iterator for Cursors<'a> {
173    type Item = Cursor<'a>;
174    fn next(&mut self) -> Option<Self::Item> {
175        for p in self.pairs.by_ref() {
176            if matches!(p.as_rule(), Rule::COMMENT | Rule::EOI) {
177                continue;
178            }
179            return Some(Cursor::new(p, Rc::clone(&self.uri)));
180        }
181        None
182    }
183    fn size_hint(&self) -> (usize, Option<usize>) {
184        let len = <Self as ExactSizeIterator>::len(self);
185        (len, Some(len))
186    }
187}
188
189/// Cursor over a pair.
190#[derive(Clone, Debug, PartialEq)]
191pub struct Cursor<'a> {
192    /// Pest Pair that holds the content for this cursor.
193    pair: pest::iterators::Pair<'a, Rule>,
194    /// Input URI.
195    uri: Uri,
196}
197impl<'a> Cursor<'a> {
198    /// Create a new cursor over a pair.
199    pub fn new(pair: pest::iterators::Pair<'a, Rule>, uri: Uri) -> Self {
200        Self { pair, uri }
201    }
202    /// Convert a cursor into its inner cursors.
203    pub fn into_inner(self) -> Cursors<'a> {
204        Cursors::new(self)
205    }
206    /// Create a range from this cursor.
207    pub fn as_range(&self) -> lsp_types::Range {
208        let span = self.pair.as_span();
209        let start = span.start_pos().line_col();
210        let end = span.end_pos().line_col();
211        lsp_types::Range::new(
212            lsp_types::Position::new(start.0 as u32 - 1, start.1 as u32 - 1),
213            lsp_types::Position::new(end.0 as u32 - 1, end.1 as u32 - 1),
214        )
215    }
216    /// Create a rule from this cursor.
217    pub fn as_rule(&self) -> Rule {
218        self.pair.as_rule()
219    }
220    /// Create a location from this cursor.
221    pub fn as_location(&self) -> FileLocation {
222        FileLocation {
223            range: self.as_range(),
224            uri: Rc::clone(&self.uri),
225        }
226    }
227    /// Captures a slice from the `&str` defined by this cursor.
228    pub fn as_str(&self) -> &str {
229        self.pair.as_str()
230    }
231}
232
233/// Generic token.
234#[derive(Clone, Debug, PartialEq)]
235pub struct Token {
236    /// Textual location of this token.
237    pub loc: FileLocation,
238    /// Reference counted textual contents.
239    pub text: Rc<str>,
240}
241impl Default for Token {
242    fn default() -> Self {
243        let text = "".into();
244        Token {
245            loc: Default::default(),
246            text,
247        }
248    }
249}
250impl<'a> From<Cursor<'a>> for Token {
251    fn from(value: Cursor<'a>) -> Self {
252        let text = value.pair.as_str().into();
253        let loc = value.as_location();
254        Self { text, loc }
255    }
256}
257impl std::fmt::Display for Token {
258    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259        write!(f, "{}", self.text)
260    }
261}