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
before · cmds/jrsonnet/src/main.rs
1use clap::Clap;2use jrsonnet_evaluator::{trace::CodeLocation, EvaluationState, LocError, StackTrace, Val};3use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParserSettings};4use std::env::current_dir;5use std::{collections::HashMap, path::PathBuf, rc::Rc, str::FromStr};67#[global_allocator]8static GLOBAL: mimallocator::Mimalloc = mimallocator::Mimalloc;910enum Format {11	None,12	Json,13	Yaml,14}1516impl FromStr for Format {17	type Err = &'static str;18	fn from_str(s: &str) -> Result<Self, Self::Err> {19		Ok(match s {20			"none" => Format::None,21			"json" => Format::Json,22			"yaml" => Format::Yaml,23			_ => return Err("no such format"),24		})25	}26}2728#[derive(PartialEq)]29enum TraceFormat {30	CppJsonnet,31	GoJsonnet,32	Custom,33}34impl FromStr for TraceFormat {35	type Err = &'static str;36	fn from_str(s: &str) -> Result<Self, Self::Err> {37		Ok(match s {38			"cpp" => TraceFormat::CppJsonnet,39			"go" => TraceFormat::GoJsonnet,40			"default" => TraceFormat::Custom,41			_ => return Err("no such format"),42		})43	}44}4546#[derive(Clone)]47struct ExtStr {48	name: String,49	value: String,50}51impl FromStr for ExtStr {52	type Err = &'static str;53	fn from_str(s: &str) -> Result<Self, Self::Err> {54		let out: Vec<_> = s.split('=').collect();55		match out.len() {56			1 => Ok(ExtStr {57				name: out[0].to_owned(),58				value: std::env::var(out[0]).or(Err("missing env var"))?,59			}),60			2 => Ok(ExtStr {61				name: out[0].to_owned(),62				value: out[1].to_owned(),63			}),64			_ => Err("bad ext-str syntax"),65		}66	}67}6869#[derive(Clap)]70#[clap(version = "0.1.0", author = "Lach <iam@lach.pw>")]71struct Opts {72	#[clap(long, about = "Disable global std variable")]73	no_stdlib: bool,74	#[clap(long, about = "Add external string", number_of_values = 1)]75	ext_str: Vec<ExtStr>,76	#[clap(long, about = "Add external string from code", number_of_values = 1)]77	ext_code: Vec<ExtStr>,78	#[clap(long, about = "Add TLA", number_of_values = 1)]79	tla_str: Vec<ExtStr>,80	#[clap(long, about = "Add TLA from code", number_of_values = 1)]81	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")]83	format: Format,84	#[clap(long, default_value = "default", possible_values = &["cpp", "go", "default"], about = "Emulated needed stacktrace display")]85	trace_format: TraceFormat,8687	#[clap(88		long,89		short = "s",90		default_value = "200",91		about = "Number of allowed stack frames"92	)]93	max_stack: usize,94	#[clap(95		long,96		short = "t",97		default_value = "20",98		about = "Max length of stack trace before cropping"99	)]100	max_trace: usize,101102	#[clap(103		long,104		about = "Required os stack size, probally you shouldn't change it"105	)]106	thread_stack_size: Option<usize>,107108	#[clap(long, short = "J", about = "Library search dir")]109	jpath: Vec<PathBuf>,110111	#[clap(112		long,113		default_value = "3",114		about = "When using --format, this option specifies string to pad output with"115	)]116	line_padding: usize,117118	#[clap(about = "File to compile")]119	input: String,120}121122fn main() {123	let opts: Opts = Opts::parse();124	if let Some(size) = opts.thread_stack_size {125		std::thread::Builder::new()126			.stack_size(size * 1024 * 1024)127			.spawn(|| main_real(opts))128			.unwrap()129			.join()130			.unwrap();131	} else {132		main_real(opts)133	}134}135136fn main_real(opts: Opts) {137	let evaluator = jrsonnet_evaluator::EvaluationState::default();138	evaluator.set_max_trace(opts.max_trace);139	evaluator.set_max_stack(opts.max_stack);140	evaluator.set_import_resolver(Box::new(jrsonnet_evaluator::FileImportResolver {141		library_paths: opts.jpath.clone(),142	}));143	if !opts.no_stdlib {144		evaluator.with_stdlib();145	}146	for ExtStr { name, value } in opts.ext_str.iter().cloned() {147		evaluator.add_ext_var(name.into(), Val::Str(value.into()));148	}149	for ExtStr { name, value } in opts.ext_code.iter().cloned() {150		evaluator.add_ext_var(name.into(), evaluator.parse_evaluate_raw(&value).unwrap());151	}152	let mut input = current_dir().unwrap();153	input.push(opts.input.clone());154	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()) {156		print_syntax_error(e, &input, &code_string);157		std::process::exit(1);158	}159	let result = evaluator.evaluate_file(&input);160	match result {161		Ok(v) => {162			let v = match v {163				Val::Func(f) => {164					let mut desc_map = HashMap::new();165					for ExtStr { name, value } in opts.tla_str.iter().cloned() {166						desc_map.insert(name, el!(Expr::Str(value.into())));167					}168					for ExtStr { name, value } in opts.tla_code.iter().cloned() {169						desc_map.insert(170							name,171							jrsonnet_parser::parse(172								&value,173								&ParserSettings {174									file_name: Rc::new(PathBuf::new()),175									loc_data: false,176								},177							)178							.unwrap(),179						);180					}181					evaluator.add_global("__tmp__tlf__".into(), Val::Func(f));182					evaluator183						.evaluate_raw(el!(Expr::Apply(184							el!(Expr::Var("__tmp__tlf__".into())),185							ArgsDesc(desc_map.into_iter().map(|(k, v)| Arg(Some(k), v)).collect()),186							false,187						)))188						.unwrap()189				}190				v => v,191			};192			let v = evaluator.run_in_state(|| match opts.format {193				Format::Json => Ok(Val::Str(v.into_json(opts.line_padding)?)),194				Format::Yaml => {195					evaluator.add_global("__tmp__to_yaml__".into(), v);196					evaluator.parse_evaluate_raw("std.manifestYamlDoc(__tmp__to_yaml__, \"  \")")197				}198				_ => Ok(v),199			});200			let v = match v {201				Ok(v) => v,202				Err(err) => {203					print_error(&err, evaluator, &opts);204					std::process::exit(1);205				}206			};207			match v {208				Val::Str(s) => println!("{}", s),209				Val::Num(n) => println!("{}", n),210				_v => eprintln!(211					"jsonnet output is not a string.\nDid you forgot to set --format, or wrap your data with std.manifestJson?"212				),213			}214		}215		Err(err) => {216			print_error(&err, evaluator, &opts);217			std::process::exit(1);218		}219	}220}221222fn print_error(err: &LocError, evaluator: EvaluationState, opts: &Opts) {223	println!("Error: {:?}", err.0);224	print_trace(&(err.1), evaluator, &opts);225}226227fn 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	};258259	let dl = DisplayList::from(snippet);260	println!("{}", dl);261}262263fn 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 = evaluator274				.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			};306307			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}319320fn 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 shows329		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.column338		);339	}340}
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(())
+	}
+}