--- a/Cargo.lock +++ b/Cargo.lock @@ -7,6 +7,7 @@ checksum = "5c96c3d1062ea7101741480185a6a1275eab01cbe8b20e378d1311bc056d2e08" dependencies = [ "unicode-width", + "yansi-term", ] [[package]] @@ -513,3 +514,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "yansi-term" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1" +dependencies = [ + "winapi", +] --- a/crates/jrsonnet-evaluator/Cargo.toml +++ b/crates/jrsonnet-evaluator/Cargo.toml @@ -55,6 +55,7 @@ # Explaining traces [dependencies.annotate-snippets] version = "0.9.0" +features = ["color"] optional = true [build-dependencies] --- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -78,7 +78,11 @@ ImportBadFileUtf8(PathBuf), #[error("tried to import {1} from {0}, but imports is not supported")] ImportNotSupported(PathBuf, PathBuf), - #[error("syntax error")] + #[error( + "syntax error, expected one of {}, got {:?}", + .error.expected, + .source_code.chars().nth(error.location.offset).map(|c| c.to_string()).unwrap_or_else(|| "EOF".into()) + )] ImportSyntaxError { path: Rc, source_code: Rc, --- a/crates/jrsonnet-evaluator/src/evaluate.rs +++ b/crates/jrsonnet-evaluator/src/evaluate.rs @@ -419,18 +419,12 @@ use Expr::*; let LocExpr(expr, loc) = expr; Ok(match &**expr { - Literal(LiteralType::This) => Val::Obj( - context - .this() - .clone() - .ok_or_else(|| CantUseSelfOutsideOfObject)?, - ), - Literal(LiteralType::Dollar) => Val::Obj( - context - .dollar() - .clone() - .ok_or_else(|| NoTopLevelObjectFound)?, - ), + Literal(LiteralType::This) => { + Val::Obj(context.this().clone().ok_or(CantUseSelfOutsideOfObject)?) + } + Literal(LiteralType::Dollar) => { + Val::Obj(context.dollar().clone().ok_or(NoTopLevelObjectFound)?) + } Literal(LiteralType::True) => Val::Bool(true), Literal(LiteralType::False) => Val::Bool(false), Literal(LiteralType::Null) => Val::Null, --- a/crates/jrsonnet-evaluator/src/trace/location.rs +++ b/crates/jrsonnet-evaluator/src/trace/location.rs @@ -1,5 +1,7 @@ #[derive(Clone, PartialEq, Debug)] pub struct CodeLocation { + pub offset: usize, + pub line: usize, pub column: usize, @@ -25,6 +27,7 @@ let mut out = vec![ CodeLocation { + offset: 0, column: 0, line: 0, line_start_offset: 0, @@ -40,6 +43,7 @@ Some(x) if x.0 == pos => { let out_idx = x.1; with_no_known_line_ending.push(out_idx); + out[out_idx].offset = pos; out[out_idx].line = line; out[out_idx].column = column; out[out_idx].line_start_offset = this_line_offset; @@ -82,12 +86,14 @@ ), vec![ CodeLocation { + offset: 0, line: 1, column: 2, line_start_offset: 0, - line_end_offset: 11 + line_end_offset: 11, }, CodeLocation { + offset: 14, line: 2, column: 4, line_start_offset: 12, --- a/crates/jrsonnet-evaluator/src/trace/mod.rs +++ b/crates/jrsonnet-evaluator/src/trace/mod.rs @@ -1,6 +1,6 @@ mod location; -use crate::{EvaluationState, LocError}; +use crate::{error::Error, EvaluationState, LocError}; pub use location::*; use std::path::PathBuf; @@ -87,6 +87,33 @@ error: &LocError, ) -> Result<(), std::fmt::Error> { writeln!(out, "{}", error.error())?; + if let Error::ImportSyntaxError { + path, + source_code, + error, + } = error.error() + { + use std::fmt::Write; + let mut n = self.resolver.resolve(path); + let mut offset = error.location.offset; + let is_eof = if offset >= source_code.len() { + offset = source_code.len() - 1; + true + } else { + false + }; + let mut location = offset_to_location(source_code, &[offset]) + .into_iter() + .next() + .unwrap(); + if is_eof { + location.column += 1; + } + + write!(n, ":").unwrap(); + print_code_location(&mut n, &location, &location).unwrap(); + write!(out, "{: Result<(), std::fmt::Error> { - use annotate_snippets::{ - display_list::{DisplayList, FormatOptions}, - snippet::{AnnotationType, Slice, Snippet, SourceAnnotation}, - }; writeln!(out, "{}", error.error())?; + if let Error::ImportSyntaxError { + path, + source_code, + error, + } = error.error() + { + let mut offset = error.location.offset; + if offset >= source_code.len() { + offset = source_code.len() - 1; + } + let mut location = offset_to_location(source_code, &[offset]) + .into_iter() + .next() + .unwrap(); + if location.column >= 1 { + location.column -= 1; + } + + self.print_snippet( + out, + source_code, + path, + &location, + &location, + "^ syntax error", + )?; + } let trace = &error.trace(); 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(); + self.print_snippet( + out, + &evaluation_state.get_source(&source.0).unwrap(), + &source.0, + &start_end[0], + &start_end[1], + desc, + )?; + } + Ok(()) + } +} + +impl ExplainingFormat { + fn print_snippet( + &self, + out: &mut dyn std::fmt::Write, + source: &str, + origin: &PathBuf, + start: &CodeLocation, + end: &CodeLocation, + desc: &str, + ) -> Result<(), std::fmt::Error> { + use annotate_snippets::{ + display_list::{DisplayList, FormatOptions}, + snippet::{AnnotationType, Slice, Snippet, SourceAnnotation}, + }; + + let source_fragment: String = source + .chars() + .skip(start.line_start_offset) + .take(end.line_end_offset - end.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 origin = self.resolver.resolve(origin); + let snippet = Snippet { + opt: FormatOptions { + color: true, + ..Default::default() + }, + title: None, + footer: vec![], + slices: vec![Slice { + source: &source_fragment, + line_start: start.line, + origin: Some(&origin), + fold: false, + annotations: vec![SourceAnnotation { + label: desc, + annotation_type: AnnotationType::Error, + range: ( + start.offset - start.line_start_offset, + end.offset - start.line_start_offset, + ), }], - }; + }], + }; - let dl = DisplayList::from(snippet); - writeln!(out, "{}", dl)?; - } + let dl = DisplayList::from(snippet); + writeln!(out, "{}", dl)?; + Ok(()) } } --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -349,7 +349,7 @@ for v in arr.iter() { out.push_str("---\n"); out.push_str(&v.manifest(format)?); - out.push_str("\n"); + out.push('\n'); } out.push_str("..."); } --- a/crates/jrsonnet-parser/src/lib.rs +++ b/crates/jrsonnet-parser/src/lib.rs @@ -37,7 +37,7 @@ rule reserved() = ("assert" / "else" / "error" / "false" / "for" / "function" / "if" / "import" / "importstr" / "in" / "local" / "null" / "tailstrict" / "then" / "self" / "super" / "true") end_of_ident() rule id() = quiet!{ !reserved() alpha() (alpha() / digit())*} / expected!("") - rule keyword(id: &'static str) + rule keyword(id: &'static str) -> () = ##parse_string_literal(id) end_of_ident() // Adds location data information to existing expression rule l(s: &ParserSettings, x: rule) -> LocExpr @@ -85,11 +85,11 @@ [' ' | '\t']*<, {prefix.len() - 1}> "|||" {let mut l = empty_lines.to_owned(); l.push_str(first_line); l.extend(lines); l} pub rule string() -> String - = "\"" str:$(("\\\"" / "\\\\" / (!['"'][_]))*) "\"" {unescape::unescape(str).unwrap()} + = quiet!{ "\"" str:$(("\\\"" / "\\\\" / (!['"'][_]))*) "\"" {unescape::unescape(str).unwrap()} / "'" str:$(("\\'" / "\\\\" / (!['\''][_]))*) "'" {unescape::unescape(str).unwrap()} / "@'" str:$(("''" / (!['\''][_]))*) "'" {str.replace("''", "'")} / "@\"" str:$(("\"\"" / (!['"'][_]))*) "\"" {str.replace("\"\"", "\"")} - / string_block() + / string_block() } / expected!("") pub rule field_name(s: &ParserSettings) -> expr::FieldName = name:$(id()) {expr::FieldName::Fixed(name.into())} @@ -208,54 +208,59 @@ SliceDesc { start, end, step } } + rule binop(x: rule<()>) -> () + = quiet!{ x() } / expected!("") + rule unaryop(x: rule<()>) -> () + = quiet!{ x() } / expected!("") + rule expr(s: &ParserSettings) -> LocExpr = start:position!() a:precedence! { - a:(@) _ "||" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Or, b))} + a:(@) _ binop(<"||">) _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Or, b))} -- - a:(@) _ "&&" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::And, b))} + a:(@) _ binop(<"&&">) _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::And, b))} -- - a:(@) _ "|" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitOr, b))} + a:(@) _ binop(<"|">) _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitOr, b))} -- - a:@ _ "^" _ b:(@) {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitXor, b))} + a:@ _ binop(<"^">) _ b:(@) {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitXor, b))} -- - a:(@) _ "&" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitAnd, b))} + a:(@) _ binop(<"&">) _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::BitAnd, b))} -- - a:(@) _ "==" _ b:@ {loc_expr_todo!(Expr::Apply( + a:(@) _ binop(<"==">) _ b:@ {loc_expr_todo!(Expr::Apply( el!(Expr::Intrinsic("equals".into())), ArgsDesc(vec![Arg(None, a), Arg(None, b)]), true ))} - a:(@) _ "!=" _ b:@ {loc_expr_todo!(Expr::UnaryOp(UnaryOpType::Not, el!(Expr::Apply( + a:(@) _ binop(<"!=">) _ b:@ {loc_expr_todo!(Expr::UnaryOp(UnaryOpType::Not, el!(Expr::Apply( el!(Expr::Intrinsic("equals".into())), ArgsDesc(vec![Arg(None, a), Arg(None, b)]), true ))))} -- - a:(@) _ "<" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lt, b))} - a:(@) _ ">" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Gt, b))} - a:(@) _ "<=" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lte, b))} - a:(@) _ ">=" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Gte, b))} - a:(@) _ keyword("in") _ b:@ {loc_expr_todo!(Expr::Apply( + a:(@) _ binop(<"<">) _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lt, b))} + a:(@) _ binop(<">">) _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Gt, b))} + a:(@) _ binop(<"<=">) _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lte, b))} + a:(@) _ binop(<">=">) _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Gte, b))} + a:(@) _ binop() _ b:@ {loc_expr_todo!(Expr::Apply( el!(Expr::Intrinsic("objectHasEx".into())), ArgsDesc(vec![Arg(None, b), Arg(None, a), Arg(None, el!(Expr::Literal(LiteralType::True)))]), true ))} -- - a:(@) _ "<<" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lhs, b))} - a:(@) _ ">>" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Rhs, b))} + a:(@) _ binop(<"<<">) _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Lhs, b))} + a:(@) _ binop(<">>">) _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Rhs, b))} -- - a:(@) _ "+" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Add, b))} - a:(@) _ "-" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Sub, b))} + a:(@) _ binop(<"+">) _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Add, b))} + a:(@) _ binop(<"-">) _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Sub, b))} -- - a:(@) _ "*" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Mul, b))} - a:(@) _ "/" _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Div, b))} - a:(@) _ "%" _ b:@ {loc_expr_todo!(Expr::Apply( + a:(@) _ binop(<"*">) _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Mul, b))} + a:(@) _ binop(<"/">) _ b:@ {loc_expr_todo!(Expr::BinaryOp(a, BinaryOpType::Div, b))} + a:(@) _ binop(<"%">) _ b:@ {loc_expr_todo!(Expr::Apply( el!(Expr::Intrinsic("mod".into())), ArgsDesc(vec![Arg(None, a), Arg(None, b)]), false ))} -- - "-" _ b:@ {loc_expr_todo!(Expr::UnaryOp(UnaryOpType::Minus, b))} - "!" _ b:@ {loc_expr_todo!(Expr::UnaryOp(UnaryOpType::Not, b))} - "~" _ b:@ { loc_expr_todo!(Expr::UnaryOp(UnaryOpType::BitNot, b)) } + unaryop(<"-">) _ b:@ {loc_expr_todo!(Expr::UnaryOp(UnaryOpType::Minus, b))} + unaryop(<"!">) _ b:@ {loc_expr_todo!(Expr::UnaryOp(UnaryOpType::Not, b))} + unaryop(<"~">) _ b:@ { loc_expr_todo!(Expr::UnaryOp(UnaryOpType::BitNot, b)) } -- a:(@) _ "[" _ s:slice_desc(s) _ "]" {loc_expr_todo!(Expr::Apply( el!(Expr::Intrinsic("slice".into())),