xschem_parser/
error.rs

1//! Parser errors.
2use std::fmt::{self, Display};
3
4use colored::Colorize;
5use derive_more::From;
6use nom::error::{ContextError, ErrorKind as NomErrorKind, FromExternalError, ParseError};
7
8use crate::{FileSpan, Span};
9
10#[derive(Clone, Debug, Eq, From, PartialEq)]
11pub enum ErrorKind {
12    /// Indicates which character was expected by the `char` function
13    Char(char),
14    /// Error kind given by various nom parsers
15    Nom(NomErrorKind),
16}
17
18/// Input with an error.
19#[derive(Clone, Debug, Eq, From, PartialEq)]
20pub struct ErrorInput<I> {
21    pub input: I,
22    pub kind: ErrorKind,
23}
24
25/// Input with context.
26#[derive(Clone, Debug, Eq, From, PartialEq)]
27pub struct InputContext<'a, I> {
28    pub input: I,
29    pub name: &'a str,
30}
31
32#[derive(Clone, Debug, Eq, From, PartialEq)]
33pub struct Error<I> {
34    pub err: ErrorInput<I>,
35    pub context: Vec<InputContext<'static, I>>,
36}
37
38impl std::error::Error for Error<&str> {}
39impl std::error::Error for Error<Span<'_>> {}
40impl std::error::Error for Error<FileSpan<'_, '_>> {}
41
42impl<I> ParseError<I> for Error<I> {
43    fn from_error_kind(input: I, kind: NomErrorKind) -> Self {
44        Self {
45            err: ErrorInput {
46                input,
47                kind: kind.into(),
48            },
49            context: Vec::default(),
50        }
51    }
52
53    fn append(_input: I, _kind: NomErrorKind, other: Self) -> Self {
54        other
55    }
56
57    fn from_char(input: I, c: char) -> Self {
58        Self {
59            err: ErrorInput {
60                input,
61                kind: c.into(),
62            },
63            context: Vec::default(),
64        }
65    }
66}
67
68impl<I> ContextError<I> for Error<I> {
69    fn add_context(input: I, name: &'static str, mut other: Self) -> Self {
70        other.context.push(InputContext { input, name });
71        other
72    }
73}
74
75impl<I, E> FromExternalError<I, E> for Error<I> {
76    fn from_external_error(input: I, kind: NomErrorKind, _e: E) -> Self {
77        Self {
78            err: ErrorInput {
79                input,
80                kind: kind.into(),
81            },
82            context: Vec::default(),
83        }
84    }
85}
86
87macro_rules! format_line {
88    ($input:expr) => {
89        format_args!(
90            "{space:width$}{ptr}:{line_number}:{column_number}\n\
91             {space:width$}{gutter}\n\
92             {line_number:>width$}{gutter} {line}\n\
93             {space:width$}{gutter}{space:column_number$}{column}\n\
94             {space:width$}{gutter}",
95            space = ' ',
96            ptr = "--> ".blue().bold(),
97            gutter = " |".blue(),
98            line_number = $input.location_line(),
99            column_number = $input.get_utf8_column(),
100            width = usize::try_from($input.location_line().ilog10() + 1).unwrap_or(6) + 1,
101            line = std::str::from_utf8($input.get_line_beginning()).unwrap_or("<invalid UTF-8>"),
102            column = "^".red().bold(),
103        )
104    };
105}
106macro_rules! format_file_line {
107    ($input:expr, $path:expr $(,)?) => {
108        format_args!(
109            "{space:width$}{ptr}{path}:{line_number}:{column_number}\n\
110             {space:width$}{gutter}\n\
111             {line_number:>width$}{gutter} {line}\n\
112             {space:width$}{gutter}{space:column_number$}{column}\n\
113             {space:width$}{gutter}",
114            space = ' ',
115            ptr = "--> ".blue().bold(),
116            gutter = " |".blue(),
117            path = $path.display(),
118            line_number = $input.location_line(),
119            column_number = $input.get_utf8_column(),
120            width = usize::try_from($input.location_line().ilog10() + 1).unwrap_or(6) + 1,
121            line = std::str::from_utf8($input.get_line_beginning()).unwrap_or("<invalid UTF-8>"),
122            column = "^".red().bold(),
123        )
124    };
125}
126
127macro_rules! format_error {
128    ($desc:expr) => {
129        format_args!(
130            "{error}: {desc}",
131            error = "error".red().bold(),
132            desc = format!("{}", $desc).bold(),
133        )
134    };
135}
136macro_rules! format_error_line {
137    ($input:expr, $desc:expr $(,)?) => {
138        format_args!(
139            "{error}\n{line}",
140            error = format_error!($desc),
141            line = format_line!($input),
142        )
143    };
144}
145macro_rules! format_error_file_line {
146    ($input:expr, $desc:expr $(,)?) => {
147        format_args!(
148            "{error}\n{line}",
149            error = format_error!($desc),
150            line = format_file_line!($input, $input.extra),
151        )
152    };
153}
154
155macro_rules! format_context {
156    ($context:expr) => {
157        format_args!(
158            "{context_in} {context}",
159            context_in = "in".blue().bold(),
160            context = $context.bold(),
161        )
162    };
163}
164macro_rules! format_context_line {
165    ($input:expr, $context:expr $(,)?) => {
166        format_args!(
167            "{context}\n{line}",
168            context = format_context!($context),
169            line = format_line!($input),
170        )
171    };
172}
173macro_rules! format_context_file_line {
174    ($input:expr, $context:expr $(,)?) => {
175        format_args!(
176            "{context}\n{line}",
177            context = format_context!($context),
178            line = format_file_line!($input, $input.extra),
179        )
180    };
181}
182
183impl Display for ErrorKind {
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        match self {
186            ErrorKind::Char(expected) => write!(f, "expected '{expected}'"),
187            ErrorKind::Nom(nom_err) => write!(f, "{}", nom_err.description()),
188        }
189    }
190}
191
192impl Display for ErrorInput<&str> {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        f.write_fmt(format_error!(format_args!("{}", self.kind)))
195    }
196}
197
198impl Display for ErrorInput<Span<'_>> {
199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200        f.write_fmt(format_error_line!(self.input, self.kind))
201    }
202}
203
204impl Display for ErrorInput<FileSpan<'_, '_>> {
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        f.write_fmt(format_error_file_line!(self.input, self.kind))
207    }
208}
209
210impl Display for InputContext<'_, &str> {
211    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212        f.write_fmt(format_context!(self.name))
213    }
214}
215
216impl Display for InputContext<'_, Span<'_>> {
217    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218        f.write_fmt(format_context_line!(self.input, self.name))
219    }
220}
221
222impl Display for InputContext<'_, FileSpan<'_, '_>> {
223    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224        f.write_fmt(format_context_file_line!(self.input, self.name))
225    }
226}
227
228impl<I> Display for Error<I>
229where
230    ErrorInput<I>: Display,
231    InputContext<'static, I>: Display,
232{
233    /// Write human readable error.
234    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        self.err.fmt(f)?;
236
237        self.context
238            .iter()
239            .try_for_each(|context| write!(f, "\n{context}"))
240    }
241}