--- /dev/null +++ b/cmds/jsonnet/src/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 { + if offsets.is_empty() { + return vec![]; + } + let mut line = 1; + let mut column = 0; + let max_offset = *offsets.iter().max().unwrap(); + + let mut offset_map = offsets + .iter() + .enumerate() + .map(|(pos, offset)| (*offset, pos)) + .collect::>(); + 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 + 1; + offset_map.pop(); + } + _ => {} + } + if ch == '\n' { + line += 1; + column = 0; + + for idx in with_no_known_line_ending.drain(..) { + out[idx].line_end_offset = pos; + } + this_line_offset = pos; + + 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 + } + ] + ) + } +} --- a/cmds/jsonnet/src/main.rs +++ b/cmds/jsonnet/src/main.rs @@ -1,5 +1,8 @@ +pub mod location; + use clap::Clap; use jsonnet_evaluator::{EvaluationState, LocError, StackTrace, Val}; +use location::{offset_to_location, CodeLocation}; use std::env::current_dir; use std::str::FromStr; @@ -144,39 +147,7 @@ println!("Error: {:?}", err.0); print_trace(&(err.1), evaluator, &opts); } - -fn line_columns(file: &str, offsets: &[usize]) -> Vec<(usize, usize)> { - if offsets.is_empty() { - return vec![]; - } - let mut line = 0; - let mut column = 0; - let max_offset = *offsets.iter().max().unwrap(); - - let mut offset_map = offsets - .iter() - .enumerate() - .map(|(pos, offset)| (*offset, pos)) - .collect::>(); - offset_map.sort_by_key(|v| v.0); - offset_map.reverse(); - let mut out = vec![(0usize, 0usize); offsets.len()]; - for (pos, ch) in (0..max_offset + 1).zip(file.chars()) { - column += 1; - if offset_map.last().unwrap().0 == pos { - out[offset_map.last().unwrap().1] = (line, column); - offset_map.pop(); - } - if ch == '\n' { - line += 1; - column = 0; - } - } - - out -} - fn print_trace(trace: &StackTrace, evaluator: EvaluationState, opts: &Opts) { use annotate_snippets::{ display_list::{DisplayList, FormatOptions}, @@ -193,8 +164,13 @@ continue; } let code = code.unwrap(); - let start_end = line_columns(&code, &[source.1, source.2]); + let start_end = offset_to_location(&code, &[source.1, source.2]); if opts.trace_format == TraceFormat::Custom { + let source_fragment: String = code + .chars() + .skip(start_end[0].line_start_offset) + .take(start_end[1].line_end_offset - start_end[0].line_start_offset) + .collect(); let snippet = Snippet { opt: FormatOptions { color: true, @@ -207,14 +183,17 @@ }), footer: vec![], slices: vec![Slice { - source: &code, - line_start: 1, + source: &source_fragment, + line_start: start_end[0].line, origin: Some(&source.0.to_str().unwrap()), fold: false, annotations: vec![SourceAnnotation { label: desc, annotation_type: AnnotationType::Error, - range: (source.1, source.2), + range: ( + source.1 - start_end[0].line_start_offset, + source.2 - start_end[0].line_start_offset, + ), }], }], }; @@ -222,36 +201,34 @@ let dl = DisplayList::from(snippet); println!("{}", dl); } else { - if opts.trace_format == TraceFormat::CppJsonnet { - print!(" "); - } else { - print!(" "); - } - print!("{}:", source.0.to_str().unwrap()); - if start_end[0].0 == start_end[1].0 { - // IDK why, but this is the behavior original jsonnet shows - if start_end[0].1 == start_end[1].1 - || opts.trace_format == TraceFormat::CppJsonnet - && start_end[0].1 + 1 == start_end[1].1 - { - println!("{}:{}", start_end[0].0 + 1, start_end[0].1) - } else { - println!( - "{}:{}-{}", - start_end[0].0 + 1, - start_end[0].1, - start_end[1].1 - ); - } - } else { - println!( - "({}:{})-({}:{})", - start_end[0].0 + 1, - start_end[0].1, - start_end[1].0 + 1, - start_end[1].1 - ); - } + print_jsonnet_pair( + source.0.to_str().unwrap(), + &start_end[0], + &start_end[1], + opts.trace_format == TraceFormat::GoJsonnet, + ); + } + } +} + +fn print_jsonnet_pair(file: &str, start: &CodeLocation, end: &CodeLocation, is_go: bool) { + if is_go { + print!(" "); + } else { + print!(" "); + } + print!("{}:", file); + if start.line == end.line { + // IDK why, but this is the behavior original jsonnet cpp impl shows + if start.column == end.column || !is_go && start.column + 1 == end.column { + println!("{}:{}", start.line, end.column) + } else { + println!("{}:{}-{}", start.line, start.column, end.column); } + } else { + println!( + "({}:{})-({}:{})", + start.line, end.column, start.line, end.column + ); } }