jrsonnet_evaluator/trace/
mod.rs1mod location;
2
3use crate::{error::Error, EvaluationState, LocError};
4pub use location::*;
5use std::path::{Path, PathBuf};
6
7pub enum PathResolver {
9 FileName,
11 Absolute,
13 Relative(PathBuf),
15}
16
17impl PathResolver {
18 pub fn resolve(&self, from: &Path) -> String {
19 match self {
20 Self::FileName => from.file_name().unwrap().to_string_lossy().into_owned(),
21 Self::Absolute => from.to_string_lossy().into_owned(),
22 Self::Relative(base) => {
23 if from.is_relative() {
24 return from.to_string_lossy().into_owned();
25 }
26 pathdiff::diff_paths(from, base)
27 .unwrap()
28 .to_string_lossy()
29 .into_owned()
30 }
31 }
32 }
33}
34
35pub trait TraceFormat {
37 fn write_trace(
38 &self,
39 out: &mut dyn std::fmt::Write,
40 evaluation_state: &EvaluationState,
41 error: &LocError,
42 ) -> Result<(), std::fmt::Error>;
43 }
51
52fn print_code_location(
53 out: &mut impl std::fmt::Write,
54 start: &CodeLocation,
55 end: &CodeLocation,
56) -> Result<(), std::fmt::Error> {
57 if start.line == end.line {
58 if start.column == end.column {
59 write!(out, "{}:{}", start.line, end.column - 1)?;
60 } else {
61 write!(out, "{}:{}-{}", start.line, start.column - 1, end.column)?;
62 }
63 } else {
64 write!(
65 out,
66 "{}:{}-{}:{}",
67 start.line,
68 end.column.saturating_sub(1),
69 start.line,
70 end.column
71 )?;
72 }
73 Ok(())
74}
75
76pub struct CompactFormat {
78 pub resolver: PathResolver,
79 pub padding: usize,
80}
81
82impl TraceFormat for CompactFormat {
83 fn write_trace(
84 &self,
85 out: &mut dyn std::fmt::Write,
86 evaluation_state: &EvaluationState,
87 error: &LocError,
88 ) -> Result<(), std::fmt::Error> {
89 write!(out, "{}", error.error())?;
90 if let Error::ImportSyntaxError {
91 path,
92 source_code,
93 error,
94 } = error.error()
95 {
96 writeln!(out)?;
97 use std::fmt::Write;
98 let mut n = self.resolver.resolve(path);
99 let mut offset = error.location.offset;
100 let is_eof = if offset >= source_code.len() {
101 offset = source_code.len() - 1;
102 true
103 } else {
104 false
105 };
106 let mut location = offset_to_location(source_code, &[offset])
107 .into_iter()
108 .next()
109 .unwrap();
110 if is_eof {
111 location.column += 1;
112 }
113
114 write!(n, ":").unwrap();
115 print_code_location(&mut n, &location, &location).unwrap();
116 write!(out, "{:<p$}{}", "", n, p = self.padding,)?;
117 }
118 let file_names = error
119 .trace()
120 .0
121 .iter()
122 .map(|el| {
123 el.location.as_ref().map(|l| {
124 use std::fmt::Write;
125 let mut resolved_path = self.resolver.resolve(&l.0);
126 let location = evaluation_state.map_source_locations(&l.0, &[l.1, l.2]);
128 write!(resolved_path, ":").unwrap();
129 print_code_location(&mut resolved_path, &location[0], &location[1]).unwrap();
130 resolved_path
131 })
132 })
133 .collect::<Vec<_>>();
134 let align = file_names
135 .iter()
136 .flatten()
137 .map(|e| e.len())
138 .max()
139 .unwrap_or(0);
140 for (el, file) in error.trace().0.iter().zip(file_names) {
141 writeln!(out)?;
142 write!(
143 out,
144 "{:<p$}{:<w$}: {}",
145 "",
146 file.unwrap_or_else(|| "".to_owned()),
147 el.desc,
148 p = self.padding,
149 w = align
150 )?;
151 }
152 Ok(())
153 }
154}
155
156pub struct JsFormat;
157impl TraceFormat for JsFormat {
158 fn write_trace(
159 &self,
160 out: &mut dyn std::fmt::Write,
161 evaluation_state: &EvaluationState,
162 error: &LocError,
163 ) -> Result<(), std::fmt::Error> {
164 write!(out, "{}", error.error())?;
165 for item in error.trace().0.iter() {
166 writeln!(out)?;
167 let desc = &item.desc;
168 if let Some(source) = &item.location {
169 let start_end =
170 evaluation_state.map_source_locations(&source.0, &[source.1, source.2]);
171
172 write!(
173 out,
174 " at {} ({}:{}:{})",
175 desc,
176 source.0.to_str().unwrap(),
177 start_end[0].line,
178 start_end[0].column,
179 )?;
180 } else {
181 write!(out, " at {}", desc,)?;
182 }
183 }
184 Ok(())
185 }
186}
187
188#[cfg(feature = "explaining-traces")]
190pub struct ExplainingFormat {
191 pub resolver: PathResolver,
192}
193#[cfg(feature = "explaining-traces")]
194impl TraceFormat for ExplainingFormat {
195 fn write_trace(
196 &self,
197 out: &mut dyn std::fmt::Write,
198 evaluation_state: &EvaluationState,
199 error: &LocError,
200 ) -> Result<(), std::fmt::Error> {
201 write!(out, "{}", error.error())?;
202 if let Error::ImportSyntaxError {
203 path,
204 source_code,
205 error,
206 } = error.error()
207 {
208 writeln!(out)?;
209 let mut offset = error.location.offset;
210 if offset >= source_code.len() {
211 offset = source_code.len() - 1;
212 }
213 let mut location = offset_to_location(source_code, &[offset])
214 .into_iter()
215 .next()
216 .unwrap();
217 if location.column >= 1 {
218 location.column -= 1;
219 }
220
221 self.print_snippet(
222 out,
223 source_code,
224 path,
225 &location,
226 &location,
227 "^ syntax error",
228 )?;
229 }
230 let trace = &error.trace();
231 for item in trace.0.iter() {
232 writeln!(out)?;
233 let desc = &item.desc;
234 if let Some(source) = &item.location {
235 let start_end =
236 evaluation_state.map_source_locations(&source.0, &[source.1, source.2]);
237 self.print_snippet(
238 out,
239 &evaluation_state.get_source(&source.0).unwrap(),
240 &source.0,
241 &start_end[0],
242 &start_end[1],
243 desc,
244 )?;
245 } else {
246 write!(out, "{}", desc)?;
247 }
248 }
249 Ok(())
250 }
251}
252
253impl ExplainingFormat {
254 fn print_snippet(
255 &self,
256 out: &mut dyn std::fmt::Write,
257 source: &str,
258 origin: &Path,
259 start: &CodeLocation,
260 end: &CodeLocation,
261 desc: &str,
262 ) -> Result<(), std::fmt::Error> {
263 use annotate_snippets::{
264 display_list::{DisplayList, FormatOptions},
265 snippet::{AnnotationType, Slice, Snippet, SourceAnnotation},
266 };
267
268 let source_fragment: String = source
269 .chars()
270 .skip(start.line_start_offset)
271 .take(end.line_end_offset - end.line_start_offset)
272 .collect();
273
274 let origin = self.resolver.resolve(origin);
275 let snippet = Snippet {
276 opt: FormatOptions {
277 color: true,
278 ..Default::default()
279 },
280 title: None,
281 footer: vec![],
282 slices: vec![Slice {
283 source: &source_fragment,
284 line_start: start.line,
285 origin: Some(&origin),
286 fold: false,
287 annotations: vec![SourceAnnotation {
288 label: desc,
289 annotation_type: AnnotationType::Error,
290 range: (
291 start.offset - start.line_start_offset,
292 end.offset - start.line_start_offset,
293 ),
294 }],
295 }],
296 };
297
298 let dl = DisplayList::from(snippet);
299 write!(out, "{}", dl)?;
300
301 Ok(())
302 }
303}