difftreelog
refactor merge trace to evaluator
in: master
6 files changed
crates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -9,7 +9,7 @@
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
-default = ["serialized-stdlib", "faster"]
+default = ["serialized-stdlib", "faster", "explaining-traces"]
# Serializes standard library AST instead of parsing them every run
serialized-stdlib = ["serde", "bincode", "jrsonnet-parser/deserialize"]
# Same as above, but with generated code instead of serde. Reduces memory usage, but increases binary size and compilation time
@@ -17,17 +17,32 @@
# Replace some standard library functions with faster implementations (I.e manifestJsonEx)
# Library works fine without this feature, but requires more memory and time for std function calls
faster = []
+# Rustc-like trace visualization
+explaining-traces = ["annotate-snippets"]
[dependencies]
jrsonnet-parser = { path = "../jrsonnet-parser", version = "1.0.0" }
+jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "1.0.0" }
+pathdiff = "0.2.0"
+
closure = "0.3.0"
-jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "1.0.0" }
indexmap = "1.4.0"
+
md5 = "0.7.0"
base64 = "0.12.3"
-serde = { version = "1.0.114", optional = true }
-bincode = { version = "1.3.1", optional = true }
+# Serialized stdlib
+[dependencies.serde]
+version = "1.0.114"
+optional = true
+[dependencies.bincode]
+version = "1.3.1"
+optional = true
+
+# Explaining traces
+[dependencies.annotate-snippets]
+version = "0.9.0"
+optional = true
[build-dependencies]
jrsonnet-parser = { path = "../jrsonnet-parser", features = ["dump", "serialize", "deserialize"], version = "1.0.0" }
crates/jrsonnet-evaluator/src/trace.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/trace.rs
+++ /dev/null
@@ -1,99 +0,0 @@
-#[derive(Clone, PartialEq, Debug)]
-pub struct CodeLocation {
- pub line: usize,
- pub column: usize,
-
- pub line_start_offset: usize,
- pub line_end_offset: usize,
-}
-
-pub fn offset_to_location(file: &str, offsets: &[usize]) -> Vec<CodeLocation> {
- if offsets.is_empty() {
- return vec![];
- }
- let mut line = 1;
- let mut column = 1;
- let max_offset = *offsets.iter().max().unwrap();
-
- let mut offset_map = offsets
- .iter()
- .enumerate()
- .map(|(pos, offset)| (*offset, pos))
- .collect::<Vec<_>>();
- offset_map.sort_by_key(|v| v.0);
- offset_map.reverse();
-
- let mut out = vec![
- CodeLocation {
- column: 0,
- line: 0,
- line_start_offset: 0,
- line_end_offset: 0
- };
- offsets.len()
- ];
- let mut with_no_known_line_ending = vec![];
- let mut this_line_offset = 0;
- for (pos, ch) in file.chars().enumerate() {
- column += 1;
- match offset_map.last() {
- Some(x) if x.0 == pos => {
- let out_idx = x.1;
- with_no_known_line_ending.push(out_idx);
- out[out_idx].line = line;
- out[out_idx].column = column;
- out[out_idx].line_start_offset = this_line_offset;
- offset_map.pop();
- }
- _ => {}
- }
- if ch == '\n' {
- line += 1;
- column = 1;
-
- for idx in with_no_known_line_ending.drain(..) {
- out[idx].line_end_offset = pos;
- }
- this_line_offset = pos + 1;
-
- if pos == max_offset + 1 {
- break;
- }
- }
- }
- let file_end = file.chars().count();
- for idx in with_no_known_line_ending {
- out[idx].line_end_offset = file_end;
- }
-
- out
-}
-
-#[cfg(test)]
-pub mod tests {
- use super::{offset_to_location, CodeLocation};
-
- #[test]
- fn test() {
- assert_eq!(
- offset_to_location(
- "hello world\n_______________________________________________________",
- &[0, 14]
- ),
- vec![
- CodeLocation {
- line: 1,
- column: 1,
- line_start_offset: 0,
- line_end_offset: 11
- },
- CodeLocation {
- line: 2,
- column: 3,
- line_start_offset: 11,
- line_end_offset: 67
- }
- ]
- )
- }
-}
crates/jrsonnet-evaluator/src/trace/location.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/trace/location.rs
@@ -0,0 +1,99 @@
+#[derive(Clone, PartialEq, Debug)]
+pub struct CodeLocation {
+ pub line: usize,
+ pub column: usize,
+
+ pub line_start_offset: usize,
+ pub line_end_offset: usize,
+}
+
+pub fn offset_to_location(file: &str, offsets: &[usize]) -> Vec<CodeLocation> {
+ if offsets.is_empty() {
+ return vec![];
+ }
+ let mut line = 1;
+ let mut column = 1;
+ let max_offset = *offsets.iter().max().unwrap();
+
+ let mut offset_map = offsets
+ .iter()
+ .enumerate()
+ .map(|(pos, offset)| (*offset, pos))
+ .collect::<Vec<_>>();
+ offset_map.sort_by_key(|v| v.0);
+ offset_map.reverse();
+
+ let mut out = vec![
+ CodeLocation {
+ column: 0,
+ line: 0,
+ line_start_offset: 0,
+ line_end_offset: 0
+ };
+ offsets.len()
+ ];
+ let mut with_no_known_line_ending = vec![];
+ let mut this_line_offset = 0;
+ for (pos, ch) in file.chars().enumerate() {
+ column += 1;
+ match offset_map.last() {
+ Some(x) if x.0 == pos => {
+ let out_idx = x.1;
+ with_no_known_line_ending.push(out_idx);
+ out[out_idx].line = line;
+ out[out_idx].column = column;
+ out[out_idx].line_start_offset = this_line_offset;
+ offset_map.pop();
+ }
+ _ => {}
+ }
+ if ch == '\n' {
+ line += 1;
+ column = 1;
+
+ for idx in with_no_known_line_ending.drain(..) {
+ out[idx].line_end_offset = pos;
+ }
+ this_line_offset = pos + 1;
+
+ if pos == max_offset + 1 {
+ break;
+ }
+ }
+ }
+ let file_end = file.chars().count();
+ for idx in with_no_known_line_ending {
+ out[idx].line_end_offset = file_end;
+ }
+
+ out
+}
+
+#[cfg(test)]
+pub mod tests {
+ use super::{offset_to_location, CodeLocation};
+
+ #[test]
+ fn test() {
+ assert_eq!(
+ offset_to_location(
+ "hello world\n_______________________________________________________",
+ &[0, 14]
+ ),
+ vec![
+ CodeLocation {
+ line: 1,
+ column: 1,
+ line_start_offset: 0,
+ line_end_offset: 11
+ },
+ CodeLocation {
+ line: 2,
+ column: 3,
+ line_start_offset: 11,
+ line_end_offset: 67
+ }
+ ]
+ )
+ }
+}
crates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/trace/mod.rs
@@ -0,0 +1,217 @@
+mod location;
+
+use crate::{EvaluationState, LocError};
+pub use location::*;
+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::fmt::Write,
+ evaluation_state: &EvaluationState,
+ error: &LocError,
+ ) -> Result<(), std::fmt::Error>;
+ // fn print_trace(
+ // &self,
+ // evaluation_state: &EvaluationState,
+ // error: &LocError,
+ // ) -> Result<(), std::fmt::Error> {
+ // self.write_trace(&mut std::fmt::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,
+ pub padding: usize,
+}
+
+impl TraceFormat for CompactFormat {
+ fn write_trace(
+ &self,
+ out: &mut dyn std::fmt::Write,
+ evaluation_state: &EvaluationState,
+ error: &LocError,
+ ) -> Result<(), std::fmt::Error> {
+ writeln!(out, "{:?}", error.0)?;
+ let file_names = (error.1)
+ .0
+ .iter()
+ .map(|el| {
+ let resolved_path = self.resolver.resolve(&el.location.0);
+ // TODO: Process all trace elements first
+ let location = evaluation_state
+ .map_source_locations(&el.location.0, &[el.location.1, el.location.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,
+ "{:<p$}{:<w$}: {}",
+ "",
+ file,
+ el.desc,
+ p = self.padding,
+ w = align
+ )?;
+ }
+ Ok(())
+ }
+}
+
+pub struct JSFormat;
+impl TraceFormat for JSFormat {
+ fn write_trace(
+ &self,
+ out: &mut dyn std::fmt::Write,
+ evaluation_state: &EvaluationState,
+ error: &LocError,
+ ) -> Result<(), std::fmt::Error> {
+ writeln!(out, "{:?}", error.0)?;
+ for (i, item) in (error.1).0.iter().enumerate() {
+ if i != 0 {
+ writeln!(out)?;
+ }
+ let desc = &item.desc;
+ let source = item.location.clone();
+ let start_end = evaluation_state.map_source_locations(&source.0, &[source.1, source.2]);
+
+ write!(
+ out,
+ " at {} ({}:{}:{})",
+ desc,
+ source.0.to_str().unwrap(),
+ start_end[0].line,
+ start_end[0].column,
+ )?;
+ }
+ Ok(())
+ }
+}
+
+/// rustc-like trace displaying
+#[cfg(feature = "explaining-traces")]
+pub struct ExplainingFormat {
+ pub resolver: PathResolver,
+}
+#[cfg(feature = "explaining-traces")]
+impl TraceFormat for ExplainingFormat {
+ fn write_trace(
+ &self,
+ out: &mut dyn std::fmt::Write,
+ evaluation_state: &EvaluationState,
+ error: &LocError,
+ ) -> Result<(), std::fmt::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.desc;
+ let source = item.location.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(())
+ }
+}
crates/jrsonnet-trace/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-trace/Cargo.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-[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.rsdiffbeforeafterbothno changes