difftreelog
feat move trace mapping api to evaluator
in: master
4 files changed
cmds/jrsonnet/src/location.rsdiffbeforeafterboth--- a/cmds/jrsonnet/src/location.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 = 0;
- 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 + 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: 1,
- line_end_offset: 11
- },
- CodeLocation {
- line: 2,
- column: 3,
- line_start_offset: 12,
- line_end_offset: 67
- }
- ]
- )
- }
-}
cmds/jrsonnet/src/main.rsdiffbeforeafterboth1pub mod location;23use clap::Clap;4use jrsonnet_evaluator::{EvaluationState, LocError, StackTrace, Val};5use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParserSettings};6use location::{offset_to_location, CodeLocation};7use std::env::current_dir;8use std::{collections::HashMap, path::PathBuf, rc::Rc, str::FromStr};910#[global_allocator]11static GLOBAL: mimallocator::Mimalloc = mimallocator::Mimalloc;1213enum Format {14 None,15 Json,16 Yaml,17}1819impl FromStr for Format {20 type Err = &'static str;21 fn from_str(s: &str) -> Result<Self, Self::Err> {22 Ok(match s {23 "none" => Format::None,24 "json" => Format::Json,25 "yaml" => Format::Yaml,26 _ => return Err("no such format"),27 })28 }29}3031#[derive(PartialEq)]32enum TraceFormat {33 CppJsonnet,34 GoJsonnet,35 Custom,36}37impl FromStr for TraceFormat {38 type Err = &'static str;39 fn from_str(s: &str) -> Result<Self, Self::Err> {40 Ok(match s {41 "cpp" => TraceFormat::CppJsonnet,42 "go" => TraceFormat::GoJsonnet,43 "default" => TraceFormat::Custom,44 _ => return Err("no such format"),45 })46 }47}4849#[derive(Clone)]50struct ExtStr {51 name: String,52 value: String,53}54impl FromStr for ExtStr {55 type Err = &'static str;56 fn from_str(s: &str) -> Result<Self, Self::Err> {57 let out: Vec<_> = s.split('=').collect();58 match out.len() {59 1 => Ok(ExtStr {60 name: out[0].to_owned(),61 value: std::env::var(out[0]).or(Err("missing env var"))?,62 }),63 2 => Ok(ExtStr {64 name: out[0].to_owned(),65 value: out[1].to_owned(),66 }),67 _ => Err("bad ext-str syntax"),68 }69 }70}7172#[derive(Clap)]73#[clap(version = "0.1.0", author = "Lach <iam@lach.pw>")]74struct Opts {75 #[clap(long, about = "Disable global std variable")]76 no_stdlib: bool,77 #[clap(long, about = "Add external string")]78 ext_str: Vec<ExtStr>,79 #[clap(long, about = "Add external string from code")]80 ext_code: Vec<ExtStr>,81 #[clap(long, about = "Add TLA")]82 tla_str: Vec<ExtStr>,83 #[clap(long, about = "Add TLA from code")]84 tla_code: Vec<ExtStr>,85 #[clap(long, short = "f", default_value = "json", possible_values = &["none", "json", "yaml"], about = "Output format, wraps resulting value to corresponding std.manifest call")]86 format: Format,87 #[clap(long, default_value = "default", possible_values = &["cpp", "go", "default"], about = "Emulated needed stacktrace display")]88 trace_format: TraceFormat,8990 #[clap(91 long,92 short = "s",93 default_value = "200",94 about = "Number of allowed stack frames"95 )]96 max_stack: usize,97 #[clap(98 long,99 short = "t",100 default_value = "20",101 about = "Max length of stack trace before cropping"102 )]103 max_trace: usize,104105 #[clap(long, short = "J", about = "Library search dir")]106 jpath: Vec<PathBuf>,107108 #[clap(109 long,110 default_value = "3",111 about = "When using --format, this option specifies string to pad output with"112 )]113 line_padding: usize,114115 #[clap(about = "File to compile", index = 1)]116 input: String,117}118119fn main() {120 let opts: Opts = Opts::parse();121 let evaluator = jrsonnet_evaluator::EvaluationState::default();122 evaluator.set_max_trace(opts.max_trace);123 evaluator.set_max_stack(opts.max_stack);124 evaluator.set_import_resolver(Box::new(jrsonnet_evaluator::FileImportResolver {125 library_paths: opts.jpath.clone(),126 }));127 if !opts.no_stdlib {128 evaluator.with_stdlib();129 }130 for ExtStr { name, value } in opts.ext_str.iter().cloned() {131 evaluator.add_ext_var(name.into(), Val::Str(value.into()));132 }133 for ExtStr { name, value } in opts.ext_code.iter().cloned() {134 evaluator.add_ext_var(name.into(), evaluator.parse_evaluate_raw(&value).unwrap());135 }136 let mut input = current_dir().unwrap();137 input.push(opts.input.clone());138 let code_string = String::from_utf8(std::fs::read(opts.input.clone()).unwrap()).unwrap();139 if let Err(e) = evaluator.add_file(Rc::new(input.clone()), code_string.clone().into()) {140 print_syntax_error(e, &input, &code_string);141 std::process::exit(1);142 }143 let result = evaluator.evaluate_file(&input);144 match result {145 Ok(v) => {146 let v = match v {147 Val::Func(f) => {148 let mut desc_map = HashMap::new();149 for ExtStr { name, value } in opts.tla_str.iter().cloned() {150 desc_map.insert(name, el!(Expr::Str(value.into())));151 }152 for ExtStr { name, value } in opts.tla_code.iter().cloned() {153 desc_map.insert(154 name,155 jrsonnet_parser::parse(156 &value,157 &ParserSettings {158 file_name: Rc::new(PathBuf::new()),159 loc_data: false,160 },161 )162 .unwrap(),163 );164 }165 evaluator.add_global("__tmp__tlf__".into(), Val::Func(f));166 evaluator167 .evaluate_raw(el!(Expr::Apply(168 el!(Expr::Var("__tmp__tlf__".into())),169 ArgsDesc(desc_map.into_iter().map(|(k, v)| Arg(Some(k), v)).collect()),170 false,171 )))172 .unwrap()173 }174 v => v,175 };176 let v = evaluator.run_in_state(|| match opts.format {177 Format::Json => Ok(Val::Str(v.into_json(opts.line_padding)?)),178 Format::Yaml => {179 evaluator.add_global("__tmp__to_yaml__".into(), v);180 evaluator.parse_evaluate_raw("std.manifestYamlDoc(__tmp__to_yaml__, \" \")")181 }182 _ => Ok(v),183 });184 let v = match v {185 Ok(v) => v,186 Err(err) => {187 print_error(&err, evaluator, &opts);188 std::process::exit(1);189 }190 };191 match v {192 Val::Str(s) => println!("{}", s),193 Val::Num(n) => println!("{}", n),194 _v => eprintln!(195 "jsonnet output is not a string.\nDid you forgot to set --format, or wrap your data with std.manifestJson?"196 ),197 }198 }199 Err(err) => {200 print_error(&err, evaluator, &opts);201 std::process::exit(1);202 }203 }204}205206fn print_error(err: &LocError, evaluator: EvaluationState, opts: &Opts) {207 println!("Error: {:?}", err.0);208 print_trace(&(err.1), evaluator, &opts);209}210211fn print_syntax_error(error: jrsonnet_parser::ParseError, file: &PathBuf, code: &str) {212 use annotate_snippets::{213 display_list::{DisplayList, FormatOptions},214 snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},215 };216 //&("Expected: ".to_owned() + error.expected)217 let origin = file.to_str().unwrap();218 let error_message = format!("Expected: {}", error.expected);219 let snippet = Snippet {220 opt: FormatOptions {221 color: true,222 ..Default::default()223 },224 title: Some(Annotation {225 label: Some(&error_message),226 id: None,227 annotation_type: AnnotationType::Error,228 }),229 footer: vec![],230 slices: vec![Slice {231 source: &code,232 line_start: 1,233 origin: Some(origin),234 fold: false,235 annotations: vec![SourceAnnotation {236 label: "At this position",237 annotation_type: AnnotationType::Error,238 range: (error.location.offset, error.location.offset + 1),239 }],240 }],241 };242243 let dl = DisplayList::from(snippet);244 println!("{}", dl);245}246247fn print_trace(trace: &StackTrace, evaluator: EvaluationState, opts: &Opts) {248 use annotate_snippets::{249 display_list::{DisplayList, FormatOptions},250 snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},251 };252 for item in trace.0.iter() {253 let desc = &item.1;254 let source = item.0.clone();255 let code = evaluator.get_source(&source.0);256 if code.is_none() {257 continue;258 }259 let code = code.unwrap();260 let start_end = offset_to_location(&code, &[source.1, source.2]);261 if opts.trace_format == TraceFormat::Custom {262 let source_fragment: String = code263 .chars()264 .skip(start_end[0].line_start_offset)265 .take(start_end[1].line_end_offset - start_end[0].line_start_offset)266 .collect();267 let snippet = Snippet {268 opt: FormatOptions {269 color: true,270 ..Default::default()271 },272 title: Some(Annotation {273 label: Some(&item.1),274 id: None,275 annotation_type: AnnotationType::Error,276 }),277 footer: vec![],278 slices: vec![Slice {279 source: &source_fragment,280 line_start: start_end[0].line,281 origin: Some(&source.0.to_str().unwrap()),282 fold: false,283 annotations: vec![SourceAnnotation {284 label: desc,285 annotation_type: AnnotationType::Error,286 range: (287 source.1 - start_end[0].line_start_offset,288 source.2 - start_end[0].line_start_offset,289 ),290 }],291 }],292 };293294 let dl = DisplayList::from(snippet);295 println!("{}", dl);296 } else {297 print_jsonnet_pair(298 source.0.to_str().unwrap(),299 &start_end[0],300 &start_end[1],301 opts.trace_format == TraceFormat::GoJsonnet,302 );303 }304 }305}306307fn print_jsonnet_pair(file: &str, start: &CodeLocation, end: &CodeLocation, is_go: bool) {308 if is_go {309 print!(" ");310 } else {311 print!(" ");312 }313 print!("{}:", file);314 if start.line == end.line {315 // IDK why, but this is the behavior original jsonnet cpp impl shows316 if start.column == end.column || !is_go && start.column + 1 == end.column {317 println!("{}:{}", start.line, end.column)318 } else {319 println!("{}:{}-{}", start.line, start.column, end.column);320 }321 } else {322 println!(323 "({}:{})-({}:{})",324 start.line, end.column, start.line, end.column325 );326 }327}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")]75 ext_str: Vec<ExtStr>,76 #[clap(long, about = "Add external string from code")]77 ext_code: Vec<ExtStr>,78 #[clap(long, about = "Add TLA")]79 tla_str: Vec<ExtStr>,80 #[clap(long, about = "Add TLA from code")]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(long, short = "J", about = "Library search dir")]103 jpath: Vec<PathBuf>,104105 #[clap(106 long,107 default_value = "3",108 about = "When using --format, this option specifies string to pad output with"109 )]110 line_padding: usize,111112 #[clap(about = "File to compile", index = 1)]113 input: String,114}115116fn main() {117 let opts: Opts = Opts::parse();118 let evaluator = jrsonnet_evaluator::EvaluationState::default();119 evaluator.set_max_trace(opts.max_trace);120 evaluator.set_max_stack(opts.max_stack);121 evaluator.set_import_resolver(Box::new(jrsonnet_evaluator::FileImportResolver {122 library_paths: opts.jpath.clone(),123 }));124 if !opts.no_stdlib {125 evaluator.with_stdlib();126 }127 for ExtStr { name, value } in opts.ext_str.iter().cloned() {128 evaluator.add_ext_var(name.into(), Val::Str(value.into()));129 }130 for ExtStr { name, value } in opts.ext_code.iter().cloned() {131 evaluator.add_ext_var(name.into(), evaluator.parse_evaluate_raw(&value).unwrap());132 }133 let mut input = current_dir().unwrap();134 input.push(opts.input.clone());135 let code_string = String::from_utf8(std::fs::read(opts.input.clone()).unwrap()).unwrap();136 if let Err(e) = evaluator.add_file(Rc::new(input.clone()), code_string.clone().into()) {137 print_syntax_error(e, &input, &code_string);138 std::process::exit(1);139 }140 let result = evaluator.evaluate_file(&input);141 match result {142 Ok(v) => {143 let v = match v {144 Val::Func(f) => {145 let mut desc_map = HashMap::new();146 for ExtStr { name, value } in opts.tla_str.iter().cloned() {147 desc_map.insert(name, el!(Expr::Str(value.into())));148 }149 for ExtStr { name, value } in opts.tla_code.iter().cloned() {150 desc_map.insert(151 name,152 jrsonnet_parser::parse(153 &value,154 &ParserSettings {155 file_name: Rc::new(PathBuf::new()),156 loc_data: false,157 },158 )159 .unwrap(),160 );161 }162 evaluator.add_global("__tmp__tlf__".into(), Val::Func(f));163 evaluator164 .evaluate_raw(el!(Expr::Apply(165 el!(Expr::Var("__tmp__tlf__".into())),166 ArgsDesc(desc_map.into_iter().map(|(k, v)| Arg(Some(k), v)).collect()),167 false,168 )))169 .unwrap()170 }171 v => v,172 };173 let v = evaluator.run_in_state(|| match opts.format {174 Format::Json => Ok(Val::Str(v.into_json(opts.line_padding)?)),175 Format::Yaml => {176 evaluator.add_global("__tmp__to_yaml__".into(), v);177 evaluator.parse_evaluate_raw("std.manifestYamlDoc(__tmp__to_yaml__, \" \")")178 }179 _ => Ok(v),180 });181 let v = match v {182 Ok(v) => v,183 Err(err) => {184 print_error(&err, evaluator, &opts);185 std::process::exit(1);186 }187 };188 match v {189 Val::Str(s) => println!("{}", s),190 Val::Num(n) => println!("{}", n),191 _v => eprintln!(192 "jsonnet output is not a string.\nDid you forgot to set --format, or wrap your data with std.manifestJson?"193 ),194 }195 }196 Err(err) => {197 print_error(&err, evaluator, &opts);198 std::process::exit(1);199 }200 }201}202203fn print_error(err: &LocError, evaluator: EvaluationState, opts: &Opts) {204 println!("Error: {:?}", err.0);205 print_trace(&(err.1), evaluator, &opts);206}207208fn print_syntax_error(error: jrsonnet_parser::ParseError, file: &PathBuf, code: &str) {209 use annotate_snippets::{210 display_list::{DisplayList, FormatOptions},211 snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},212 };213 //&("Expected: ".to_owned() + error.expected)214 let origin = file.to_str().unwrap();215 let error_message = format!("Expected: {}", error.expected);216 let snippet = Snippet {217 opt: FormatOptions {218 color: true,219 ..Default::default()220 },221 title: Some(Annotation {222 label: Some(&error_message),223 id: None,224 annotation_type: AnnotationType::Error,225 }),226 footer: vec![],227 slices: vec![Slice {228 source: &code,229 line_start: 1,230 origin: Some(origin),231 fold: false,232 annotations: vec![SourceAnnotation {233 label: "At this position",234 annotation_type: AnnotationType::Error,235 range: (error.location.offset, error.location.offset + 1),236 }],237 }],238 };239240 let dl = DisplayList::from(snippet);241 println!("{}", dl);242}243244fn print_trace(trace: &StackTrace, evaluator: EvaluationState, opts: &Opts) {245 use annotate_snippets::{246 display_list::{DisplayList, FormatOptions},247 snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation},248 };249 for item in trace.0.iter() {250 let desc = &item.1;251 let source = item.0.clone();252 let start_end = evaluator.map_source_locations(&source.0, &[source.1, source.2]);253 if opts.trace_format == TraceFormat::Custom {254 let source_fragment: String = evaluator255 .get_source(&source.0)256 .unwrap()257 .chars()258 .skip(start_end[0].line_start_offset)259 .take(start_end[1].line_end_offset - start_end[0].line_start_offset)260 .collect();261 let snippet = Snippet {262 opt: FormatOptions {263 color: true,264 ..Default::default()265 },266 title: Some(Annotation {267 label: Some(&item.1),268 id: None,269 annotation_type: AnnotationType::Error,270 }),271 footer: vec![],272 slices: vec![Slice {273 source: &source_fragment,274 line_start: start_end[0].line,275 origin: Some(&source.0.to_str().unwrap()),276 fold: false,277 annotations: vec![SourceAnnotation {278 label: desc,279 annotation_type: AnnotationType::Error,280 range: (281 source.1 - start_end[0].line_start_offset,282 source.2 - start_end[0].line_start_offset,283 ),284 }],285 }],286 };287288 let dl = DisplayList::from(snippet);289 println!("{}", dl);290 } else {291 print_jsonnet_pair(292 source.0.to_str().unwrap(),293 &start_end[0],294 &start_end[1],295 opts.trace_format == TraceFormat::GoJsonnet,296 );297 }298 }299}300301fn print_jsonnet_pair(file: &str, start: &CodeLocation, end: &CodeLocation, is_go: bool) {302 if is_go {303 print!(" ");304 } else {305 print!(" ");306 }307 print!("{}:", file);308 if start.line == end.line {309 // IDK why, but this is the behavior original jsonnet cpp impl shows310 if start.column == end.column || !is_go && start.column + 1 == end.column {311 println!("{}:{}", start.line, end.column)312 } else {313 println!("{}:{}-{}", start.line, start.column, end.column);314 }315 } else {316 println!(317 "({}:{})-({}:{})",318 start.line, end.column, start.line, end.column319 );320 }321}crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -16,6 +16,7 @@
mod map;
mod obj;
mod val;
+pub mod trace;
pub use ctx::*;
pub use dynamic::*;
@@ -27,6 +28,7 @@
pub use obj::*;
use std::{cell::{Ref, RefCell, RefMut}, collections::HashMap, fmt::Debug, path::PathBuf, rc::Rc};
pub use val::*;
+use trace::{offset_to_location, CodeLocation};
type BindableFn = dyn Fn(Option<ObjValue>, Option<ObjValue>) -> Result<LazyVal>;
#[derive(Clone)]
@@ -187,6 +189,10 @@
let ro_map = &self.data().files;
ro_map.get(name).map(|value| value.0.clone())
}
+ pub fn map_source_locations(&self, file: &PathBuf, locs: &[usize]) -> Vec<CodeLocation> {
+ offset_to_location(&self.get_source(file).unwrap(), locs)
+ }
+
pub fn evaluate_file(&self, name: &PathBuf) -> Result<Val> {
self.run_in_state(|| {
let expr: LocExpr = {
crates/jrsonnet-evaluator/src/trace.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/trace.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 = 0;
+ 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 + 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: 1,
+ line_end_offset: 11
+ },
+ CodeLocation {
+ line: 2,
+ column: 3,
+ line_start_offset: 12,
+ line_end_offset: 67
+ }
+ ]
+ )
+ }
+}