1use 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 Char(char),
14 Nom(NomErrorKind),
16}
17
18#[derive(Clone, Debug, Eq, From, PartialEq)]
20pub struct ErrorInput<I> {
21 pub input: I,
22 pub kind: ErrorKind,
23}
24
25#[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 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}