jrsonnet_evaluator/trace/
mod.rs

1mod location;
2
3use crate::{error::Error, EvaluationState, LocError};
4pub use location::*;
5use std::path::{Path, PathBuf};
6
7/// The way paths should be displayed
8pub enum PathResolver {
9	/// Only filename
10	FileName,
11	/// Absolute path
12	Absolute,
13	/// Path relative to base directory
14	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
35/// Implements pretty-printing of traces
36pub 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	// fn print_trace(
44	// 	&self,
45	// 	evaluation_state: &EvaluationState,
46	// 	error: &LocError,
47	// ) -> Result<(), std::fmt::Error> {
48	// 	self.write_trace(&mut std::fmt::stdout(), evaluation_state, error)
49	// }
50}
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
76/// vanilla-like jsonnet formatting
77pub 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					// TODO: Process all trace elements first
127					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/// rustc-like trace displaying
189#[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}