1use super::prelude::*;
2
3use std::fmt::{self, Write};
4use winnow::error::ParseError;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct Error {
9 inner: Box<ErrorInner>,
10}
11
12impl Error {
13 pub(super) fn from_parse_error(err: &ParseError<Input, ContextError>) -> Error {
14 Error::new(ErrorInner::from_parse_error(err))
15 }
16
17 fn new(inner: ErrorInner) -> Error {
18 Error {
19 inner: Box::new(inner),
20 }
21 }
22
23 pub fn message(&self) -> &str {
25 &self.inner.message
26 }
27
28 pub fn line(&self) -> &str {
33 &self.inner.line
34 }
35
36 pub fn location(&self) -> &Location {
38 &self.inner.location
39 }
40}
41
42impl std::error::Error for Error {}
43
44impl fmt::Display for Error {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 fmt::Display::fmt(&self.inner, f)
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
51struct ErrorInner {
52 message: String,
53 line: String,
54 location: Location,
55}
56
57impl ErrorInner {
58 fn from_parse_error(err: &ParseError<Input, ContextError>) -> ErrorInner {
59 let (line, location) = locate_error(err);
60
61 ErrorInner {
62 message: format_context_error(err.inner()),
63 line: String::from_utf8_lossy(line).to_string(),
64 location,
65 }
66 }
67
68 fn spacing(&self) -> String {
69 " ".repeat(self.location.line.to_string().len())
70 }
71}
72
73impl fmt::Display for ErrorInner {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 write!(
76 f,
77 "{s}--> HCL parse error in line {l}, column {c}\n\
78 {s} |\n\
79 {l} | {line}\n\
80 {s} | {caret:>c$}---\n\
81 {s} |\n\
82 {s} = {message}",
83 s = self.spacing(),
84 l = self.location.line,
85 c = self.location.column,
86 line = self.line,
87 caret = '^',
88 message = self.message,
89 )
90 }
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
95pub struct Location {
96 line: usize,
97 column: usize,
98 offset: usize,
99}
100
101impl Location {
102 pub fn line(&self) -> usize {
104 self.line
105 }
106
107 pub fn column(&self) -> usize {
109 self.column
110 }
111
112 pub fn offset(&self) -> usize {
114 self.offset
115 }
116}
117
118fn locate_error<'a>(err: &'a ParseError<Input<'a>, ContextError>) -> (&'a [u8], Location) {
119 let input = err.input().as_bytes();
120 if input.is_empty() {
121 return (
122 input,
123 Location {
124 line: 1,
125 column: 1,
126 offset: 0,
127 },
128 );
129 }
130 let offset = err.offset().min(input.len() - 1);
131 let column_offset = err.offset() - offset;
132
133 let line_begin = input[..offset]
135 .iter()
136 .rev()
137 .position(|&b| b == b'\n')
138 .map_or(0, |pos| offset - pos);
139
140 let line_context = input[line_begin..]
142 .iter()
143 .position(|&b| b == b'\n')
144 .map_or(&input[line_begin..], |pos| {
145 &input[line_begin..line_begin + pos]
146 });
147
148 let line = input[..line_begin].iter().filter(|&&b| b == b'\n').count() + 1;
151
152 let column = std::str::from_utf8(&input[line_begin..=offset])
155 .map_or_else(|_| offset - line_begin + 1, |s| s.chars().count())
156 + column_offset;
157
158 (
159 line_context,
160 Location {
161 line,
162 column,
163 offset,
164 },
165 )
166}
167
168fn format_context_error(err: &ContextError) -> String {
172 let mut buf = String::new();
173
174 let label = err.context().find_map(|c| match c {
175 StrContext::Label(c) => Some(c),
176 _ => None,
177 });
178
179 let expected = err
180 .context()
181 .filter_map(|c| match c {
182 StrContext::Expected(c) => Some(c),
183 _ => None,
184 })
185 .collect::<Vec<_>>();
186
187 if let Some(label) = label {
188 _ = write!(buf, "invalid {label}; ");
189 }
190
191 if expected.is_empty() {
192 _ = buf.write_str("unexpected token");
193 } else {
194 _ = write!(buf, "expected ");
195
196 match expected.len() {
197 0 => {}
198 1 => {
199 _ = write!(buf, "{}", &expected[0]);
200 }
201 n => {
202 for (i, expected) in expected.iter().enumerate() {
203 if i == n - 1 {
204 _ = buf.write_str(" or ");
205 } else if i > 0 {
206 _ = buf.write_str(", ");
207 }
208
209 _ = write!(buf, "{expected}");
210 }
211 }
212 }
213 }
214
215 if let Some(cause) = err.cause() {
216 _ = write!(buf, "; {cause}");
217 }
218
219 buf
220}