Skip to main content

tui_markup/parser/
error.rs

1use std::fmt::Display;
2
3use winnow::{
4    error::{AddContext, ParserError},
5    stream::{LocatingSlice, Offset},
6};
7
8use crate::{error::LocatedError, parser::LSpan};
9
10/// Kind of parse error.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum ErrorKind {
13    /// There is unescaped `<`, `>`, `\` character.
14    UnescapedChar,
15    /// There is a unescapable character after `\`.
16    UnescapableChar,
17    /// Element not closed but reaches line end.
18    ElementNotClose,
19}
20
21/// Error type for [parse][super::parse].
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct Error<'a> {
24    kind: Option<ErrorKind>,
25    // The remaining input at the error point. Used for display of the first character.
26    pub(crate) input: LSpan<'a>,
27    // Line number in source
28    pub(crate) line: usize,
29}
30
31impl Display for Error<'_> {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        f.write_str(match self.kind {
34            Some(kind) => match kind {
35                ErrorKind::UnescapedChar => "unescaped character",
36                ErrorKind::UnescapableChar => "unescapable character",
37                ErrorKind::ElementNotClose => "expect '>' to close element for element starter",
38            },
39            None => "unknown error",
40        })?;
41
42        if let Some(c) = self.input.chars().next() {
43            f.write_fmt(format_args!(" \'{}\'", c))?;
44        }
45
46        let (line, offset) = self.location();
47        f.write_fmt(format_args!(" near {}:{}", line, offset))
48    }
49}
50
51impl std::error::Error for Error<'_> {}
52
53impl<'a> Error<'a> {
54    fn new(input: &LocatingSlice<&'a str>) -> Self {
55        Self {
56            kind: None,
57            input: *input,
58            line: 0,
59        }
60    }
61
62    pub(crate) fn with_input(mut self, input: &LocatingSlice<&'a str>) -> Self {
63        self.input = *input;
64        self
65    }
66
67    /// Set the line number for this error.
68    pub(crate) fn with_line(mut self, line: usize) -> Self {
69        self.line = line;
70        self
71    }
72
73    /// Set the line number for this error.
74    pub(crate) fn with_kind(mut self, kind: ErrorKind) -> Self {
75        self.kind = Some(kind);
76        self
77    }
78
79    /// Get error kind.
80    pub fn kind(&self) -> Option<ErrorKind> {
81        self.kind
82    }
83}
84
85impl LocatedError for Error<'_> {
86    fn location(&self) -> (usize, usize) {
87        let mut start = self.input;
88        start.reset_to_start();
89        (self.line + 1, self.input.offset_from(&start) + 1)
90    }
91}
92
93impl<'a> ParserError<LSpan<'a>> for Error<'a> {
94    type Inner = Self;
95
96    fn from_input(input: &LocatingSlice<&'a str>) -> Self {
97        Self::new(input)
98    }
99
100    fn into_inner(self) -> Result<Self::Inner, Self> {
101        Ok(self)
102    }
103}
104
105impl<'a> AddContext<LSpan<'a>, ErrorKind> for Error<'a> {
106    fn add_context(
107        self, _input: &LSpan<'a>, _token_start: &<LSpan<'a> as winnow::stream::Stream>::Checkpoint,
108        context: ErrorKind,
109    ) -> Self {
110        self.with_kind(context)
111    }
112}