git.delta.rocks / jrsonnet / refs/commits / 3b72cd84a927

difftreelog

Merge pull request #27 from CertainLach/syntax-error-display

Yaroslav Bulyukin2020-11-17parents: #036eddf #1ca5234.patch.diff
in: master
Syntax error display

8 files changed

modifiedCargo.lockdiffbeforeafterboth
--- 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",
+]
modifiedcrates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth
--- 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]
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- 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<PathBuf>,
 		source_code: Rc<str>,
modifiedcrates/jrsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/evaluate.rs
1use crate::{2	context_creator, error::Error::*, future_wrapper, lazy_val, push, throw, with_state, Context,3	ContextCreator, FuncDesc, FuncVal, LazyBinding, LazyVal, ObjMember, ObjValue, Result, Val,4	ValType,5};6use closure::closure;7use jrsonnet_parser::{8	ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, ExprLocation, FieldMember,9	ForSpecData, IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc, UnaryOpType,10	Visibility,11};12use rustc_hash::FxHashMap;13use std::{collections::HashMap, rc::Rc};1415pub fn evaluate_binding(b: &BindSpec, context_creator: ContextCreator) -> (Rc<str>, LazyBinding) {16	let b = b.clone();17	if let Some(params) = &b.params {18		let params = params.clone();19		(20			b.name.clone(),21			LazyBinding::Bindable(Rc::new(move |this, super_obj| {22				Ok(lazy_val!(23					closure!(clone b, clone params, clone context_creator, || Ok(evaluate_method(24						context_creator.0(this.clone(), super_obj.clone())?,25						b.name.clone(),26						params.clone(),27						b.value.clone(),28					)))29				))30			})),31		)32	} else {33		(34			b.name.clone(),35			LazyBinding::Bindable(Rc::new(move |this, super_obj| {36				Ok(lazy_val!(closure!(clone context_creator, clone b, ||37						evaluate_named(38							context_creator.0(this.clone(), super_obj.clone())?,39							&b.value,40							b.name.clone()41						)42				)))43			})),44		)45	}46}4748pub fn evaluate_method(ctx: Context, name: Rc<str>, params: ParamsDesc, body: LocExpr) -> Val {49	Val::Func(Rc::new(FuncVal::Normal(FuncDesc {50		name,51		ctx,52		params,53		body,54	})))55}5657pub fn evaluate_field_name(58	context: Context,59	field_name: &jrsonnet_parser::FieldName,60) -> Result<Option<Rc<str>>> {61	Ok(match field_name {62		jrsonnet_parser::FieldName::Fixed(n) => Some(n.clone()),63		jrsonnet_parser::FieldName::Dyn(expr) => {64			let lazy = evaluate(context, expr)?;65			let value = lazy.unwrap_if_lazy()?;66			if matches!(value, Val::Null) {67				None68			} else {69				Some(value.try_cast_str("dynamic field name")?)70			}71		}72	})73}7475pub fn evaluate_unary_op(op: UnaryOpType, b: &Val) -> Result<Val> {76	Ok(match (op, b) {77		(o, Val::Lazy(l)) => evaluate_unary_op(o, &l.evaluate()?)?,78		(UnaryOpType::Not, Val::Bool(v)) => Val::Bool(!v),79		(UnaryOpType::Minus, Val::Num(n)) => Val::Num(-*n),80		(UnaryOpType::BitNot, Val::Num(n)) => Val::Num(!(*n as i32) as f64),81		(op, o) => throw!(UnaryOperatorDoesNotOperateOnType(op, o.value_type()?)),82	})83}8485pub fn evaluate_add_op(a: &Val, b: &Val) -> Result<Val> {86	Ok(match (a, b) {87		(Val::Str(v1), Val::Str(v2)) => Val::Str(((**v1).to_owned() + v2).into()),8889		// Can't use generic json serialization way, because it depends on number to string concatenation (std.jsonnet:890)90		(Val::Num(n), Val::Str(o)) => Val::Str(format!("{}{}", n, o).into()),91		(Val::Str(o), Val::Num(n)) => Val::Str(format!("{}{}", o, n).into()),9293		(Val::Str(s), o) => Val::Str(format!("{}{}", s, o.clone().to_string()?).into()),94		(o, Val::Str(s)) => Val::Str(format!("{}{}", o.clone().to_string()?, s).into()),9596		(Val::Obj(v1), Val::Obj(v2)) => Val::Obj(v2.with_super(v1.clone())),97		(Val::Arr(a), Val::Arr(b)) => Val::Arr(Rc::new([&a[..], &b[..]].concat())),98		(Val::Num(v1), Val::Num(v2)) => Val::new_checked_num(v1 + v2)?,99		_ => throw!(BinaryOperatorDoesNotOperateOnValues(100			BinaryOpType::Add,101			a.value_type()?,102			b.value_type()?,103		)),104	})105}106107pub fn evaluate_binary_op_special(108	context: Context,109	a: &LocExpr,110	op: BinaryOpType,111	b: &LocExpr,112) -> Result<Val> {113	Ok(114		match (evaluate(context.clone(), a)?.unwrap_if_lazy()?, op, b) {115			(Val::Bool(true), BinaryOpType::Or, _o) => Val::Bool(true),116			(Val::Bool(false), BinaryOpType::And, _o) => Val::Bool(false),117			(a, op, eb) => {118				evaluate_binary_op_normal(&a, op, &evaluate(context, eb)?.unwrap_if_lazy()?)?119			}120		},121	)122}123124pub fn evaluate_binary_op_normal(a: &Val, op: BinaryOpType, b: &Val) -> Result<Val> {125	Ok(match (a, op, b) {126		(a, BinaryOpType::Add, b) => evaluate_add_op(a, b)?,127128		(Val::Str(v1), BinaryOpType::Mul, Val::Num(v2)) => Val::Str(v1.repeat(*v2 as usize).into()),129130		// Bool X Bool131		(Val::Bool(a), BinaryOpType::And, Val::Bool(b)) => Val::Bool(*a && *b),132		(Val::Bool(a), BinaryOpType::Or, Val::Bool(b)) => Val::Bool(*a || *b),133134		// Str X Str135		(Val::Str(v1), BinaryOpType::Lt, Val::Str(v2)) => Val::Bool(v1 < v2),136		(Val::Str(v1), BinaryOpType::Gt, Val::Str(v2)) => Val::Bool(v1 > v2),137		(Val::Str(v1), BinaryOpType::Lte, Val::Str(v2)) => Val::Bool(v1 <= v2),138		(Val::Str(v1), BinaryOpType::Gte, Val::Str(v2)) => Val::Bool(v1 >= v2),139140		// Num X Num141		(Val::Num(v1), BinaryOpType::Mul, Val::Num(v2)) => Val::new_checked_num(v1 * v2)?,142		(Val::Num(v1), BinaryOpType::Div, Val::Num(v2)) => {143			if *v2 <= f64::EPSILON {144				throw!(DivisionByZero)145			}146			Val::new_checked_num(v1 / v2)?147		}148149		(Val::Num(v1), BinaryOpType::Sub, Val::Num(v2)) => Val::new_checked_num(v1 - v2)?,150151		(Val::Num(v1), BinaryOpType::Lt, Val::Num(v2)) => Val::Bool(v1 < v2),152		(Val::Num(v1), BinaryOpType::Gt, Val::Num(v2)) => Val::Bool(v1 > v2),153		(Val::Num(v1), BinaryOpType::Lte, Val::Num(v2)) => Val::Bool(v1 <= v2),154		(Val::Num(v1), BinaryOpType::Gte, Val::Num(v2)) => Val::Bool(v1 >= v2),155156		(Val::Num(v1), BinaryOpType::BitAnd, Val::Num(v2)) => {157			Val::Num(((*v1 as i32) & (*v2 as i32)) as f64)158		}159		(Val::Num(v1), BinaryOpType::BitOr, Val::Num(v2)) => {160			Val::Num(((*v1 as i32) | (*v2 as i32)) as f64)161		}162		(Val::Num(v1), BinaryOpType::BitXor, Val::Num(v2)) => {163			Val::Num(((*v1 as i32) ^ (*v2 as i32)) as f64)164		}165		(Val::Num(v1), BinaryOpType::Lhs, Val::Num(v2)) => {166			if *v2 < 0.0 {167				throw!(RuntimeError("shift by negative exponent".into()))168			}169			Val::Num(((*v1 as i32) << (*v2 as i32)) as f64)170		}171		(Val::Num(v1), BinaryOpType::Rhs, Val::Num(v2)) => {172			if *v2 < 0.0 {173				throw!(RuntimeError("shift by negative exponent".into()))174			}175			Val::Num(((*v1 as i32) >> (*v2 as i32)) as f64)176		}177178		_ => throw!(BinaryOperatorDoesNotOperateOnValues(179			op,180			a.value_type()?,181			b.value_type()?,182		)),183	})184}185186future_wrapper!(HashMap<Rc<str>, LazyBinding>, FutureNewBindings);187future_wrapper!(ObjValue, FutureObjValue);188189pub fn evaluate_comp<T>(190	context: Context,191	value: &impl Fn(Context) -> Result<T>,192	specs: &[CompSpec],193) -> Result<Option<Vec<T>>> {194	Ok(match specs.get(0) {195		None => Some(vec![value(context)?]),196		Some(CompSpec::IfSpec(IfSpecData(cond))) => {197			if evaluate(context.clone(), cond)?.try_cast_bool("if spec")? {198				evaluate_comp(context, value, &specs[1..])?199			} else {200				None201			}202		}203		Some(CompSpec::ForSpec(ForSpecData(var, expr))) => {204			match evaluate(context.clone(), expr)?.unwrap_if_lazy()? {205				Val::Arr(list) => {206					let mut out = Vec::new();207					for item in list.iter() {208						let item = item.unwrap_if_lazy()?;209						out.push(evaluate_comp(210							context.clone().with_var(var.clone(), item.clone()),211							value,212							&specs[1..],213						)?);214					}215					Some(out.into_iter().flatten().flatten().collect())216				}217				_ => throw!(InComprehensionCanOnlyIterateOverArray),218			}219		}220	})221}222223pub fn evaluate_member_list_object(context: Context, members: &[Member]) -> Result<ObjValue> {224	let new_bindings = FutureNewBindings::new();225	let future_this = FutureObjValue::new();226	let context_creator = context_creator!(227		closure!(clone context, clone new_bindings, |this: Option<ObjValue>, super_obj: Option<ObjValue>| {228			Ok(context.clone().extend_unbound(229				new_bindings.clone().unwrap(),230				context.dollar().clone().or_else(||this.clone()),231				Some(this.unwrap()),232				super_obj233			)?)234		})235	);236	{237		let mut bindings: HashMap<Rc<str>, LazyBinding> = HashMap::new();238		for (n, b) in members239			.iter()240			.filter_map(|m| match m {241				Member::BindStmt(b) => Some(b.clone()),242				_ => None,243			})244			.map(|b| evaluate_binding(&b, context_creator.clone()))245		{246			bindings.insert(n, b);247		}248		new_bindings.fill(bindings);249	}250251	let mut new_members = HashMap::new();252	for member in members.iter() {253		match member {254			Member::Field(FieldMember {255				name,256				plus,257				params: None,258				visibility,259				value,260			}) => {261				let name = evaluate_field_name(context.clone(), name)?;262				if name.is_none() {263					continue;264				}265				let name = name.unwrap();266				new_members.insert(267					name.clone(),268					ObjMember {269						add: *plus,270						visibility: *visibility,271						invoke: LazyBinding::Bindable(Rc::new(272							closure!(clone name, clone value, clone context_creator, |this, super_obj| {273								Ok(LazyVal::new_resolved(evaluate(274									context_creator.0(this, super_obj)?,275									&value,276								)?))277							}),278						)),279						location: value.1.clone(),280					},281				);282			}283			Member::Field(FieldMember {284				name,285				params: Some(params),286				value,287				..288			}) => {289				let name = evaluate_field_name(context.clone(), name)?;290				if name.is_none() {291					continue;292				}293				let name = name.unwrap();294				new_members.insert(295					name.clone(),296					ObjMember {297						add: false,298						visibility: Visibility::Hidden,299						invoke: LazyBinding::Bindable(Rc::new(300							closure!(clone value, clone context_creator, clone params, clone name, |this, super_obj| {301								// TODO: Assert302								Ok(LazyVal::new_resolved(evaluate_method(303									context_creator.0(this, super_obj)?,304									name.clone(),305									params.clone(),306									value.clone(),307								)))308							}),309						)),310						location: value.1.clone(),311					},312				);313			}314			Member::BindStmt(_) => {}315			Member::AssertStmt(_) => {}316		}317	}318	Ok(future_this.fill(ObjValue::new(None, Rc::new(new_members))))319}320321pub fn evaluate_object(context: Context, object: &ObjBody) -> Result<ObjValue> {322	Ok(match object {323		ObjBody::MemberList(members) => evaluate_member_list_object(context, members)?,324		ObjBody::ObjComp(obj) => {325			let future_this = FutureObjValue::new();326			let mut new_members = HashMap::new();327			for (k, v) in evaluate_comp(328				context.clone(),329				&|ctx| {330					let new_bindings = FutureNewBindings::new();331					let context_creator = context_creator!(332						closure!(clone context, clone new_bindings, |this: Option<ObjValue>, super_obj: Option<ObjValue>| {333							Ok(context.clone().extend_unbound(334								new_bindings.clone().unwrap(),335								context.dollar().clone().or_else(||this.clone()),336								None,337								super_obj338							)?)339						})340					);341					let mut bindings: HashMap<Rc<str>, LazyBinding> = HashMap::new();342					for (n, b) in obj343						.pre_locals344						.iter()345						.chain(obj.post_locals.iter())346						.map(|b| evaluate_binding(b, context_creator.clone()))347					{348						bindings.insert(n, b);349					}350					let bindings = new_bindings.fill(bindings);351					let ctx = ctx.extend_unbound(bindings, None, None, None)?;352					let key = evaluate(ctx.clone(), &obj.key)?;353					let value = LazyBinding::Bindable(Rc::new(354						closure!(clone ctx, clone obj.value, |this, _super_obj| {355							Ok(LazyVal::new_resolved(evaluate(ctx.clone().extend(FxHashMap::default(), None, this, None), &value)?))356						}),357					));358359					Ok((key, value))360				},361				&obj.compspecs,362			)?363			.unwrap()364			{365				match k {366					Val::Null => {}367					Val::Str(n) => {368						new_members.insert(369							n,370							ObjMember {371								add: false,372								visibility: Visibility::Normal,373								invoke: v,374								location: obj.value.1.clone(),375							},376						);377					}378					v => throw!(FieldMustBeStringGot(v.value_type()?)),379				}380			}381382			future_this.fill(ObjValue::new(None, Rc::new(new_members)))383		}384	})385}386387pub fn evaluate_apply(388	context: Context,389	value: &LocExpr,390	args: &ArgsDesc,391	loc: &Option<ExprLocation>,392	tailstrict: bool,393) -> Result<Val> {394	let lazy = evaluate(context.clone(), value)?;395	let value = lazy.unwrap_if_lazy()?;396	Ok(match value {397		Val::Func(f) => {398			let body = || f.evaluate(context, loc, args, tailstrict);399			if tailstrict {400				body()?401			} else {402				push(loc, || format!("function <{}> call", f.name()), body)?403			}404		}405		v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type()?)),406	})407}408409pub fn evaluate_named(context: Context, lexpr: &LocExpr, name: Rc<str>) -> Result<Val> {410	use Expr::*;411	let LocExpr(expr, _loc) = lexpr;412	Ok(match &**expr {413		Function(params, body) => evaluate_method(context, name, params.clone(), body.clone()),414		_ => evaluate(context, lexpr)?,415	})416}417418pub fn evaluate(context: Context, expr: &LocExpr) -> Result<Val> {419	use Expr::*;420	let LocExpr(expr, loc) = expr;421	Ok(match &**expr {422		Literal(LiteralType::This) => Val::Obj(423			context424				.this()425				.clone()426				.ok_or_else(|| CantUseSelfOutsideOfObject)?,427		),428		Literal(LiteralType::Dollar) => Val::Obj(429			context430				.dollar()431				.clone()432				.ok_or_else(|| NoTopLevelObjectFound)?,433		),434		Literal(LiteralType::True) => Val::Bool(true),435		Literal(LiteralType::False) => Val::Bool(false),436		Literal(LiteralType::Null) => Val::Null,437		Parened(e) => evaluate(context, e)?,438		Str(v) => Val::Str(v.clone()),439		Num(v) => Val::new_checked_num(*v)?,440		BinaryOp(v1, o, v2) => evaluate_binary_op_special(context, v1, *o, v2)?,441		UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(context, v)?)?,442		Var(name) => push(443			loc,444			|| format!("variable <{}>", name),445			|| Ok(Val::Lazy(context.binding(name.clone())?).unwrap_if_lazy()?),446		)?,447		Index(LocExpr(v, _), index) if matches!(&**v, Expr::Literal(LiteralType::Super)) => {448			let name = evaluate(context.clone(), index)?.try_cast_str("object index")?;449			context450				.super_obj()451				.clone()452				.expect("no super found")453				.get_raw(name, &context.this().clone().expect("no this found"))?454				.expect("value not found")455		}456		Index(value, index) => {457			match (458				evaluate(context.clone(), value)?.unwrap_if_lazy()?,459				evaluate(context, index)?,460			) {461				(Val::Obj(v), Val::Str(s)) => {462					let sn = s.clone();463					push(464						loc,465						|| format!("field <{}> access", sn),466						|| {467							if let Some(v) = v.get(s.clone())? {468								Ok(v.unwrap_if_lazy()?)469							} else if v.get("__intrinsic_namespace__".into())?.is_some() {470								Ok(Val::Func(Rc::new(FuncVal::Intrinsic(s))))471							} else {472								throw!(NoSuchField(s))473							}474						},475					)?476				}477				(Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(478					ValType::Obj,479					ValType::Str,480					n.value_type()?,481				)),482483				(Val::Arr(v), Val::Num(n)) => {484					if n.fract() > f64::EPSILON {485						throw!(FractionalIndex)486					}487					v.get(n as usize)488						.ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?489						.clone()490						.unwrap_if_lazy()?491				}492				(Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),493				(Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(494					ValType::Arr,495					ValType::Num,496					n.value_type()?,497				)),498499				(Val::Str(s), Val::Num(n)) => Val::Str(500					s.chars()501						.skip(n as usize)502						.take(1)503						.collect::<String>()504						.into(),505				),506				(Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(507					ValType::Str,508					ValType::Num,509					n.value_type()?,510				)),511512				(v, _) => throw!(CantIndexInto(v.value_type()?)),513			}514		}515		LocalExpr(bindings, returned) => {516			let mut new_bindings: HashMap<Rc<str>, LazyBinding> = HashMap::new();517			let future_context = Context::new_future();518519			let context_creator = context_creator!(520				closure!(clone future_context, |_, _| Ok(future_context.clone().unwrap()))521			);522523			for (k, v) in bindings524				.iter()525				.map(|b| evaluate_binding(b, context_creator.clone()))526			{527				new_bindings.insert(k, v);528			}529530			let context = context531				.extend_unbound(new_bindings, None, None, None)?532				.into_future(future_context);533			evaluate(context, &returned.clone())?534		}535		Arr(items) => {536			let mut out = Vec::with_capacity(items.len());537			for item in items {538				out.push(Val::Lazy(lazy_val!(539					closure!(clone context, clone item, || {540						evaluate(context.clone(), &item)541					})542				)));543			}544			Val::Arr(Rc::new(out))545		}546		ArrComp(expr, comp_specs) => Val::Arr(547			// First comp_spec should be for_spec, so no "None" possible here548			Rc::new(evaluate_comp(context, &|ctx| evaluate(ctx, expr), comp_specs)?.unwrap()),549		),550		Obj(body) => Val::Obj(evaluate_object(context, body)?),551		ObjExtend(s, t) => evaluate_add_op(552			&evaluate(context.clone(), s)?,553			&Val::Obj(evaluate_object(context, t)?),554		)?,555		Apply(value, args, tailstrict) => evaluate_apply(context, value, args, loc, *tailstrict)?,556		Function(params, body) => {557			evaluate_method(context, "anonymous".into(), params.clone(), body.clone())558		}559		Intrinsic(name) => Val::Func(Rc::new(FuncVal::Intrinsic(name.clone()))),560		AssertExpr(AssertStmt(value, msg), returned) => {561			let assertion_result = push(562				&value.1,563				|| "assertion condition".to_owned(),564				|| {565					evaluate(context.clone(), value)?566						.try_cast_bool("assertion condition should be of type `boolean`")567				},568			)?;569			if assertion_result {570				evaluate(context, returned)?571			} else if let Some(msg) = msg {572				throw!(AssertionFailed(evaluate(context, msg)?.to_string()?));573			} else {574				throw!(AssertionFailed(Val::Null.to_string()?));575			}576		}577		ErrorStmt(e) => push(578			loc,579			|| "error statement".to_owned(),580			|| {581				throw!(RuntimeError(582					evaluate(context, e)?.try_cast_str("error text should be of type `string`")?,583				))584			},585		)?,586		IfElse {587			cond,588			cond_then,589			cond_else,590		} => {591			if push(592				loc,593				|| "if condition".to_owned(),594				|| evaluate(context.clone(), &cond.0)?.try_cast_bool("in if condition"),595			)? {596				evaluate(context, cond_then)?597			} else {598				match cond_else {599					Some(v) => evaluate(context, v)?,600					None => Val::Null,601				}602			}603		}604		Import(path) => {605			let mut tmp = loc606				.clone()607				.expect("imports cannot be used without loc_data")608				.0;609			let import_location = Rc::make_mut(&mut tmp);610			import_location.pop();611			push(612				loc,613				|| format!("import {:?}", path),614				|| with_state(|s| s.import_file(import_location, path)),615			)?616		}617		ImportStr(path) => {618			let mut tmp = loc619				.clone()620				.expect("imports cannot be used without loc_data")621				.0;622			let import_location = Rc::make_mut(&mut tmp);623			import_location.pop();624			Val::Str(with_state(|s| s.import_file_str(import_location, path))?)625		}626		Literal(LiteralType::Super) => throw!(StandaloneSuper),627	})628}
after · crates/jrsonnet-evaluator/src/evaluate.rs
1use crate::{2	context_creator, error::Error::*, future_wrapper, lazy_val, push, throw, with_state, Context,3	ContextCreator, FuncDesc, FuncVal, LazyBinding, LazyVal, ObjMember, ObjValue, Result, Val,4	ValType,5};6use closure::closure;7use jrsonnet_parser::{8	ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, ExprLocation, FieldMember,9	ForSpecData, IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc, UnaryOpType,10	Visibility,11};12use rustc_hash::FxHashMap;13use std::{collections::HashMap, rc::Rc};1415pub fn evaluate_binding(b: &BindSpec, context_creator: ContextCreator) -> (Rc<str>, LazyBinding) {16	let b = b.clone();17	if let Some(params) = &b.params {18		let params = params.clone();19		(20			b.name.clone(),21			LazyBinding::Bindable(Rc::new(move |this, super_obj| {22				Ok(lazy_val!(23					closure!(clone b, clone params, clone context_creator, || Ok(evaluate_method(24						context_creator.0(this.clone(), super_obj.clone())?,25						b.name.clone(),26						params.clone(),27						b.value.clone(),28					)))29				))30			})),31		)32	} else {33		(34			b.name.clone(),35			LazyBinding::Bindable(Rc::new(move |this, super_obj| {36				Ok(lazy_val!(closure!(clone context_creator, clone b, ||37						evaluate_named(38							context_creator.0(this.clone(), super_obj.clone())?,39							&b.value,40							b.name.clone()41						)42				)))43			})),44		)45	}46}4748pub fn evaluate_method(ctx: Context, name: Rc<str>, params: ParamsDesc, body: LocExpr) -> Val {49	Val::Func(Rc::new(FuncVal::Normal(FuncDesc {50		name,51		ctx,52		params,53		body,54	})))55}5657pub fn evaluate_field_name(58	context: Context,59	field_name: &jrsonnet_parser::FieldName,60) -> Result<Option<Rc<str>>> {61	Ok(match field_name {62		jrsonnet_parser::FieldName::Fixed(n) => Some(n.clone()),63		jrsonnet_parser::FieldName::Dyn(expr) => {64			let lazy = evaluate(context, expr)?;65			let value = lazy.unwrap_if_lazy()?;66			if matches!(value, Val::Null) {67				None68			} else {69				Some(value.try_cast_str("dynamic field name")?)70			}71		}72	})73}7475pub fn evaluate_unary_op(op: UnaryOpType, b: &Val) -> Result<Val> {76	Ok(match (op, b) {77		(o, Val::Lazy(l)) => evaluate_unary_op(o, &l.evaluate()?)?,78		(UnaryOpType::Not, Val::Bool(v)) => Val::Bool(!v),79		(UnaryOpType::Minus, Val::Num(n)) => Val::Num(-*n),80		(UnaryOpType::BitNot, Val::Num(n)) => Val::Num(!(*n as i32) as f64),81		(op, o) => throw!(UnaryOperatorDoesNotOperateOnType(op, o.value_type()?)),82	})83}8485pub fn evaluate_add_op(a: &Val, b: &Val) -> Result<Val> {86	Ok(match (a, b) {87		(Val::Str(v1), Val::Str(v2)) => Val::Str(((**v1).to_owned() + v2).into()),8889		// Can't use generic json serialization way, because it depends on number to string concatenation (std.jsonnet:890)90		(Val::Num(n), Val::Str(o)) => Val::Str(format!("{}{}", n, o).into()),91		(Val::Str(o), Val::Num(n)) => Val::Str(format!("{}{}", o, n).into()),9293		(Val::Str(s), o) => Val::Str(format!("{}{}", s, o.clone().to_string()?).into()),94		(o, Val::Str(s)) => Val::Str(format!("{}{}", o.clone().to_string()?, s).into()),9596		(Val::Obj(v1), Val::Obj(v2)) => Val::Obj(v2.with_super(v1.clone())),97		(Val::Arr(a), Val::Arr(b)) => Val::Arr(Rc::new([&a[..], &b[..]].concat())),98		(Val::Num(v1), Val::Num(v2)) => Val::new_checked_num(v1 + v2)?,99		_ => throw!(BinaryOperatorDoesNotOperateOnValues(100			BinaryOpType::Add,101			a.value_type()?,102			b.value_type()?,103		)),104	})105}106107pub fn evaluate_binary_op_special(108	context: Context,109	a: &LocExpr,110	op: BinaryOpType,111	b: &LocExpr,112) -> Result<Val> {113	Ok(114		match (evaluate(context.clone(), a)?.unwrap_if_lazy()?, op, b) {115			(Val::Bool(true), BinaryOpType::Or, _o) => Val::Bool(true),116			(Val::Bool(false), BinaryOpType::And, _o) => Val::Bool(false),117			(a, op, eb) => {118				evaluate_binary_op_normal(&a, op, &evaluate(context, eb)?.unwrap_if_lazy()?)?119			}120		},121	)122}123124pub fn evaluate_binary_op_normal(a: &Val, op: BinaryOpType, b: &Val) -> Result<Val> {125	Ok(match (a, op, b) {126		(a, BinaryOpType::Add, b) => evaluate_add_op(a, b)?,127128		(Val::Str(v1), BinaryOpType::Mul, Val::Num(v2)) => Val::Str(v1.repeat(*v2 as usize).into()),129130		// Bool X Bool131		(Val::Bool(a), BinaryOpType::And, Val::Bool(b)) => Val::Bool(*a && *b),132		(Val::Bool(a), BinaryOpType::Or, Val::Bool(b)) => Val::Bool(*a || *b),133134		// Str X Str135		(Val::Str(v1), BinaryOpType::Lt, Val::Str(v2)) => Val::Bool(v1 < v2),136		(Val::Str(v1), BinaryOpType::Gt, Val::Str(v2)) => Val::Bool(v1 > v2),137		(Val::Str(v1), BinaryOpType::Lte, Val::Str(v2)) => Val::Bool(v1 <= v2),138		(Val::Str(v1), BinaryOpType::Gte, Val::Str(v2)) => Val::Bool(v1 >= v2),139140		// Num X Num141		(Val::Num(v1), BinaryOpType::Mul, Val::Num(v2)) => Val::new_checked_num(v1 * v2)?,142		(Val::Num(v1), BinaryOpType::Div, Val::Num(v2)) => {143			if *v2 <= f64::EPSILON {144				throw!(DivisionByZero)145			}146			Val::new_checked_num(v1 / v2)?147		}148149		(Val::Num(v1), BinaryOpType::Sub, Val::Num(v2)) => Val::new_checked_num(v1 - v2)?,150151		(Val::Num(v1), BinaryOpType::Lt, Val::Num(v2)) => Val::Bool(v1 < v2),152		(Val::Num(v1), BinaryOpType::Gt, Val::Num(v2)) => Val::Bool(v1 > v2),153		(Val::Num(v1), BinaryOpType::Lte, Val::Num(v2)) => Val::Bool(v1 <= v2),154		(Val::Num(v1), BinaryOpType::Gte, Val::Num(v2)) => Val::Bool(v1 >= v2),155156		(Val::Num(v1), BinaryOpType::BitAnd, Val::Num(v2)) => {157			Val::Num(((*v1 as i32) & (*v2 as i32)) as f64)158		}159		(Val::Num(v1), BinaryOpType::BitOr, Val::Num(v2)) => {160			Val::Num(((*v1 as i32) | (*v2 as i32)) as f64)161		}162		(Val::Num(v1), BinaryOpType::BitXor, Val::Num(v2)) => {163			Val::Num(((*v1 as i32) ^ (*v2 as i32)) as f64)164		}165		(Val::Num(v1), BinaryOpType::Lhs, Val::Num(v2)) => {166			if *v2 < 0.0 {167				throw!(RuntimeError("shift by negative exponent".into()))168			}169			Val::Num(((*v1 as i32) << (*v2 as i32)) as f64)170		}171		(Val::Num(v1), BinaryOpType::Rhs, Val::Num(v2)) => {172			if *v2 < 0.0 {173				throw!(RuntimeError("shift by negative exponent".into()))174			}175			Val::Num(((*v1 as i32) >> (*v2 as i32)) as f64)176		}177178		_ => throw!(BinaryOperatorDoesNotOperateOnValues(179			op,180			a.value_type()?,181			b.value_type()?,182		)),183	})184}185186future_wrapper!(HashMap<Rc<str>, LazyBinding>, FutureNewBindings);187future_wrapper!(ObjValue, FutureObjValue);188189pub fn evaluate_comp<T>(190	context: Context,191	value: &impl Fn(Context) -> Result<T>,192	specs: &[CompSpec],193) -> Result<Option<Vec<T>>> {194	Ok(match specs.get(0) {195		None => Some(vec![value(context)?]),196		Some(CompSpec::IfSpec(IfSpecData(cond))) => {197			if evaluate(context.clone(), cond)?.try_cast_bool("if spec")? {198				evaluate_comp(context, value, &specs[1..])?199			} else {200				None201			}202		}203		Some(CompSpec::ForSpec(ForSpecData(var, expr))) => {204			match evaluate(context.clone(), expr)?.unwrap_if_lazy()? {205				Val::Arr(list) => {206					let mut out = Vec::new();207					for item in list.iter() {208						let item = item.unwrap_if_lazy()?;209						out.push(evaluate_comp(210							context.clone().with_var(var.clone(), item.clone()),211							value,212							&specs[1..],213						)?);214					}215					Some(out.into_iter().flatten().flatten().collect())216				}217				_ => throw!(InComprehensionCanOnlyIterateOverArray),218			}219		}220	})221}222223pub fn evaluate_member_list_object(context: Context, members: &[Member]) -> Result<ObjValue> {224	let new_bindings = FutureNewBindings::new();225	let future_this = FutureObjValue::new();226	let context_creator = context_creator!(227		closure!(clone context, clone new_bindings, |this: Option<ObjValue>, super_obj: Option<ObjValue>| {228			Ok(context.clone().extend_unbound(229				new_bindings.clone().unwrap(),230				context.dollar().clone().or_else(||this.clone()),231				Some(this.unwrap()),232				super_obj233			)?)234		})235	);236	{237		let mut bindings: HashMap<Rc<str>, LazyBinding> = HashMap::new();238		for (n, b) in members239			.iter()240			.filter_map(|m| match m {241				Member::BindStmt(b) => Some(b.clone()),242				_ => None,243			})244			.map(|b| evaluate_binding(&b, context_creator.clone()))245		{246			bindings.insert(n, b);247		}248		new_bindings.fill(bindings);249	}250251	let mut new_members = HashMap::new();252	for member in members.iter() {253		match member {254			Member::Field(FieldMember {255				name,256				plus,257				params: None,258				visibility,259				value,260			}) => {261				let name = evaluate_field_name(context.clone(), name)?;262				if name.is_none() {263					continue;264				}265				let name = name.unwrap();266				new_members.insert(267					name.clone(),268					ObjMember {269						add: *plus,270						visibility: *visibility,271						invoke: LazyBinding::Bindable(Rc::new(272							closure!(clone name, clone value, clone context_creator, |this, super_obj| {273								Ok(LazyVal::new_resolved(evaluate(274									context_creator.0(this, super_obj)?,275									&value,276								)?))277							}),278						)),279						location: value.1.clone(),280					},281				);282			}283			Member::Field(FieldMember {284				name,285				params: Some(params),286				value,287				..288			}) => {289				let name = evaluate_field_name(context.clone(), name)?;290				if name.is_none() {291					continue;292				}293				let name = name.unwrap();294				new_members.insert(295					name.clone(),296					ObjMember {297						add: false,298						visibility: Visibility::Hidden,299						invoke: LazyBinding::Bindable(Rc::new(300							closure!(clone value, clone context_creator, clone params, clone name, |this, super_obj| {301								// TODO: Assert302								Ok(LazyVal::new_resolved(evaluate_method(303									context_creator.0(this, super_obj)?,304									name.clone(),305									params.clone(),306									value.clone(),307								)))308							}),309						)),310						location: value.1.clone(),311					},312				);313			}314			Member::BindStmt(_) => {}315			Member::AssertStmt(_) => {}316		}317	}318	Ok(future_this.fill(ObjValue::new(None, Rc::new(new_members))))319}320321pub fn evaluate_object(context: Context, object: &ObjBody) -> Result<ObjValue> {322	Ok(match object {323		ObjBody::MemberList(members) => evaluate_member_list_object(context, members)?,324		ObjBody::ObjComp(obj) => {325			let future_this = FutureObjValue::new();326			let mut new_members = HashMap::new();327			for (k, v) in evaluate_comp(328				context.clone(),329				&|ctx| {330					let new_bindings = FutureNewBindings::new();331					let context_creator = context_creator!(332						closure!(clone context, clone new_bindings, |this: Option<ObjValue>, super_obj: Option<ObjValue>| {333							Ok(context.clone().extend_unbound(334								new_bindings.clone().unwrap(),335								context.dollar().clone().or_else(||this.clone()),336								None,337								super_obj338							)?)339						})340					);341					let mut bindings: HashMap<Rc<str>, LazyBinding> = HashMap::new();342					for (n, b) in obj343						.pre_locals344						.iter()345						.chain(obj.post_locals.iter())346						.map(|b| evaluate_binding(b, context_creator.clone()))347					{348						bindings.insert(n, b);349					}350					let bindings = new_bindings.fill(bindings);351					let ctx = ctx.extend_unbound(bindings, None, None, None)?;352					let key = evaluate(ctx.clone(), &obj.key)?;353					let value = LazyBinding::Bindable(Rc::new(354						closure!(clone ctx, clone obj.value, |this, _super_obj| {355							Ok(LazyVal::new_resolved(evaluate(ctx.clone().extend(FxHashMap::default(), None, this, None), &value)?))356						}),357					));358359					Ok((key, value))360				},361				&obj.compspecs,362			)?363			.unwrap()364			{365				match k {366					Val::Null => {}367					Val::Str(n) => {368						new_members.insert(369							n,370							ObjMember {371								add: false,372								visibility: Visibility::Normal,373								invoke: v,374								location: obj.value.1.clone(),375							},376						);377					}378					v => throw!(FieldMustBeStringGot(v.value_type()?)),379				}380			}381382			future_this.fill(ObjValue::new(None, Rc::new(new_members)))383		}384	})385}386387pub fn evaluate_apply(388	context: Context,389	value: &LocExpr,390	args: &ArgsDesc,391	loc: &Option<ExprLocation>,392	tailstrict: bool,393) -> Result<Val> {394	let lazy = evaluate(context.clone(), value)?;395	let value = lazy.unwrap_if_lazy()?;396	Ok(match value {397		Val::Func(f) => {398			let body = || f.evaluate(context, loc, args, tailstrict);399			if tailstrict {400				body()?401			} else {402				push(loc, || format!("function <{}> call", f.name()), body)?403			}404		}405		v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type()?)),406	})407}408409pub fn evaluate_named(context: Context, lexpr: &LocExpr, name: Rc<str>) -> Result<Val> {410	use Expr::*;411	let LocExpr(expr, _loc) = lexpr;412	Ok(match &**expr {413		Function(params, body) => evaluate_method(context, name, params.clone(), body.clone()),414		_ => evaluate(context, lexpr)?,415	})416}417418pub fn evaluate(context: Context, expr: &LocExpr) -> Result<Val> {419	use Expr::*;420	let LocExpr(expr, loc) = expr;421	Ok(match &**expr {422		Literal(LiteralType::This) => {423			Val::Obj(context.this().clone().ok_or(CantUseSelfOutsideOfObject)?)424		}425		Literal(LiteralType::Dollar) => {426			Val::Obj(context.dollar().clone().ok_or(NoTopLevelObjectFound)?)427		}428		Literal(LiteralType::True) => Val::Bool(true),429		Literal(LiteralType::False) => Val::Bool(false),430		Literal(LiteralType::Null) => Val::Null,431		Parened(e) => evaluate(context, e)?,432		Str(v) => Val::Str(v.clone()),433		Num(v) => Val::new_checked_num(*v)?,434		BinaryOp(v1, o, v2) => evaluate_binary_op_special(context, v1, *o, v2)?,435		UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(context, v)?)?,436		Var(name) => push(437			loc,438			|| format!("variable <{}>", name),439			|| Ok(Val::Lazy(context.binding(name.clone())?).unwrap_if_lazy()?),440		)?,441		Index(LocExpr(v, _), index) if matches!(&**v, Expr::Literal(LiteralType::Super)) => {442			let name = evaluate(context.clone(), index)?.try_cast_str("object index")?;443			context444				.super_obj()445				.clone()446				.expect("no super found")447				.get_raw(name, &context.this().clone().expect("no this found"))?448				.expect("value not found")449		}450		Index(value, index) => {451			match (452				evaluate(context.clone(), value)?.unwrap_if_lazy()?,453				evaluate(context, index)?,454			) {455				(Val::Obj(v), Val::Str(s)) => {456					let sn = s.clone();457					push(458						loc,459						|| format!("field <{}> access", sn),460						|| {461							if let Some(v) = v.get(s.clone())? {462								Ok(v.unwrap_if_lazy()?)463							} else if v.get("__intrinsic_namespace__".into())?.is_some() {464								Ok(Val::Func(Rc::new(FuncVal::Intrinsic(s))))465							} else {466								throw!(NoSuchField(s))467							}468						},469					)?470				}471				(Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(472					ValType::Obj,473					ValType::Str,474					n.value_type()?,475				)),476477				(Val::Arr(v), Val::Num(n)) => {478					if n.fract() > f64::EPSILON {479						throw!(FractionalIndex)480					}481					v.get(n as usize)482						.ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?483						.clone()484						.unwrap_if_lazy()?485				}486				(Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),487				(Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(488					ValType::Arr,489					ValType::Num,490					n.value_type()?,491				)),492493				(Val::Str(s), Val::Num(n)) => Val::Str(494					s.chars()495						.skip(n as usize)496						.take(1)497						.collect::<String>()498						.into(),499				),500				(Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(501					ValType::Str,502					ValType::Num,503					n.value_type()?,504				)),505506				(v, _) => throw!(CantIndexInto(v.value_type()?)),507			}508		}509		LocalExpr(bindings, returned) => {510			let mut new_bindings: HashMap<Rc<str>, LazyBinding> = HashMap::new();511			let future_context = Context::new_future();512513			let context_creator = context_creator!(514				closure!(clone future_context, |_, _| Ok(future_context.clone().unwrap()))515			);516517			for (k, v) in bindings518				.iter()519				.map(|b| evaluate_binding(b, context_creator.clone()))520			{521				new_bindings.insert(k, v);522			}523524			let context = context525				.extend_unbound(new_bindings, None, None, None)?526				.into_future(future_context);527			evaluate(context, &returned.clone())?528		}529		Arr(items) => {530			let mut out = Vec::with_capacity(items.len());531			for item in items {532				out.push(Val::Lazy(lazy_val!(533					closure!(clone context, clone item, || {534						evaluate(context.clone(), &item)535					})536				)));537			}538			Val::Arr(Rc::new(out))539		}540		ArrComp(expr, comp_specs) => Val::Arr(541			// First comp_spec should be for_spec, so no "None" possible here542			Rc::new(evaluate_comp(context, &|ctx| evaluate(ctx, expr), comp_specs)?.unwrap()),543		),544		Obj(body) => Val::Obj(evaluate_object(context, body)?),545		ObjExtend(s, t) => evaluate_add_op(546			&evaluate(context.clone(), s)?,547			&Val::Obj(evaluate_object(context, t)?),548		)?,549		Apply(value, args, tailstrict) => evaluate_apply(context, value, args, loc, *tailstrict)?,550		Function(params, body) => {551			evaluate_method(context, "anonymous".into(), params.clone(), body.clone())552		}553		Intrinsic(name) => Val::Func(Rc::new(FuncVal::Intrinsic(name.clone()))),554		AssertExpr(AssertStmt(value, msg), returned) => {555			let assertion_result = push(556				&value.1,557				|| "assertion condition".to_owned(),558				|| {559					evaluate(context.clone(), value)?560						.try_cast_bool("assertion condition should be of type `boolean`")561				},562			)?;563			if assertion_result {564				evaluate(context, returned)?565			} else if let Some(msg) = msg {566				throw!(AssertionFailed(evaluate(context, msg)?.to_string()?));567			} else {568				throw!(AssertionFailed(Val::Null.to_string()?));569			}570		}571		ErrorStmt(e) => push(572			loc,573			|| "error statement".to_owned(),574			|| {575				throw!(RuntimeError(576					evaluate(context, e)?.try_cast_str("error text should be of type `string`")?,577				))578			},579		)?,580		IfElse {581			cond,582			cond_then,583			cond_else,584		} => {585			if push(586				loc,587				|| "if condition".to_owned(),588				|| evaluate(context.clone(), &cond.0)?.try_cast_bool("in if condition"),589			)? {590				evaluate(context, cond_then)?591			} else {592				match cond_else {593					Some(v) => evaluate(context, v)?,594					None => Val::Null,595				}596			}597		}598		Import(path) => {599			let mut tmp = loc600				.clone()601				.expect("imports cannot be used without loc_data")602				.0;603			let import_location = Rc::make_mut(&mut tmp);604			import_location.pop();605			push(606				loc,607				|| format!("import {:?}", path),608				|| with_state(|s| s.import_file(import_location, path)),609			)?610		}611		ImportStr(path) => {612			let mut tmp = loc613				.clone()614				.expect("imports cannot be used without loc_data")615				.0;616			let import_location = Rc::make_mut(&mut tmp);617			import_location.pop();618			Val::Str(with_state(|s| s.import_file_str(import_location, path))?)619		}620		Literal(LiteralType::Super) => throw!(StandaloneSuper),621	})622}
modifiedcrates/jrsonnet-evaluator/src/trace/location.rsdiffbeforeafterboth
--- 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,
modifiedcrates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth
--- 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, "{:<p$}{}", "", n, p = self.padding,)?;
+		}
 		let file_names = error
 			.trace()
 			.0
@@ -167,52 +194,101 @@
 		evaluation_state: &EvaluationState,
 		error: &LocError,
 	) -> 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(())
 	}
 }
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- 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("...");
 				}
modifiedcrates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth
--- 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!("<identifier>")
 
-		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<Expr>) -> 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!("<string>")
 
 		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!("<binary op>")
+		rule unaryop(x: rule<()>) -> ()
+			= quiet!{ x() } / expected!("<unary op>")
+
 		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(<keyword("in")>) _ 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())),