git.delta.rocks / jrsonnet / refs/commits / 458a1bc63c56

difftreelog

refactor extrace trace format to separate crate

Лач2020-07-16parent: #3b3945f.patch.diff
in: master

4 files changed

modifiedcmds/jrsonnet/Cargo.tomldiffbeforeafterboth
--- a/cmds/jrsonnet/Cargo.toml
+++ b/cmds/jrsonnet/Cargo.toml
@@ -11,11 +11,9 @@
 [dependencies]
 jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "1.0.0" }
 jrsonnet-parser = { path = "../../crates/jrsonnet-parser", version = "1.0.0" }
-annotate-snippets = "0.8.0"
+jrsonnet-trace = { path = "../../crates/jrsonnet-trace", version = "1.0.0" }
 # TODO: Fix mimalloc compile errors, and use them
 mimallocator = "0.1.3"
 
 [dependencies.clap]
 version = "3.0.0-beta.1"
-default-features = false
-features = ["std", "derive"]
modifiedcmds/jrsonnet/src/main.rsdiffbeforeafterboth
1use clap::Clap;1use clap::Clap;
2use jrsonnet_evaluator::{trace::CodeLocation, EvaluationState, LocError, StackTrace, Val};2use jrsonnet_evaluator::Val;
3use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParserSettings};3use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParserSettings};
4use jrsonnet_trace::{CompactFormat, ExplainingFormat, PathResolver, TraceFormat};
4use std::env::current_dir;5use std::env::current_dir;
5use std::{collections::HashMap, path::PathBuf, rc::Rc, str::FromStr};6use std::{collections::HashMap, path::PathBuf, rc::Rc, str::FromStr};
67
26}27}
2728
28#[derive(PartialEq)]29#[derive(PartialEq)]
29enum TraceFormat {30enum TraceFormatName {
30 CppJsonnet,31 Compact,
31 GoJsonnet,32 Explaining,
32 Custom,
33}33}
34impl FromStr for TraceFormat {34impl FromStr for TraceFormatName {
35 type Err = &'static str;35 type Err = &'static str;
36 fn from_str(s: &str) -> Result<Self, Self::Err> {36 fn from_str(s: &str) -> Result<Self, Self::Err> {
37 Ok(match s {37 Ok(match s {
38 "cpp" => TraceFormat::CppJsonnet,38 "compact" => TraceFormatName::Compact,
39 "go" => TraceFormat::GoJsonnet,
40 "default" => TraceFormat::Custom,39 "explaining" => TraceFormatName::Explaining,
41 _ => return Err("no such format"),40 _ => return Err("no such format"),
42 })41 })
43 }42 }
67}66}
6867
69#[derive(Clap)]68#[derive(Clap)]
70#[clap(version = "0.1.0", author = "Lach <iam@lach.pw>")]69#[clap(name = "jrsonnet", version, author)]
71struct Opts {70pub struct Opts {
72 #[clap(long, about = "Disable global std variable")]71 #[clap(long, about = "Disable global std variable")]
73 no_stdlib: bool,72 no_stdlib: bool,
74 #[clap(long, about = "Add external string", number_of_values = 1)]73 #[clap(long, about = "Add external string", number_of_values = 1)]
81 tla_code: Vec<ExtStr>,80 tla_code: Vec<ExtStr>,
82 #[clap(long, short = "f", default_value = "json", possible_values = &["none", "json", "yaml"], about = "Output format, wraps resulting value to corresponding std.manifest call")]81 #[clap(long, short = "f", default_value = "json", possible_values = &["none", "json", "yaml"], about = "Output format, wraps resulting value to corresponding std.manifest call")]
83 format: Format,82 format: Format,
84 #[clap(long, default_value = "default", possible_values = &["cpp", "go", "default"], about = "Emulated needed stacktrace display")]83 #[clap(long, default_value = "compact", possible_values = &["compact", "explaining"], about = "Choose format of displayed stacktraces")]
85 trace_format: TraceFormat,84 trace_format: TraceFormatName,
8685
87 #[clap(86 #[clap(
88 long,87 long,
150 evaluator.add_ext_var(name.into(), evaluator.parse_evaluate_raw(&value).unwrap());149 evaluator.add_ext_var(name.into(), evaluator.parse_evaluate_raw(&value).unwrap());
151 }150 }
151
152 let resolver = PathResolver::Relative(std::env::current_dir().unwrap());
153 let trace_format: Box<dyn TraceFormat> = match opts.trace_format {
154 TraceFormatName::Compact => Box::new(CompactFormat { resolver }),
155 TraceFormatName::Explaining => Box::new(ExplainingFormat { resolver }),
156 };
157
152 let mut input = current_dir().unwrap();158 let mut input = current_dir().unwrap();
153 input.push(opts.input.clone());159 input.push(opts.input.clone());
154 let code_string = String::from_utf8(std::fs::read(opts.input.clone()).unwrap()).unwrap();160 let code_string = String::from_utf8(std::fs::read(opts.input.clone()).unwrap()).unwrap();
155 if let Err(e) = evaluator.add_file(Rc::new(input.clone()), code_string.clone().into()) {161 if let Err(e) = evaluator.add_file(Rc::new(input.clone()), code_string.into()) {
156 print_syntax_error(e, &input, &code_string);162 trace_format.print_trace(&evaluator, &e).unwrap();
157 std::process::exit(1);163 std::process::exit(1);
158 }164 }
159 let result = evaluator.evaluate_file(&input);165 let result = evaluator.evaluate_file(&input);
191 };197 };
192 let v = evaluator.run_in_state(|| match opts.format {198 let v = evaluator.run_in_state(|| match opts.format {
193 Format::Json => Ok(Val::Str(v.into_json(opts.line_padding)?)),199 Format::Json => Ok(Val::Str(v.into_json(opts.line_padding)?)),
194 Format::Yaml => {200 Format::Yaml => Ok(Val::Str(v.into_yaml(opts.line_padding)?)),
195 evaluator.add_global("__tmp__to_yaml__".into(), v);
196 evaluator.parse_evaluate_raw("std.manifestYamlDoc(__tmp__to_yaml__, \" \")")
197 }
198 _ => Ok(v),201 _ => Ok(v),
199 });202 });
200 let v = match v {203 let v = match v {
201 Ok(v) => v,204 Ok(v) => v,
202 Err(err) => {205 Err(err) => {
203 print_error(&err, evaluator, &opts);206 trace_format.print_trace(&evaluator, &err).unwrap();
204 std::process::exit(1);207 std::process::exit(1);
205 }208 }
206 };209 };
213 }216 }
214 }217 }
215 Err(err) => {218 Err(err) => {
216 print_error(&err, evaluator, &opts);219 trace_format.print_trace(&evaluator, &err).unwrap();
217 std::process::exit(1);220 std::process::exit(1);
218 }221 }
219 }222 }
220}223}
221
222fn print_error(err: &LocError, evaluator: EvaluationState, opts: &Opts) {
223 println!("Error: {:?}", err.0);
224 print_trace(&(err.1), evaluator, &opts);
225}
226
227fn print_syntax_error(error: jrsonnet_parser::ParseError, file: &PathBuf, code: &str) {
228 use annotate_snippets::{
229 display_list::{DisplayList, FormatOptions},
230 snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
231 };
232 //&("Expected: ".to_owned() + error.expected)
233 let origin = file.to_str().unwrap();
234 let error_message = format!("Expected: {}", error.expected);
235 let snippet = Snippet {
236 opt: FormatOptions {
237 color: true,
238 ..Default::default()
239 },
240 title: Some(Annotation {
241 label: Some(&error_message),
242 id: None,
243 annotation_type: AnnotationType::Error,
244 }),
245 footer: vec![],
246 slices: vec![Slice {
247 source: &code,
248 line_start: 1,
249 origin: Some(origin),
250 fold: false,
251 annotations: vec![SourceAnnotation {
252 label: "At this position",
253 annotation_type: AnnotationType::Error,
254 range: (error.location.offset, error.location.offset + 1),
255 }],
256 }],
257 };
258
259 let dl = DisplayList::from(snippet);
260 println!("{}", dl);
261}
262
263fn print_trace(trace: &StackTrace, evaluator: EvaluationState, opts: &Opts) {
264 use annotate_snippets::{
265 display_list::{DisplayList, FormatOptions},
266 snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},
267 };
268 for item in trace.0.iter() {
269 let desc = &item.1;
270 let source = item.0.clone();
271 let start_end = evaluator.map_source_locations(&source.0, &[source.1, source.2]);
272 if opts.trace_format == TraceFormat::Custom {
273 let source_fragment: String = evaluator
274 .get_source(&source.0)
275 .unwrap()
276 .chars()
277 .skip(start_end[0].line_start_offset)
278 .take(start_end[1].line_end_offset - start_end[0].line_start_offset)
279 .collect();
280 let snippet = Snippet {
281 opt: FormatOptions {
282 color: true,
283 ..Default::default()
284 },
285 title: Some(Annotation {
286 label: Some(&item.1),
287 id: None,
288 annotation_type: AnnotationType::Error,
289 }),
290 footer: vec![],
291 slices: vec![Slice {
292 source: &source_fragment,
293 line_start: start_end[0].line,
294 origin: Some(&source.0.to_str().unwrap()),
295 fold: false,
296 annotations: vec![SourceAnnotation {
297 label: desc,
298 annotation_type: AnnotationType::Error,
299 range: (
300 source.1 - start_end[0].line_start_offset,
301 source.2 - start_end[0].line_start_offset,
302 ),
303 }],
304 }],
305 };
306
307 let dl = DisplayList::from(snippet);
308 println!("{}", dl);
309 } else {
310 print_jsonnet_pair(
311 source.0.to_str().unwrap(),
312 &start_end[0],
313 &start_end[1],
314 opts.trace_format == TraceFormat::GoJsonnet,
315 );
316 }
317 }
318}
319
320fn print_jsonnet_pair(file: &str, start: &CodeLocation, end: &CodeLocation, is_go: bool) {
321 if is_go {
322 print!(" ");
323 } else {
324 print!(" ");
325 }
326 print!("{}:", file);
327 if start.line == end.line {
328 // IDK why, but this is the behavior original jsonnet cpp impl shows
329 if start.column == end.column || !is_go && start.column + 1 == end.column {
330 println!("{}:{}", start.line, end.column)
331 } else {
332 println!("{}:{}-{}", start.line, start.column, end.column);
333 }
334 } else {
335 println!(
336 "({}:{})-({}:{})",
337 start.line, end.column, start.line, end.column
338 );
339 }
340}
341224
addedcrates/jrsonnet-trace/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-trace/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "jrsonnet-trace"
+version = "1.0.0"
+authors = ["Лач <iam@lach.pw>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+jrsonnet-evaluator = { path = "../jrsonnet-evaluator", version = "1.0.0" }
+jrsonnet-parser = { path = "../jrsonnet-parser", version = "1.0.0" }
+pathdiff = "0.2.0"
+annotate-snippets = "0.9.0"
addedcrates/jrsonnet-trace/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-trace/src/lib.rs
@@ -0,0 +1,176 @@
+use jrsonnet_evaluator::{
+	trace::{offset_to_location, CodeLocation},
+	EvaluationState, LocError,
+};
+use std::path::PathBuf;
+
+/// How paths should be displayed
+pub enum PathResolver {
+	/// Only filename will be shown
+	FileName,
+	/// Absolute path of file
+	Absolute,
+	/// Relative path from base directory
+	Relative(PathBuf),
+}
+
+impl PathResolver {
+	pub fn resolve(&self, from: &PathBuf) -> String {
+		match self {
+			PathResolver::FileName => from.file_name().unwrap().to_string_lossy().into_owned(),
+			PathResolver::Absolute => from.to_string_lossy().into_owned(),
+			PathResolver::Relative(base) => {
+				if from.is_relative() {
+					return from.to_string_lossy().into_owned();
+				}
+				pathdiff::diff_paths(from, base)
+					.unwrap()
+					.to_string_lossy()
+					.into_owned()
+			}
+		}
+	}
+}
+
+/// Implements trace to string pretty-printing
+pub trait TraceFormat {
+	fn write_trace(
+		&self,
+		out: &mut dyn std::io::Write,
+		evaluation_state: &EvaluationState,
+		error: &LocError,
+	) -> Result<(), std::io::Error>;
+	fn print_trace(
+		&self,
+		evaluation_state: &EvaluationState,
+		error: &LocError,
+	) -> Result<(), std::io::Error> {
+		self.write_trace(&mut std::io::stdout(), evaluation_state, error)
+	}
+}
+
+fn print_code_location(
+	out: &mut impl std::fmt::Write,
+	start: &CodeLocation,
+	end: &CodeLocation,
+) -> Result<(), std::fmt::Error> {
+	if start.line == end.line {
+		if start.column == end.column {
+			write!(out, "{}:{}", start.line, end.column - 1)?;
+		} else {
+			write!(out, "{}:{}-{}", start.line, start.column - 1, end.column)?;
+		}
+	} else {
+		write!(
+			out,
+			"{}:{}-{}:{}",
+			start.line,
+			end.column - 1,
+			start.line,
+			end.column
+		)?;
+	}
+	Ok(())
+}
+
+/// vanilla jsonnet like formatting
+pub struct CompactFormat {
+	pub resolver: PathResolver,
+}
+
+impl TraceFormat for CompactFormat {
+	fn write_trace(
+		&self,
+		out: &mut dyn std::io::Write,
+		evaluation_state: &EvaluationState,
+		error: &LocError,
+	) -> Result<(), std::io::Error> {
+		writeln!(out, "{:?}", error.0)?;
+		let file_names = (error.1)
+			.0
+			.iter()
+			.map(|el| {
+				let resolved_path = self.resolver.resolve(&(el.0).0);
+				// TODO: Process all trace elements first
+				let location =
+					evaluation_state.map_source_locations(&(el.0).0, &[(el.0).1, (el.0).2]);
+				(resolved_path, location)
+			})
+			.map(|(mut n, location)| {
+				use std::fmt::Write;
+				write!(n, ":").unwrap();
+				print_code_location(&mut n, &location[0], &location[1]).unwrap();
+				n
+			})
+			.collect::<Vec<_>>();
+		let align = file_names.iter().map(|e| e.len()).max().unwrap_or(0);
+		for (i, (el, file)) in (error.1).0.iter().zip(file_names).enumerate() {
+			if i != 0 {
+				writeln!(out)?;
+			}
+			write!(out, "{:<w$}: {}", file, el.1, w = align)?;
+		}
+		Ok(())
+	}
+}
+
+/// rustc-like trace displaying
+pub struct ExplainingFormat {
+	pub resolver: PathResolver,
+}
+impl TraceFormat for ExplainingFormat {
+	fn write_trace(
+		&self,
+		out: &mut dyn std::io::Write,
+		evaluation_state: &EvaluationState,
+		error: &LocError,
+	) -> Result<(), std::io::Error> {
+		use annotate_snippets::{
+			display_list::{DisplayList, FormatOptions},
+			snippet::{AnnotationType, Slice, Snippet, SourceAnnotation},
+		};
+		writeln!(out, "{:?}", error.0)?;
+		let trace = &error.1;
+		for item in trace.0.iter() {
+			let desc = &item.1;
+			let source = item.0.clone();
+			let start_end = evaluation_state.map_source_locations(&source.0, &[source.1, source.2]);
+
+			let source_fragment: String = evaluation_state
+				.get_source(&source.0)
+				.unwrap()
+				.chars()
+				.skip(start_end[0].line_start_offset)
+				.take(start_end[1].line_end_offset - start_end[0].line_start_offset)
+				.collect();
+
+			let origin = self.resolver.resolve(&source.0);
+			let snippet = Snippet {
+				opt: FormatOptions {
+					color: true,
+					..Default::default()
+				},
+				title: None,
+				footer: vec![],
+				slices: vec![Slice {
+					source: &source_fragment,
+					line_start: start_end[0].line,
+					origin: Some(&origin),
+					fold: false,
+					annotations: vec![SourceAnnotation {
+						label: desc,
+						annotation_type: AnnotationType::Error,
+						range: (
+							source.1 - start_end[0].line_start_offset,
+							source.2 - start_end[0].line_start_offset,
+						),
+					}],
+				}],
+			};
+
+			let dl = DisplayList::from(snippet);
+			writeln!(out, "{}", dl)?;
+		}
+		Ok(())
+	}
+}