difftreelog
refactor extrace trace format to separate crate
in: master
4 files changed
cmds/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"]
cmds/jrsonnet/src/main.rsdiffbeforeafterboth1use 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}crates/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"
crates/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(())
+ }
+}