git.delta.rocks / jrsonnet / refs/commits / 44f6e2c9e550

difftreelog

refactor split peg parser and ir

kzvrllptYaroslav Bolyukin2026-03-22parent: #1ddb92c.patch.diff
in: master

109 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -609,7 +609,7 @@
  "jrsonnet-cli",
  "jrsonnet-evaluator",
  "jrsonnet-gcmodule",
- "jrsonnet-parser",
+ "jrsonnet-ir",
  "mimallocator",
  "serde",
  "serde_json",
@@ -623,7 +623,7 @@
  "clap",
  "jrsonnet-evaluator",
  "jrsonnet-gcmodule",
- "jrsonnet-parser",
+ "jrsonnet-ir",
  "jrsonnet-stdlib",
 ]
 
@@ -637,8 +637,9 @@
  "hi-doc",
  "jrsonnet-gcmodule",
  "jrsonnet-interner",
+ "jrsonnet-ir",
  "jrsonnet-macros",
- "jrsonnet-parser",
+ "jrsonnet-peg-parser",
  "jrsonnet-types",
  "num-bigint",
  "pathdiff",
@@ -704,6 +705,17 @@
 ]
 
 [[package]]
+name = "jrsonnet-ir"
+version = "0.5.0-pre97"
+dependencies = [
+ "insta",
+ "jrsonnet-gcmodule",
+ "jrsonnet-interner",
+ "peg",
+ "static_assertions",
+]
+
+[[package]]
 name = "jrsonnet-macros"
 version = "0.5.0-pre97"
 dependencies = [
@@ -714,14 +726,12 @@
 ]
 
 [[package]]
-name = "jrsonnet-parser"
+name = "jrsonnet-peg-parser"
 version = "0.5.0-pre97"
 dependencies = [
  "insta",
- "jrsonnet-gcmodule",
- "jrsonnet-interner",
+ "jrsonnet-ir",
  "peg",
- "static_assertions",
 ]
 
 [[package]]
@@ -746,8 +756,8 @@
  "base64",
  "jrsonnet-evaluator",
  "jrsonnet-gcmodule",
+ "jrsonnet-ir",
  "jrsonnet-macros",
- "jrsonnet-parser",
  "lru",
  "md5",
  "num-bigint",
@@ -812,7 +822,7 @@
  "jrsonnet-evaluator",
  "jrsonnet-gcmodule",
  "jrsonnet-interner",
- "jrsonnet-parser",
+ "jrsonnet-ir",
  "jrsonnet-stdlib",
 ]
 
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,8 @@
 [workspace.dependencies]
 jrsonnet-evaluator = { path = "./crates/jrsonnet-evaluator", version = "0.5.0-pre97" }
 jrsonnet-macros = { path = "./crates/jrsonnet-macros", version = "0.5.0-pre97" }
-jrsonnet-parser = { path = "./crates/jrsonnet-parser", version = "0.5.0-pre97" }
+jrsonnet-ir = { path = "./crates/jrsonnet-ir", version = "0.5.0-pre97" }
+jrsonnet-peg-parser = { path = "./crates/jrsonnet-peg-parser", version = "0.5.0-pre97" }
 jrsonnet-rowan-parser = { path = "./crates/jrsonnet-rowan-parser", version = "0.5.0-pre97" }
 jrsonnet-interner = { path = "./crates/jrsonnet-interner", version = "0.5.0-pre97" }
 jrsonnet-stdlib = { path = "./crates/jrsonnet-stdlib", version = "0.5.0-pre97" }
modifiedbindings/jsonnet/Cargo.tomldiffbeforeafterboth
--- a/bindings/jsonnet/Cargo.toml
+++ b/bindings/jsonnet/Cargo.toml
@@ -20,7 +20,7 @@
 
 [dependencies]
 jrsonnet-evaluator.workspace = true
-jrsonnet-parser.workspace = true
+jrsonnet-ir.workspace = true
 jrsonnet-stdlib.workspace = true
 jrsonnet-gcmodule.workspace = true
 jrsonnet-interner.workspace = true
modifiedbindings/jsonnet/src/import.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/import.rs
+++ b/bindings/jsonnet/src/import.rs
@@ -17,7 +17,7 @@
 	AsPathLike, ImportResolver, ResolvePath,
 };
 use jrsonnet_gcmodule::Acyclic;
-use jrsonnet_parser::{SourceDirectory, SourceFile, SourcePath};
+use jrsonnet_ir::{SourceDirectory, SourceFile, SourcePath};
 
 use crate::VM;
 
modifiedbindings/jsonnet/src/lib.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/lib.rs
+++ b/bindings/jsonnet/src/lib.rs
@@ -31,7 +31,7 @@
 	AsPathLike, FileImportResolver, IStr, ImportResolver, Result, State, Val,
 };
 use jrsonnet_gcmodule::Acyclic;
-use jrsonnet_parser::SourcePath;
+use jrsonnet_ir::SourcePath;
 use jrsonnet_stdlib::ContextInitializer;
 
 /// WASM stub
modifiedcmds/jrsonnet/Cargo.tomldiffbeforeafterboth
--- a/cmds/jrsonnet/Cargo.toml
+++ b/cmds/jrsonnet/Cargo.toml
@@ -37,7 +37,7 @@
 # obj?.field, obj?.['field']
 exp-null-coaelse = [
     "jrsonnet-evaluator/exp-null-coaelse",
-    "jrsonnet-parser/exp-null-coaelse",
+    "jrsonnet-ir/exp-null-coaelse",
     "jrsonnet-cli/exp-null-coaelse",
 ]
 # --exp-apply
@@ -45,7 +45,7 @@
 
 [dependencies]
 jrsonnet-evaluator.workspace = true
-jrsonnet-parser.workspace = true
+jrsonnet-ir.workspace = true
 jrsonnet-cli.workspace = true
 jrsonnet-gcmodule.workspace = true
 
modifiedcmds/jrsonnet/src/main.rsdiffbeforeafterboth
--- a/cmds/jrsonnet/src/main.rs
+++ b/cmds/jrsonnet/src/main.rs
@@ -11,7 +11,7 @@
 	error::{Error as JrError, ErrorKind},
 	ResultExt, State, Val,
 };
-use jrsonnet_parser::{SourceDefaultIgnoreJpath, SourcePath};
+use jrsonnet_ir::{SourceDefaultIgnoreJpath, SourcePath};
 
 #[cfg(feature = "mimalloc")]
 #[global_allocator]
modifiedcrates/jrsonnet-cli/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-cli/Cargo.toml
+++ b/crates/jrsonnet-cli/Cargo.toml
@@ -29,7 +29,7 @@
 
 [dependencies]
 jrsonnet-evaluator = { workspace = true, features = ["explaining-traces"] }
-jrsonnet-parser.workspace = true
+jrsonnet-ir.workspace = true
 jrsonnet-stdlib.workspace = true
 jrsonnet-gcmodule.workspace = true
 
modifiedcrates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -22,17 +22,18 @@
 # Allows to preserve field order in objects
 exp-preserve-order = []
 # Implements field destructuring
-exp-destruct = ["jrsonnet-parser/exp-destruct"]
+exp-destruct = ["jrsonnet-peg-parser/exp-destruct"]
 # Iteration over objects yields [key, value] elements
 exp-object-iteration = []
 # Bigint type
 exp-bigint = ["num-bigint", "jrsonnet-types/exp-bigint"]
 # obj?.field, obj?.['field']
-exp-null-coaelse = ["jrsonnet-parser/exp-null-coaelse"]
+exp-null-coaelse = ["jrsonnet-peg-parser/exp-null-coaelse"]
 
 [dependencies]
 jrsonnet-interner.workspace = true
-jrsonnet-parser.workspace = true
+jrsonnet-ir.workspace = true
+jrsonnet-peg-parser.workspace = true
 jrsonnet-types.workspace = true
 jrsonnet-macros.workspace = true
 jrsonnet-gcmodule.workspace = true
modifiedcrates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/arr/mod.rs
+++ b/crates/jrsonnet-evaluator/src/arr/mod.rs
@@ -7,7 +7,7 @@
 
 use jrsonnet_gcmodule::{cc_dyn, Cc};
 use jrsonnet_interner::IBytes;
-use jrsonnet_parser::{Expr, Spanned};
+use jrsonnet_ir::{Expr, Spanned};
 
 use crate::{function::NativeFn, Context, Result, Thunk, Val};
 
@@ -85,7 +85,7 @@
 
 	pub fn extended(a: Self, b: Self) -> Self {
 		// TODO: benchmark for an optimal value, currently just a arbitrary choice
-		const ARR_EXTEND_THRESHOLD: usize = 100;
+		const ARR_EXTEND_THRESHOLD: usize = 1000;
 
 		if a.is_empty() {
 			b
modifiedcrates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/arr/spec.rs
+++ b/crates/jrsonnet-evaluator/src/arr/spec.rs
@@ -3,7 +3,7 @@
 
 use jrsonnet_gcmodule::{Cc, Trace};
 use jrsonnet_interner::{IBytes, IStr};
-use jrsonnet_parser::{Expr, Spanned};
+use jrsonnet_ir::{Expr, Spanned};
 
 use super::ArrValue;
 use crate::function::NativeFn;
modifiedcrates/jrsonnet-evaluator/src/async_import.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/async_import.rs
+++ b/crates/jrsonnet-evaluator/src/async_import.rs
@@ -2,11 +2,12 @@
 use std::{any::Any, cell::RefCell, future::Future};
 
 use jrsonnet_gcmodule::Acyclic;
-use jrsonnet_parser::{
+use jrsonnet_ir::{
 	ArgsDesc, AssertExpr, AssertStmt, BindSpec, CompSpec, Destruct, Expr, ExprParam, ExprParams,
-	FieldMember, FieldName, ForSpecData, IfElse, IfSpecData, ImportKind, ObjBody, ParserSettings,
-	Slice, SliceDesc, Source, SourcePath, Spanned,
+	FieldMember, FieldName, ForSpecData, IfElse, IfSpecData, ImportKind, ObjBody, Slice, SliceDesc,
+	Source, SourcePath, Spanned,
 };
+use jrsonnet_peg_parser::ParserSettings;
 use rustc_hash::FxHashMap;
 
 use crate::{AsPathLike, FileData, ImportResolver, ResolvePathOwned, State};
@@ -322,7 +323,7 @@
 						};
 						let source = Source::new(path.clone(), code.clone());
 						// If failed - then skip import
-						file.parsed = jrsonnet_parser::parse(&code, &ParserSettings { source })
+						file.parsed = jrsonnet_peg_parser::parse(&code, &ParserSettings { source })
 							.map(Rc::new)
 							.ok();
 						if let Some(parsed) = &file.parsed {
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -2,7 +2,7 @@
 
 use jrsonnet_gcmodule::{Acyclic, Trace};
 use jrsonnet_interner::IStr;
-use jrsonnet_parser::{BinaryOpType, Source, SourcePath, Span, Spanned, UnaryOpType};
+use jrsonnet_ir::{BinaryOpType, Source, SourcePath, Span, Spanned, UnaryOpType};
 use jrsonnet_types::ValType;
 use thiserror::Error;
 
@@ -169,7 +169,7 @@
 	ImportSyntaxError {
 		path: Source,
 		#[trace(skip)]
-		error: Box<jrsonnet_parser::ParseError>,
+		error: Box<jrsonnet_peg_parser::ParseError>,
 	},
 
 	#[error("runtime error: {}", format_empty_str(.0))]
modifiedcrates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -1,7 +1,7 @@
 use std::{collections::HashMap, hash::BuildHasher};
 
 use jrsonnet_interner::IStr;
-use jrsonnet_parser::{BindSpec, Destruct};
+use jrsonnet_ir::{BindSpec, Destruct};
 
 use crate::{
 	bail,
@@ -31,7 +31,7 @@
 		Destruct::Skip => {}
 		#[cfg(feature = "exp-destruct")]
 		Destruct::Array { start, rest, end } => {
-			use jrsonnet_parser::DestructRest;
+			use jrsonnet_ir::DestructRest;
 
 			let min_len = start.len() + end.len();
 			let has_rest = rest.is_some();
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -2,7 +2,7 @@
 
 use jrsonnet_gcmodule::{Cc, Trace};
 use jrsonnet_interner::IStr;
-use jrsonnet_parser::{
+use jrsonnet_ir::{
 	function::ParamName, ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, ExprParams,
 	FieldMember, FieldName, ForSpecData, IfSpecData, ImportKind, LiteralType, ObjBody, ObjMembers,
 	Spanned,
@@ -47,17 +47,17 @@
 	stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)
 }
 
-pub fn evaluate_trivial(expr: &Spanned<Expr>) -> Option<Val> {
-	fn is_trivial(expr: &Spanned<Expr>) -> bool {
-		match &**expr {
+pub fn evaluate_trivial(expr: &Expr) -> Option<Val> {
+	fn is_trivial(expr: &Expr) -> bool {
+		match &*expr {
 			Expr::Str(_)
 			| Expr::Num(_)
 			| Expr::Literal(LiteralType::False | LiteralType::True | LiteralType::Null) => true,
-			Expr::Arr(a) => a.iter().all(is_trivial),
+			Expr::Arr(a) => a.iter().all(|e| is_trivial(&**e)),
 			_ => false,
 		}
 	}
-	Some(match &**expr {
+	Some(match &*expr {
 		Expr::Str(s) => Val::string(s.clone()),
 		Expr::Num(n) => {
 			Val::Num(NumValue::new(*n).expect("parser will not allow non-finite values"))
@@ -71,7 +71,7 @@
 			}
 			Val::Arr(ArrValue::eager(
 				n.iter()
-					.map(evaluate_trivial)
+					.map(|e| evaluate_trivial(&**e))
 					.map(|e| e.expect("checked trivial"))
 					.collect(),
 			))
@@ -395,14 +395,13 @@
 }
 
 #[allow(clippy::too_many_lines)]
-pub fn evaluate(ctx: Context, expr: &Spanned<Expr>) -> Result<Val> {
+pub fn evaluate(ctx: Context, expr: &Expr) -> Result<Val> {
 	use Expr::*;
 
 	if let Some(trivial) = evaluate_trivial(expr) {
 		return Ok(trivial);
 	}
-	let loc = expr.span();
-	Ok(match &**expr {
+	Ok(match expr {
 		Literal(LiteralType::This) => Val::Obj(ctx.try_this()?),
 		Literal(LiteralType::Super) => Val::Obj(ctx.try_sup_this()?.standalone_super()?),
 		Literal(LiteralType::Dollar) => Val::Obj(ctx.try_dollar()?),
@@ -433,9 +432,9 @@
 		BinaryOp(bin) => evaluate_binary_op_special(ctx, &bin.lhs, bin.op, &bin.rhs)?,
 		UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,
 		Var(name) => in_frame(
-			CallLocation::new(&loc),
+			CallLocation::new(&name.span()),
 			|| format!("local <{name}> access"),
-			|| ctx.binding(name.clone())?.evaluate(),
+			|| ctx.binding((**name).clone())?.evaluate(),
 		)?,
 		Index { indexable, parts } => ensure_sufficient_stack(|| {
 			let mut parts = parts.iter();
@@ -591,7 +590,13 @@
 			&Val::Obj(evaluate_object(ctx, b)?),
 		)?,
 		Apply(value, args, tailstrict) => ensure_sufficient_stack(|| {
-			evaluate_apply(ctx, value, args, CallLocation::new(&loc), *tailstrict)
+			evaluate_apply(
+				ctx,
+				value,
+				args,
+				CallLocation::new(&args.span()),
+				*tailstrict,
+			)
 		})?,
 		Function(params, body) => {
 			evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())
@@ -601,13 +606,13 @@
 			evaluate(ctx, &assert.rest)?
 		}
 		ErrorStmt(e) => in_frame(
-			CallLocation::new(&loc),
+			CallLocation::new(&e.span()),
 			|| "error statement".to_owned(),
 			|| bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),
 		)?,
 		IfElse(if_else) => {
 			if in_frame(
-				CallLocation::new(&loc),
+				CallLocation::new(&if_else.cond.0.span()),
 				|| "if condition".to_owned(),
 				|| bool::from_untyped(evaluate(ctx.clone(), &if_else.cond.0)?),
 			)? {
modifiedcrates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs
@@ -1,6 +1,6 @@
 use std::cmp::Ordering;
 
-use jrsonnet_parser::{BinaryOpType, Expr, Spanned, UnaryOpType};
+use jrsonnet_ir::{BinaryOpType, Expr, Spanned, UnaryOpType};
 
 use crate::{
 	arr::ArrValue,
modifiedcrates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/builtin.rs
+++ b/crates/jrsonnet-evaluator/src/function/builtin.rs
@@ -1,7 +1,7 @@
 use std::any::Any;
 
 use jrsonnet_gcmodule::{cc_dyn, Trace, TraceBox};
-use jrsonnet_parser::function::{FunctionSignature, ParamDefault, ParamName, ParamParse};
+use jrsonnet_ir::function::{FunctionSignature, ParamDefault, ParamName, ParamParse};
 
 use super::CallLocation;
 use crate::{Result, Thunk, Val};
modifiedcrates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/mod.rs
+++ b/crates/jrsonnet-evaluator/src/function/mod.rs
@@ -4,7 +4,7 @@
 use jrsonnet_gcmodule::{Cc, Trace};
 use jrsonnet_interner::IStr;
 pub use jrsonnet_macros::builtin;
-use jrsonnet_parser::{ArgsDesc, Destruct, Expr, ExprParams, Span, Spanned};
+use jrsonnet_ir::{ArgsDesc, Destruct, Expr, ExprParams, Span, Spanned};
 
 use self::{
 	builtin::{Builtin, StaticBuiltin},
@@ -24,7 +24,7 @@
 pub use native::NativeFn;
 pub use prepared::PreparedFuncVal;
 
-pub use jrsonnet_parser::function::*;
+pub use jrsonnet_ir::function::*;
 
 /// Function callsite location.
 /// Either from other jsonnet code, specified by expression location, or from native (without location).
modifiedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/parse.rs
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -1,6 +1,6 @@
 use std::rc::Rc;
 
-use jrsonnet_parser::{
+use jrsonnet_ir::{
 	function::{FunctionSignature, ParamName},
 	ArgsDesc, Expr, ExprParams, Spanned,
 };
modifiedcrates/jrsonnet-evaluator/src/function/prepared.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/prepared.rs
+++ b/crates/jrsonnet-evaluator/src/function/prepared.rs
@@ -1,8 +1,8 @@
 use std::rc::Rc;
 
 use jrsonnet_gcmodule::{Acyclic, Trace};
-use jrsonnet_parser::function::FunctionSignature;
-use jrsonnet_parser::{ExprParams, IStr};
+use jrsonnet_ir::function::FunctionSignature;
+use jrsonnet_ir::{ExprParams, IStr};
 use rustc_hash::{FxHashMap, FxHashSet};
 
 use crate::destructure::destruct;
modifiedcrates/jrsonnet-evaluator/src/import.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/import.rs
+++ b/crates/jrsonnet-evaluator/src/import.rs
@@ -10,7 +10,7 @@
 use fs::File;
 use jrsonnet_gcmodule::Acyclic;
 use jrsonnet_interner::IBytes;
-use jrsonnet_parser::{
+use jrsonnet_ir::{
 	IStr, SourceDefaultIgnoreJpath, SourceDirectory, SourceFifo, SourceFile, SourcePath,
 };
 
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -44,8 +44,9 @@
 pub use jrsonnet_interner::{IBytes, IStr};
 #[doc(hidden)]
 pub use jrsonnet_macros;
-pub use jrsonnet_parser as parser;
-use jrsonnet_parser::{Expr, ParserSettings, Source, SourcePath, Spanned};
+pub use jrsonnet_ir as parser;
+use jrsonnet_ir::{Expr, Source, SourcePath, Spanned};
+use jrsonnet_peg_parser::ParserSettings;
 pub use obj::*;
 pub use rustc_hash;
 use rustc_hash::FxHashMap;
@@ -344,7 +345,7 @@
 		let file_name = Source::new(path.clone(), code.clone());
 		if file.parsed.is_none() {
 			file.parsed = Some(
-				jrsonnet_parser::parse(
+				jrsonnet_peg_parser::parse(
 					&code,
 					&ParserSettings {
 						source: file_name.clone(),
@@ -460,7 +461,7 @@
 	pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {
 		let code = code.into();
 		let source = Source::new_virtual(name.into(), code.clone());
-		let parsed = jrsonnet_parser::parse(
+		let parsed = jrsonnet_peg_parser::parse(
 			&code,
 			&ParserSettings {
 				source: source.clone(),
@@ -481,7 +482,7 @@
 	) -> Result<Val> {
 		let code = code.into();
 		let source = Source::new_virtual(name.into(), code.clone());
-		let parsed = jrsonnet_parser::parse(
+		let parsed = jrsonnet_peg_parser::parse(
 			&code,
 			&ParserSettings {
 				source: source.clone(),
modifiedcrates/jrsonnet-evaluator/src/obj/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj/mod.rs
+++ b/crates/jrsonnet-evaluator/src/obj/mod.rs
@@ -5,12 +5,12 @@
 use educe::Educe;
 use jrsonnet_gcmodule::{cc_dyn, Acyclic, Cc, Trace, Weak};
 use jrsonnet_interner::IStr;
-use jrsonnet_parser::Span;
+use jrsonnet_ir::Span;
 use rustc_hash::{FxHashMap, FxHashSet};
 
 mod oop;
 
-pub use jrsonnet_parser::Visibility;
+pub use jrsonnet_ir::Visibility;
 pub use oop::ObjValueBuilder;
 
 use crate::{
modifiedcrates/jrsonnet-evaluator/src/obj/oop.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj/oop.rs
+++ b/crates/jrsonnet-evaluator/src/obj/oop.rs
@@ -8,7 +8,7 @@
 	bail, error::ErrorKind::*, in_frame, CcUnbound, MaybeUnbound, Result, Thunk, Unbound, Val,
 };
 use jrsonnet_gcmodule::{Cc, Trace};
-use jrsonnet_parser::IStr;
+use jrsonnet_ir::IStr;
 use rustc_hash::{FxHashMap, FxHashSet};
 
 use super::ordering::{FieldIndex, SuperDepth};
modifiedcrates/jrsonnet-evaluator/src/tla.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/tla.rs
+++ b/crates/jrsonnet-evaluator/src/tla.rs
@@ -2,7 +2,7 @@
 
 use jrsonnet_gcmodule::Trace;
 use jrsonnet_interner::IStr;
-use jrsonnet_parser::{SourceFifo, SourcePath};
+use jrsonnet_ir::{SourceFifo, SourcePath};
 
 use crate::{
 	function::{CallLocation, PreparedFuncVal},
modifiedcrates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/trace/mod.rs
+++ b/crates/jrsonnet-evaluator/src/trace/mod.rs
@@ -6,9 +6,9 @@
 };
 
 use jrsonnet_gcmodule::Trace;
-use jrsonnet_parser::CodeLocation;
+use jrsonnet_ir::CodeLocation;
 #[cfg(feature = "explaining-traces")]
-use jrsonnet_parser::Span;
+use jrsonnet_ir::Span;
 
 use crate::{error::ErrorKind, Error};
 
modifiedcrates/jrsonnet-formatter/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-formatter/src/lib.rs
+++ b/crates/jrsonnet-formatter/src/lib.rs
@@ -855,7 +855,7 @@
 }
 
 pub struct FormatOptions {
-	// 0 for hard tabs
+	// 0 for hard tabs, otherwise number of spaces
 	pub indent: u8,
 }
 
addedcrates/jrsonnet-ir/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-ir/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "jrsonnet-ir"
+description = "jsonnet language parser and AST"
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+version.workspace = true
+
+[features]
+default = []
+exp-destruct = []
+exp-null-coaelse = []
+
+[dependencies]
+jrsonnet-interner.workspace = true
+jrsonnet-gcmodule.workspace = true
+
+static_assertions.workspace = true
+
+peg.workspace = true
+
+[dev-dependencies]
+insta.workspace = true
addedcrates/jrsonnet-ir/README.adocdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-ir/README.adoc
@@ -0,0 +1,3 @@
+= jrsonnet-parser
+
+Parser for jsonnet language
addedcrates/jrsonnet-ir/src/expr.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-ir/src/expr.rs
@@ -0,0 +1,493 @@
+use std::{
+	fmt::{self, Debug, Display},
+	ops::Deref,
+	rc::Rc,
+};
+
+use jrsonnet_gcmodule::Acyclic;
+use jrsonnet_interner::IStr;
+
+use crate::{
+	function::{FunctionSignature, ParamDefault, ParamName, ParamParse},
+	source::Source,
+};
+
+#[derive(Debug, PartialEq, Acyclic)]
+pub enum FieldName {
+	/// {fixed: 2}
+	Fixed(IStr),
+	/// {["dyn"+"amic"]: 3}
+	Dyn(Spanned<Expr>),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Acyclic)]
+#[repr(u8)]
+pub enum Visibility {
+	/// :
+	Normal,
+	/// ::
+	Hidden,
+	/// :::
+	Unhide,
+}
+
+impl Visibility {
+	pub fn is_visible(&self) -> bool {
+		matches!(self, Self::Normal | Self::Unhide)
+	}
+}
+
+#[derive(Debug, PartialEq, Acyclic)]
+pub struct AssertStmt(pub Spanned<Expr>, pub Option<Spanned<Expr>>);
+
+#[derive(Debug, PartialEq, Acyclic)]
+pub struct FieldMember {
+	pub name: FieldName,
+	pub plus: bool,
+	pub params: Option<ExprParams>,
+	pub visibility: Visibility,
+	pub value: Rc<Spanned<Expr>>,
+}
+
+#[derive(Debug, PartialEq, Acyclic)]
+pub enum Member {
+	Field(FieldMember),
+	BindStmt(BindSpec),
+	AssertStmt(AssertStmt),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Acyclic)]
+pub enum UnaryOpType {
+	Plus,
+	Minus,
+	BitNot,
+	Not,
+}
+
+impl Display for UnaryOpType {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		use UnaryOpType::*;
+		write!(
+			f,
+			"{}",
+			match self {
+				Plus => "+",
+				Minus => "-",
+				BitNot => "~",
+				Not => "!",
+			}
+		)
+	}
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Acyclic)]
+pub enum BinaryOpType {
+	Mul,
+	Div,
+
+	/// Implemented as intrinsic, put here for completeness
+	Mod,
+
+	Add,
+	Sub,
+
+	Lhs,
+	Rhs,
+
+	Lt,
+	Gt,
+	Lte,
+	Gte,
+
+	BitAnd,
+	BitOr,
+	BitXor,
+
+	Eq,
+	Neq,
+
+	And,
+	Or,
+	#[cfg(feature = "exp-null-coaelse")]
+	NullCoaelse,
+
+	// Equialent to std.objectHasEx(a, b, true)
+	In,
+}
+
+impl Display for BinaryOpType {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		use BinaryOpType::*;
+		write!(
+			f,
+			"{}",
+			match self {
+				Mul => "*",
+				Div => "/",
+				Mod => "%",
+				Add => "+",
+				Sub => "-",
+				Lhs => "<<",
+				Rhs => ">>",
+				Lt => "<",
+				Gt => ">",
+				Lte => "<=",
+				Gte => ">=",
+				BitAnd => "&",
+				BitOr => "|",
+				BitXor => "^",
+				Eq => "==",
+				Neq => "!=",
+				And => "&&",
+				Or => "||",
+				In => "in",
+				#[cfg(feature = "exp-null-coaelse")]
+				NullCoaelse => "??",
+			}
+		)
+	}
+}
+
+/// name, default value
+#[derive(Debug, PartialEq, Acyclic)]
+pub struct ExprParam {
+	pub destruct: Destruct,
+	pub default: Option<Rc<Spanned<Expr>>>,
+}
+
+/// Defined function parameters
+#[derive(Debug, Clone, PartialEq, Acyclic)]
+pub struct ExprParams {
+	pub exprs: Rc<Vec<ExprParam>>,
+	pub signature: FunctionSignature,
+	binds_len: usize,
+}
+impl ExprParams {
+	pub fn len(&self) -> usize {
+		self.exprs.len()
+	}
+	pub fn is_empty(&self) -> bool {
+		self.exprs.is_empty()
+	}
+
+	pub fn binds_len(&self) -> usize {
+		self.binds_len
+	}
+	pub fn new(exprs: Vec<ExprParam>) -> Self {
+		Self {
+			signature: FunctionSignature::new(
+				exprs
+					.iter()
+					.map(|p| {
+						ParamParse::new(
+							p.destruct.name(),
+							ParamDefault::exists(p.default.is_some()),
+						)
+					})
+					.collect(),
+			),
+			binds_len: exprs.iter().map(|v| v.destruct.binds_len()).sum(),
+			exprs: Rc::new(exprs),
+		}
+	}
+}
+
+#[derive(Debug, PartialEq, Acyclic)]
+pub struct ArgsDesc {
+	pub unnamed: Vec<Rc<Spanned<Expr>>>,
+	pub named: Vec<(IStr, Rc<Spanned<Expr>>)>,
+}
+impl ArgsDesc {
+	pub fn new(unnamed: Vec<Rc<Spanned<Expr>>>, named: Vec<(IStr, Rc<Spanned<Expr>>)>) -> Self {
+		Self { unnamed, named }
+	}
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Acyclic)]
+pub enum DestructRest {
+	/// ...rest
+	Keep(IStr),
+	/// ...
+	Drop,
+}
+
+#[derive(Debug, Clone, PartialEq, Acyclic)]
+pub enum Destruct {
+	Full(IStr),
+	#[cfg(feature = "exp-destruct")]
+	Skip,
+	#[cfg(feature = "exp-destruct")]
+	Array {
+		start: Vec<Destruct>,
+		rest: Option<DestructRest>,
+		end: Vec<Destruct>,
+	},
+	#[cfg(feature = "exp-destruct")]
+	Object {
+		#[allow(clippy::type_complexity)]
+		fields: Vec<(IStr, Option<Destruct>, Option<Rc<Spanned<Expr>>>)>,
+		rest: Option<DestructRest>,
+	},
+}
+impl Destruct {
+	/// Name of destructure, used for function parameter names
+	pub fn name(&self) -> ParamName {
+		match self {
+			Self::Full(name) => ParamName::Named(name.clone()),
+			#[cfg(feature = "exp-destruct")]
+			_ => ParamName::Unnamed,
+		}
+	}
+	pub fn binds_len(&self) -> usize {
+		#[cfg(feature = "exp-destruct")]
+		fn cap_rest(rest: &Option<DestructRest>) -> usize {
+			match rest {
+				Some(DestructRest::Keep(_)) => 1,
+				Some(DestructRest::Drop) => 0,
+				None => 0,
+			}
+		}
+		match self {
+			Self::Full(_) => 1,
+			#[cfg(feature = "exp-destruct")]
+			Self::Skip => 0,
+			#[cfg(feature = "exp-destruct")]
+			Self::Array { start, rest, end } => {
+				start.iter().map(Destruct::binds_len).sum::<usize>()
+					+ end.iter().map(Destruct::binds_len).sum::<usize>()
+					+ cap_rest(rest)
+			}
+			#[cfg(feature = "exp-destruct")]
+			Self::Object { fields, rest } => {
+				let mut out = 0;
+				for (_, into, _) in fields {
+					match into {
+						Some(v) => out += v.binds_len(),
+						// Field is destructured to default name
+						None => out += 1,
+					}
+				}
+				out + cap_rest(rest)
+			}
+		}
+	}
+}
+
+#[derive(Debug, PartialEq, Acyclic)]
+pub enum BindSpec {
+	Field {
+		into: Destruct,
+		value: Rc<Spanned<Expr>>,
+	},
+	Function {
+		name: IStr,
+		params: ExprParams,
+		value: Rc<Spanned<Expr>>,
+	},
+}
+impl BindSpec {
+	pub fn binds_len(&self) -> usize {
+		match self {
+			BindSpec::Field { into, .. } => into.binds_len(),
+			BindSpec::Function { .. } => 1,
+		}
+	}
+}
+
+#[derive(Debug, PartialEq, Acyclic)]
+pub struct IfSpecData(pub Spanned<Expr>);
+
+#[derive(Debug, PartialEq, Acyclic)]
+pub struct ForSpecData(pub Destruct, pub Spanned<Expr>);
+
+#[derive(Debug, PartialEq, Acyclic)]
+pub enum CompSpec {
+	IfSpec(IfSpecData),
+	ForSpec(ForSpecData),
+}
+
+#[derive(Debug, PartialEq, Acyclic)]
+pub struct ObjComp {
+	pub locals: Rc<Vec<BindSpec>>,
+	pub field: Rc<FieldMember>,
+	pub compspecs: Vec<CompSpec>,
+}
+
+#[derive(Debug, PartialEq, Acyclic)]
+pub struct ObjMembers {
+	pub locals: Rc<Vec<BindSpec>>,
+	pub asserts: Rc<Vec<AssertStmt>>,
+	pub fields: Vec<FieldMember>,
+}
+
+#[derive(Debug, PartialEq, Acyclic)]
+pub enum ObjBody {
+	MemberList(ObjMembers),
+	ObjComp(ObjComp),
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Acyclic)]
+pub enum LiteralType {
+	This,
+	Super,
+	Dollar,
+	Null,
+	True,
+	False,
+}
+
+#[derive(Debug, PartialEq, Acyclic)]
+pub struct SliceDesc {
+	pub start: Option<Spanned<Expr>>,
+	pub end: Option<Spanned<Expr>>,
+	pub step: Option<Spanned<Expr>>,
+}
+
+#[derive(Debug, PartialEq, Acyclic)]
+pub struct AssertExpr {
+	pub assert: AssertStmt,
+	pub rest: Spanned<Expr>,
+}
+
+#[derive(Debug, PartialEq, Acyclic)]
+pub struct BinaryOp {
+	pub lhs: Spanned<Expr>,
+	pub op: BinaryOpType,
+	pub rhs: Spanned<Expr>,
+}
+
+#[derive(Debug, PartialEq, Acyclic)]
+pub enum ImportKind {
+	Normal,
+	Str,
+	Bin,
+}
+
+#[derive(Debug, PartialEq, Acyclic)]
+pub struct IfElse {
+	pub cond: IfSpecData,
+	pub cond_then: Spanned<Expr>,
+	pub cond_else: Option<Spanned<Expr>>,
+}
+
+#[derive(Debug, PartialEq, Acyclic)]
+pub struct Slice {
+	pub value: Spanned<Expr>,
+	pub slice: SliceDesc,
+}
+
+/// Syntax base
+#[derive(Debug, PartialEq, Acyclic)]
+pub enum Expr {
+	Literal(LiteralType),
+
+	/// String value: "hello"
+	Str(IStr),
+	/// Number: 1, 2.0, 2e+20
+	Num(f64),
+	/// Variable name: test
+	Var(Spanned<IStr>),
+
+	/// Array of expressions: [1, 2, "Hello"]
+	Arr(Rc<Vec<Spanned<Expr>>>),
+	/// Array comprehension:
+	/// ```jsonnet
+	///  ingredients: [
+	///    { kind: kind, qty: 4 / 3 }
+	///    for kind in [
+	///      'Honey Syrup',
+	///      'Lemon Juice',
+	///      'Farmers Gin',
+	///    ]
+	///  ],
+	/// ```
+	ArrComp(Rc<Spanned<Expr>>, Vec<CompSpec>),
+
+	/// Object: {a: 2}
+	Obj(ObjBody),
+	/// Object extension: var1 {b: 2}
+	ObjExtend(Rc<Spanned<Expr>>, ObjBody),
+
+	/// -2
+	UnaryOp(UnaryOpType, Box<Spanned<Expr>>),
+	/// 2 - 2
+	BinaryOp(Box<BinaryOp>),
+	/// assert 2 == 2 : "Math is broken"
+	AssertExpr(Rc<AssertExpr>),
+	/// local a = 2; { b: a }
+	LocalExpr(Vec<BindSpec>, Box<Spanned<Expr>>),
+
+	/// import* "hello"
+	Import(ImportKind, Box<Spanned<Expr>>),
+	/// error "I'm broken"
+	ErrorStmt(Box<Spanned<Expr>>),
+	/// a(b, c)
+	Apply(Box<Spanned<Expr>>, Spanned<ArgsDesc>, bool),
+	/// a[b], a.b, a?.b
+	Index {
+		indexable: Box<Spanned<Expr>>,
+		parts: Vec<IndexPart>,
+	},
+	/// function(x) x
+	Function(ExprParams, Rc<Spanned<Expr>>),
+	/// if true == false then 1 else 2
+	IfElse(Box<IfElse>),
+	Slice(Box<Slice>),
+}
+
+#[derive(Debug, PartialEq, Acyclic)]
+pub struct IndexPart {
+	pub value: Spanned<Expr>,
+	#[cfg(feature = "exp-null-coaelse")]
+	pub null_coaelse: bool,
+}
+
+/// file, begin offset, end offset
+#[derive(Clone, PartialEq, Eq, Acyclic)]
+#[repr(C)]
+pub struct Span(pub Source, pub u32, pub u32);
+impl Span {
+	pub fn belongs_to(&self, other: &Span) -> bool {
+		other.0 == self.0 && other.1 <= self.1 && other.2 >= self.2
+	}
+}
+
+static_assertions::assert_eq_size!(Span, (usize, usize));
+
+impl Debug for Span {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "{:?}:{:?}-{:?}", self.0, self.1, self.2)
+	}
+}
+
+#[derive(Clone, PartialEq, Acyclic)]
+pub struct Spanned<T: Acyclic>(T, Span);
+impl<T: Acyclic> Deref for Spanned<T> {
+	type Target = T;
+	fn deref(&self) -> &Self::Target {
+		&self.0
+	}
+}
+impl<T: Acyclic> Spanned<T> {
+	#[inline]
+	pub fn new(v: T, s: Span) -> Self {
+		Self(v, s)
+	}
+	#[inline]
+	pub fn span(&self) -> Span {
+		self.1.clone()
+	}
+}
+
+impl<T: Debug + Acyclic> Debug for Spanned<T> {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		let expr = &**self;
+		if f.alternate() {
+			write!(f, "{:#?}", expr)?;
+		} else {
+			write!(f, "{:?}", expr)?;
+		}
+		write!(f, " from {:?}", self.span())?;
+		Ok(())
+	}
+}
addedcrates/jrsonnet-ir/src/function.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-ir/src/function.rs
@@ -0,0 +1,132 @@
+use std::fmt;
+use std::ops::Deref;
+use std::rc::Rc;
+
+use jrsonnet_gcmodule::Acyclic;
+use jrsonnet_interner::IStr;
+
+#[derive(Clone, Acyclic, Debug, PartialEq, Eq)]
+pub enum ParamName {
+	Unnamed,
+	Named(IStr),
+}
+impl ParamName {
+	pub fn as_str(&self) -> Option<&str> {
+		match self {
+			ParamName::Unnamed => None,
+			ParamName::Named(istr) => Some(istr),
+		}
+	}
+	pub fn is_anonymous(&self) -> bool {
+		matches!(self, Self::Unnamed)
+	}
+	pub fn is_named(&self) -> bool {
+		matches!(self, Self::Named(_))
+	}
+}
+impl PartialEq<IStr> for ParamName {
+	fn eq(&self, other: &IStr) -> bool {
+		match self {
+			ParamName::Unnamed => false,
+			ParamName::Named(istr) => istr == other,
+		}
+	}
+}
+
+impl fmt::Display for ParamName {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		match &self {
+			Self::Named(v) => write!(f, "{v}"),
+			Self::Unnamed => write!(f, "<unnamed>"),
+		}
+	}
+}
+
+#[derive(Clone, Copy, Debug, Acyclic, PartialEq, Eq)]
+pub enum ParamDefault {
+	None,
+	Exists,
+	Literal(&'static str),
+}
+impl ParamDefault {
+	pub const fn exists(is_exists: bool) -> Self {
+		if is_exists {
+			Self::Exists
+		} else {
+			Self::None
+		}
+	}
+}
+impl fmt::Display for ParamDefault {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		match self {
+			ParamDefault::None => Ok(()),
+			ParamDefault::Exists => write!(f, " = <default>"),
+			ParamDefault::Literal(lit) => write!(f, " = {lit}"),
+		}
+	}
+}
+
+#[derive(Clone, Acyclic, Debug, PartialEq, Eq)]
+pub struct ParamParse {
+	name: ParamName,
+	default: ParamDefault,
+}
+impl ParamParse {
+	pub fn new(name: ParamName, default: ParamDefault) -> Self {
+		Self { name, default }
+	}
+	/// Parameter name for named call parsing
+	pub fn name(&self) -> &ParamName {
+		&self.name
+	}
+	pub fn default(&self) -> ParamDefault {
+		self.default
+	}
+	pub fn has_default(&self) -> bool {
+		!matches!(self.default, ParamDefault::None)
+	}
+}
+impl fmt::Display for ParamParse {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "{}{}", self.name, self.default)
+	}
+}
+
+#[derive(Debug, Clone, Acyclic, PartialEq, Eq)]
+pub struct FunctionSignature(Rc<[ParamParse]>);
+impl Deref for FunctionSignature {
+	type Target = [ParamParse];
+
+	fn deref(&self) -> &Self::Target {
+		&self.0
+	}
+}
+
+thread_local! {
+	static EMPTY_SIGNATURE: FunctionSignature = FunctionSignature::new([].into());
+}
+
+impl FunctionSignature {
+	pub fn new(v: Rc<[ParamParse]>) -> Self {
+		Self(v)
+	}
+	pub fn empty() -> Self {
+		EMPTY_SIGNATURE.with(|p| p.clone())
+	}
+}
+impl fmt::Display for FunctionSignature {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		if self.0.is_empty() {
+			return write!(f, "(/*no arguments*/)");
+		}
+		write!(f, "(")?;
+		for (i, par) in self.0.iter().enumerate() {
+			if i != 0 {
+				write!(f, ", ")?;
+			}
+			write!(f, "{par}")?;
+		}
+		write!(f, ")")
+	}
+}
addedcrates/jrsonnet-ir/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-ir/src/lib.rs
@@ -0,0 +1,15 @@
+#![allow(clippy::redundant_closure_call, clippy::derive_partial_eq_without_eq)]
+
+mod expr;
+pub use expr::*;
+pub use jrsonnet_interner::IStr;
+pub mod function;
+mod location;
+mod source;
+pub mod unescape;
+
+pub use location::CodeLocation;
+pub use source::{
+	Source, SourceDefaultIgnoreJpath, SourceDirectory, SourceFifo, SourceFile, SourcePath,
+	SourcePathT, SourceVirtual,
+};
addedcrates/jrsonnet-ir/src/location.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-ir/src/location.rs
@@ -0,0 +1,115 @@
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
+pub struct CodeLocation {
+	pub offset: usize,
+
+	pub line: usize,
+	pub column: usize,
+
+	pub line_start_offset: usize,
+	pub line_end_offset: usize,
+}
+
+#[allow(clippy::module_name_repetitions)]
+pub fn location_to_offset(mut file: &str, mut line: usize, column: usize) -> Option<usize> {
+	let mut offset = 0;
+	while line > 1 {
+		let pos = file.find('\n')?;
+		offset += pos + 1;
+		file = &file[pos + 1..];
+		line -= 1;
+	}
+	offset += column - 1;
+	Some(offset)
+}
+
+#[allow(clippy::module_name_repetitions)]
+pub fn offset_to_location<const S: usize>(file: &str, offsets: &[u32; S]) -> [CodeLocation; S] {
+	if offsets.is_empty() {
+		return [CodeLocation::default(); S];
+	}
+	let mut line = 1;
+	let mut column = 1;
+	let max_offset = *offsets.iter().max().expect("offsets is not empty");
+
+	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 = [CodeLocation::default(); S];
+	let mut with_no_known_line_ending = vec![];
+	let mut this_line_offset = 0;
+	for (pos, ch) in file
+		.chars()
+		.enumerate()
+		.chain(std::iter::once((file.len(), ' ')))
+	{
+		column += 1;
+		match offset_map.last() {
+			Some(x) if x.0 == pos as u32 => {
+				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;
+				offset_map.pop();
+			}
+			_ => {}
+		}
+		if ch == '\n' {
+			line += 1;
+			column = 1;
+
+			for idx in with_no_known_line_ending.drain(..) {
+				out[idx].line_end_offset = pos;
+			}
+			this_line_offset = pos + 1;
+
+			if pos == max_offset as usize + 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]
+			),
+			[
+				CodeLocation {
+					offset: 0,
+					line: 1,
+					column: 2,
+					line_start_offset: 0,
+					line_end_offset: 11,
+				},
+				CodeLocation {
+					offset: 14,
+					line: 2,
+					column: 4,
+					line_start_offset: 12,
+					line_end_offset: 67
+				}
+			]
+		)
+	}
+}
addedcrates/jrsonnet-ir/src/source.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-ir/src/source.rs
@@ -0,0 +1,307 @@
+use std::{
+	any::Any,
+	fmt::{self, Debug, Display},
+	hash::{Hash, Hasher},
+	path::{Path, PathBuf},
+	rc::Rc,
+};
+
+use jrsonnet_gcmodule::Acyclic;
+use jrsonnet_interner::{IBytes, IStr};
+
+use crate::location::{location_to_offset, offset_to_location, CodeLocation};
+
+macro_rules! any_ext_methods {
+	($T:ident) => {
+		fn as_any(&self) -> &dyn Any;
+		fn dyn_hash(&self, hasher: &mut dyn Hasher);
+		fn dyn_eq(&self, other: &dyn $T) -> bool;
+		fn dyn_debug(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
+	};
+}
+macro_rules! any_ext_impl {
+	($T:ident) => {
+		fn as_any(&self) -> &dyn Any {
+			self
+		}
+		fn dyn_hash(&self, mut hasher: &mut dyn Hasher) {
+			self.hash(&mut hasher)
+		}
+		fn dyn_eq(&self, other: &dyn $T) -> bool {
+			let Some(other) = other.as_any().downcast_ref::<Self>() else {
+				return false;
+			};
+			let this = <Self as $T>::as_any(self)
+				.downcast_ref::<Self>()
+				.expect("restricted by impl");
+			this == other
+		}
+		fn dyn_debug(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+			<Self as std::fmt::Debug>::fmt(self, fmt)
+		}
+	};
+}
+macro_rules! any_ext {
+	($T:ident) => {
+		impl Hash for dyn $T {
+			fn hash<H: Hasher>(&self, state: &mut H) {
+				self.dyn_hash(state)
+			}
+		}
+		impl PartialEq for dyn $T {
+			fn eq(&self, other: &Self) -> bool {
+				self.dyn_eq(other)
+			}
+		}
+		impl Eq for dyn $T {}
+	};
+}
+pub trait SourcePathT: Acyclic + Debug + Display {
+	/// This method should be checked by resolver before panicking with bad SourcePath input
+	/// if `true` - then resolver may threat this path as default, and default is usally a CWD
+	fn is_default(&self) -> bool;
+	fn path(&self) -> Option<&Path>;
+	any_ext_methods!(SourcePathT);
+}
+any_ext!(SourcePathT);
+
+/// Represents location of a file
+///
+/// Standard CLI only operates using
+/// - [`SourceFile`] - for any file
+/// - [`SourceDirectory`] - for resolution from CWD
+/// - [`SourceVirtual`] - for stdlib/ext-str
+/// - [`SourceFifo`] - for /dev/fd/X (This path may appear with `jrsonnet <(command_that_produces_jsonnet)`)
+///
+/// From all of those, only [`SourceVirtual`] may be constructed manually, any other path kind should be only obtained
+/// from assigned `ImportResolver`
+/// However, you should always check `is_default` method return, as it will return true for any paths, where default
+/// search location is applicable
+///
+/// Resolver may also return custom implementations of this trait, for example it may return http url in case of remotely loaded files
+#[derive(Eq, Clone, Acyclic)]
+pub struct SourcePath(Rc<dyn SourcePathT>);
+impl SourcePath {
+	pub fn new(inner: impl SourcePathT) -> Self {
+		Self(Rc::new(inner))
+	}
+	pub fn downcast_ref<T: SourcePathT>(&self) -> Option<&T> {
+		self.0.as_any().downcast_ref()
+	}
+	pub fn is_default(&self) -> bool {
+		self.0.is_default()
+	}
+	pub fn path(&self) -> Option<&Path> {
+		self.0.path()
+	}
+}
+impl Hash for SourcePath {
+	fn hash<H: Hasher>(&self, state: &mut H) {
+		self.0.hash(state);
+	}
+}
+impl PartialEq for SourcePath {
+	#[allow(clippy::op_ref)]
+	fn eq(&self, other: &Self) -> bool {
+		&*self.0 == &*other.0
+	}
+}
+impl Display for SourcePath {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "{}", self.0)
+	}
+}
+impl Debug for SourcePath {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "{:?}", self.0)
+	}
+}
+impl Default for SourcePath {
+	fn default() -> Self {
+		Self(Rc::new(SourceDefault))
+	}
+}
+
+#[derive(Acyclic, Hash, PartialEq, Eq, Debug)]
+struct SourceDefault;
+impl Display for SourceDefault {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "<default>")
+	}
+}
+impl SourcePathT for SourceDefault {
+	fn is_default(&self) -> bool {
+		true
+	}
+	fn path(&self) -> Option<&Path> {
+		None
+	}
+	any_ext_impl!(SourcePathT);
+}
+
+#[derive(Acyclic, Hash, PartialEq, Eq, Debug)]
+pub struct SourceDefaultIgnoreJpath;
+impl Display for SourceDefaultIgnoreJpath {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "<default (ignoring jpath)>")
+	}
+}
+impl SourcePathT for SourceDefaultIgnoreJpath {
+	fn is_default(&self) -> bool {
+		true
+	}
+	fn path(&self) -> Option<&Path> {
+		None
+	}
+	any_ext_impl!(SourcePathT);
+}
+
+/// Represents path to the file on the disk
+/// Directories shouldn't be put here, as resolution for files differs from resolution for directories:
+///
+/// When `file` is being resolved from `SourceFile(a/b/c)`, it should be resolved to `SourceFile(a/b/file)`,
+/// however if it is being resolved from `SourceDirectory(a/b/c)`, then it should be resolved to `SourceDirectory(a/b/c/file)`
+#[derive(Acyclic, Hash, PartialEq, Eq, Debug)]
+pub struct SourceFile(PathBuf);
+impl SourceFile {
+	pub fn new(path: PathBuf) -> Self {
+		Self(path)
+	}
+	pub fn path(&self) -> &Path {
+		&self.0
+	}
+}
+impl Display for SourceFile {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "{}", self.0.display())
+	}
+}
+impl SourcePathT for SourceFile {
+	fn is_default(&self) -> bool {
+		false
+	}
+	fn path(&self) -> Option<&Path> {
+		Some(&self.0)
+	}
+	any_ext_impl!(SourcePathT);
+}
+
+/// Represents path to the directory on the disk
+///
+/// See also [`SourceFile`]
+#[derive(Acyclic, Hash, PartialEq, Eq, Debug)]
+pub struct SourceDirectory(PathBuf);
+impl SourceDirectory {
+	pub fn new(path: PathBuf) -> Self {
+		Self(path)
+	}
+	pub fn path(&self) -> &Path {
+		&self.0
+	}
+}
+impl Display for SourceDirectory {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "{}", self.0.display())
+	}
+}
+impl SourcePathT for SourceDirectory {
+	fn is_default(&self) -> bool {
+		false
+	}
+	fn path(&self) -> Option<&Path> {
+		Some(&self.0)
+	}
+	any_ext_impl!(SourcePathT);
+}
+
+/// Represents virtual file, whose are located in memory, and shouldn't be cached
+///
+/// It is used for --ext-code=.../--tla-code=.../standard library source code by default,
+/// and user can construct arbitrary values by hand, without asking import resolver
+#[derive(Acyclic, Hash, PartialEq, Eq, Clone)]
+pub struct SourceVirtual(pub IStr);
+impl Display for SourceVirtual {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "virtual:{}", self.0)
+	}
+}
+impl fmt::Debug for SourceVirtual {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "virtual:{}", self.0)
+	}
+}
+impl SourcePathT for SourceVirtual {
+	fn is_default(&self) -> bool {
+		true
+	}
+	fn path(&self) -> Option<&Path> {
+		None
+	}
+	any_ext_impl!(SourcePathT);
+}
+
+/// Represents resolved FIFO file, those files may only be read once, and this type is only used for
+/// unix, where user might want to do `jrsonnet <(command_that_produces_jsonnet_source)`
+/// In most cases, user most probably want to use `jrsonnet -` instead of `jrsonnet /dev/stdin`
+/// for better cross-platform support.
+// PartialEq is limited to ptr equality
+#[allow(clippy::derived_hash_with_manual_eq)]
+#[derive(Acyclic, Debug, Hash)]
+pub struct SourceFifo(pub String, pub IBytes);
+impl PartialEq for SourceFifo {
+	fn eq(&self, other: &Self) -> bool {
+		std::ptr::eq(self, other)
+	}
+}
+impl fmt::Display for SourceFifo {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "fifo({:?})", self.0)
+	}
+}
+impl SourcePathT for SourceFifo {
+	fn is_default(&self) -> bool {
+		// In case of FD input, user won't expect relative paths to be resolved from /dev/fd/
+		true
+	}
+
+	fn path(&self) -> Option<&Path> {
+		None
+	}
+
+	any_ext_impl!(SourcePathT);
+}
+
+/// Either real file, or virtual
+/// Hash of FileName always have same value as raw Path, to make it possible to use with raw_entry_mut
+#[derive(Clone, PartialEq, Eq, Acyclic)]
+pub struct Source(pub Rc<(SourcePath, IStr)>);
+
+impl Source {
+	pub fn new(path: SourcePath, code: IStr) -> Self {
+		Self(Rc::new((path, code)))
+	}
+
+	pub fn new_virtual(name: IStr, code: IStr) -> Self {
+		Self::new(SourcePath::new(SourceVirtual(name)), code)
+	}
+
+	pub fn code(&self) -> &str {
+		&self.0 .1
+	}
+
+	pub fn source_path(&self) -> &SourcePath {
+		&self.0 .0
+	}
+
+	pub fn map_source_locations<const S: usize>(&self, locs: &[u32; S]) -> [CodeLocation; S] {
+		offset_to_location(&self.0 .1, locs)
+	}
+	pub fn map_from_source_location(&self, line: usize, column: usize) -> Option<usize> {
+		location_to_offset(&self.0 .1, line, column)
+	}
+}
+impl fmt::Debug for Source {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "{:?}", self.0 .0)
+	}
+}
addedcrates/jrsonnet-ir/src/unescape.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-ir/src/unescape.rs
@@ -0,0 +1,55 @@
+use std::str::Chars;
+
+fn decode_unicode(chars: &mut Chars) -> Option<u16> {
+	IntoIterator::into_iter([chars.next()?, chars.next()?, chars.next()?, chars.next()?])
+		.map(|c| c.to_digit(16).map(|f| f as u16))
+		.try_fold(0u16, |acc, v| Some((acc << 4) | (v?)))
+}
+
+pub fn unescape(s: &str) -> Option<String> {
+	let mut chars = s.chars();
+	let mut out = String::with_capacity(s.len());
+
+	while let Some(c) = chars.next() {
+		if c != '\\' {
+			out.push(c);
+			continue;
+		}
+		match chars.next()? {
+			c @ ('\\' | '"' | '\'') => out.push(c),
+			'b' => out.push('\u{0008}'),
+			'f' => out.push('\u{000c}'),
+			'n' => out.push('\n'),
+			'r' => out.push('\r'),
+			't' => out.push('\t'),
+			'u' => match decode_unicode(&mut chars)? {
+				// May only be second byte
+				0xDC00..=0xDFFF => return None,
+				// Surrogate pair
+				n1 @ 0xD800..=0xDBFF => {
+					if chars.next() != Some('\\') {
+						return None;
+					}
+					if chars.next() != Some('u') {
+						return None;
+					}
+					let n2 = decode_unicode(&mut chars)?;
+					if !matches!(n2, 0xDC00..=0xDFFF) {
+						return None;
+					}
+					let n = (((n1 - 0xD800) as u32) << 10 | (n2 - 0xDC00) as u32) + 0x1_0000;
+					out.push(char::from_u32(n)?);
+				}
+				n => out.push(char::from_u32(n as u32)?),
+			},
+			'x' => {
+				let c = IntoIterator::into_iter([chars.next()?, chars.next()?])
+					.map(|c| c.to_digit(16))
+					.try_fold(0u32, |acc, v| Some((acc << 8) | (v?)))?;
+				out.push(char::from_u32(c)?)
+			}
+			_ => return None,
+		}
+	}
+	Some(out)
+}
deletedcrates/jrsonnet-parser/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-parser/Cargo.toml
+++ /dev/null
@@ -1,24 +0,0 @@
-[package]
-name = "jrsonnet-parser"
-description = "jsonnet language parser and AST"
-authors.workspace = true
-edition.workspace = true
-license.workspace = true
-repository.workspace = true
-version.workspace = true
-
-[features]
-default = []
-exp-destruct = []
-exp-null-coaelse = []
-
-[dependencies]
-jrsonnet-interner.workspace = true
-jrsonnet-gcmodule.workspace = true
-
-static_assertions.workspace = true
-
-peg.workspace = true
-
-[dev-dependencies]
-insta.workspace = true
deletedcrates/jrsonnet-parser/README.adocdiffbeforeafterboth
--- a/crates/jrsonnet-parser/README.adoc
+++ /dev/null
@@ -1,3 +0,0 @@
-= jrsonnet-parser
-
-Parser for jsonnet language
deletedcrates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/expr.rs
+++ /dev/null
@@ -1,493 +0,0 @@
-use std::{
-	fmt::{self, Debug, Display},
-	ops::Deref,
-	rc::Rc,
-};
-
-use jrsonnet_gcmodule::Acyclic;
-use jrsonnet_interner::IStr;
-
-use crate::{
-	function::{FunctionSignature, ParamDefault, ParamName, ParamParse},
-	source::Source,
-};
-
-#[derive(Debug, PartialEq, Acyclic)]
-pub enum FieldName {
-	/// {fixed: 2}
-	Fixed(IStr),
-	/// {["dyn"+"amic"]: 3}
-	Dyn(Spanned<Expr>),
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Acyclic)]
-#[repr(u8)]
-pub enum Visibility {
-	/// :
-	Normal,
-	/// ::
-	Hidden,
-	/// :::
-	Unhide,
-}
-
-impl Visibility {
-	pub fn is_visible(&self) -> bool {
-		matches!(self, Self::Normal | Self::Unhide)
-	}
-}
-
-#[derive(Debug, PartialEq, Acyclic)]
-pub struct AssertStmt(pub Spanned<Expr>, pub Option<Spanned<Expr>>);
-
-#[derive(Debug, PartialEq, Acyclic)]
-pub struct FieldMember {
-	pub name: FieldName,
-	pub plus: bool,
-	pub params: Option<ExprParams>,
-	pub visibility: Visibility,
-	pub value: Rc<Spanned<Expr>>,
-}
-
-#[derive(Debug, PartialEq, Acyclic)]
-pub(crate) enum Member {
-	Field(FieldMember),
-	BindStmt(BindSpec),
-	AssertStmt(AssertStmt),
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Acyclic)]
-pub enum UnaryOpType {
-	Plus,
-	Minus,
-	BitNot,
-	Not,
-}
-
-impl Display for UnaryOpType {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		use UnaryOpType::*;
-		write!(
-			f,
-			"{}",
-			match self {
-				Plus => "+",
-				Minus => "-",
-				BitNot => "~",
-				Not => "!",
-			}
-		)
-	}
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Acyclic)]
-pub enum BinaryOpType {
-	Mul,
-	Div,
-
-	/// Implemented as intrinsic, put here for completeness
-	Mod,
-
-	Add,
-	Sub,
-
-	Lhs,
-	Rhs,
-
-	Lt,
-	Gt,
-	Lte,
-	Gte,
-
-	BitAnd,
-	BitOr,
-	BitXor,
-
-	Eq,
-	Neq,
-
-	And,
-	Or,
-	#[cfg(feature = "exp-null-coaelse")]
-	NullCoaelse,
-
-	// Equialent to std.objectHasEx(a, b, true)
-	In,
-}
-
-impl Display for BinaryOpType {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		use BinaryOpType::*;
-		write!(
-			f,
-			"{}",
-			match self {
-				Mul => "*",
-				Div => "/",
-				Mod => "%",
-				Add => "+",
-				Sub => "-",
-				Lhs => "<<",
-				Rhs => ">>",
-				Lt => "<",
-				Gt => ">",
-				Lte => "<=",
-				Gte => ">=",
-				BitAnd => "&",
-				BitOr => "|",
-				BitXor => "^",
-				Eq => "==",
-				Neq => "!=",
-				And => "&&",
-				Or => "||",
-				In => "in",
-				#[cfg(feature = "exp-null-coaelse")]
-				NullCoaelse => "??",
-			}
-		)
-	}
-}
-
-/// name, default value
-#[derive(Debug, PartialEq, Acyclic)]
-pub struct ExprParam {
-	pub destruct: Destruct,
-	pub default: Option<Rc<Spanned<Expr>>>,
-}
-
-/// Defined function parameters
-#[derive(Debug, Clone, PartialEq, Acyclic)]
-pub struct ExprParams {
-	pub exprs: Rc<Vec<ExprParam>>,
-	pub signature: FunctionSignature,
-	binds_len: usize,
-}
-impl ExprParams {
-	pub fn len(&self) -> usize {
-		self.exprs.len()
-	}
-	pub fn is_empty(&self) -> bool {
-		self.exprs.is_empty()
-	}
-
-	pub fn binds_len(&self) -> usize {
-		self.binds_len
-	}
-	pub fn new(exprs: Vec<ExprParam>) -> Self {
-		Self {
-			signature: FunctionSignature::new(
-				exprs
-					.iter()
-					.map(|p| {
-						ParamParse::new(
-							p.destruct.name(),
-							ParamDefault::exists(p.default.is_some()),
-						)
-					})
-					.collect(),
-			),
-			binds_len: exprs.iter().map(|v| v.destruct.binds_len()).sum(),
-			exprs: Rc::new(exprs),
-		}
-	}
-}
-
-#[derive(Debug, PartialEq, Acyclic)]
-pub struct ArgsDesc {
-	pub unnamed: Vec<Rc<Spanned<Expr>>>,
-	pub named: Vec<(IStr, Rc<Spanned<Expr>>)>,
-}
-impl ArgsDesc {
-	pub fn new(unnamed: Vec<Rc<Spanned<Expr>>>, named: Vec<(IStr, Rc<Spanned<Expr>>)>) -> Self {
-		Self { unnamed, named }
-	}
-}
-
-#[derive(Debug, Clone, PartialEq, Eq, Acyclic)]
-pub enum DestructRest {
-	/// ...rest
-	Keep(IStr),
-	/// ...
-	Drop,
-}
-
-#[derive(Debug, Clone, PartialEq, Acyclic)]
-pub enum Destruct {
-	Full(IStr),
-	#[cfg(feature = "exp-destruct")]
-	Skip,
-	#[cfg(feature = "exp-destruct")]
-	Array {
-		start: Vec<Destruct>,
-		rest: Option<DestructRest>,
-		end: Vec<Destruct>,
-	},
-	#[cfg(feature = "exp-destruct")]
-	Object {
-		#[allow(clippy::type_complexity)]
-		fields: Vec<(IStr, Option<Destruct>, Option<Rc<Spanned<Expr>>>)>,
-		rest: Option<DestructRest>,
-	},
-}
-impl Destruct {
-	/// Name of destructure, used for function parameter names
-	pub fn name(&self) -> ParamName {
-		match self {
-			Self::Full(name) => ParamName::Named(name.clone()),
-			#[cfg(feature = "exp-destruct")]
-			_ => ParamName::Unnamed,
-		}
-	}
-	pub fn binds_len(&self) -> usize {
-		#[cfg(feature = "exp-destruct")]
-		fn cap_rest(rest: &Option<DestructRest>) -> usize {
-			match rest {
-				Some(DestructRest::Keep(_)) => 1,
-				Some(DestructRest::Drop) => 0,
-				None => 0,
-			}
-		}
-		match self {
-			Self::Full(_) => 1,
-			#[cfg(feature = "exp-destruct")]
-			Self::Skip => 0,
-			#[cfg(feature = "exp-destruct")]
-			Self::Array { start, rest, end } => {
-				start.iter().map(Destruct::binds_len).sum::<usize>()
-					+ end.iter().map(Destruct::binds_len).sum::<usize>()
-					+ cap_rest(rest)
-			}
-			#[cfg(feature = "exp-destruct")]
-			Self::Object { fields, rest } => {
-				let mut out = 0;
-				for (_, into, _) in fields {
-					match into {
-						Some(v) => out += v.binds_len(),
-						// Field is destructured to default name
-						None => out += 1,
-					}
-				}
-				out + cap_rest(rest)
-			}
-		}
-	}
-}
-
-#[derive(Debug, PartialEq, Acyclic)]
-pub enum BindSpec {
-	Field {
-		into: Destruct,
-		value: Rc<Spanned<Expr>>,
-	},
-	Function {
-		name: IStr,
-		params: ExprParams,
-		value: Rc<Spanned<Expr>>,
-	},
-}
-impl BindSpec {
-	pub fn binds_len(&self) -> usize {
-		match self {
-			BindSpec::Field { into, .. } => into.binds_len(),
-			BindSpec::Function { .. } => 1,
-		}
-	}
-}
-
-#[derive(Debug, PartialEq, Acyclic)]
-pub struct IfSpecData(pub Spanned<Expr>);
-
-#[derive(Debug, PartialEq, Acyclic)]
-pub struct ForSpecData(pub Destruct, pub Spanned<Expr>);
-
-#[derive(Debug, PartialEq, Acyclic)]
-pub enum CompSpec {
-	IfSpec(IfSpecData),
-	ForSpec(ForSpecData),
-}
-
-#[derive(Debug, PartialEq, Acyclic)]
-pub struct ObjComp {
-	pub locals: Rc<Vec<BindSpec>>,
-	pub field: Rc<FieldMember>,
-	pub compspecs: Vec<CompSpec>,
-}
-
-#[derive(Debug, PartialEq, Acyclic)]
-pub struct ObjMembers {
-	pub locals: Rc<Vec<BindSpec>>,
-	pub asserts: Rc<Vec<AssertStmt>>,
-	pub fields: Vec<FieldMember>,
-}
-
-#[derive(Debug, PartialEq, Acyclic)]
-pub enum ObjBody {
-	MemberList(ObjMembers),
-	ObjComp(ObjComp),
-}
-
-#[derive(Debug, PartialEq, Eq, Clone, Copy, Acyclic)]
-pub enum LiteralType {
-	This,
-	Super,
-	Dollar,
-	Null,
-	True,
-	False,
-}
-
-#[derive(Debug, PartialEq, Acyclic)]
-pub struct SliceDesc {
-	pub start: Option<Spanned<Expr>>,
-	pub end: Option<Spanned<Expr>>,
-	pub step: Option<Spanned<Expr>>,
-}
-
-#[derive(Debug, PartialEq, Acyclic)]
-pub struct AssertExpr {
-	pub assert: AssertStmt,
-	pub rest: Spanned<Expr>,
-}
-
-#[derive(Debug, PartialEq, Acyclic)]
-pub struct BinaryOp {
-	pub lhs: Spanned<Expr>,
-	pub op: BinaryOpType,
-	pub rhs: Spanned<Expr>,
-}
-
-#[derive(Debug, PartialEq, Acyclic)]
-pub enum ImportKind {
-	Normal,
-	Str,
-	Bin,
-}
-
-#[derive(Debug, PartialEq, Acyclic)]
-pub struct IfElse {
-	pub cond: IfSpecData,
-	pub cond_then: Spanned<Expr>,
-	pub cond_else: Option<Spanned<Expr>>,
-}
-
-#[derive(Debug, PartialEq, Acyclic)]
-pub struct Slice {
-	pub value: Spanned<Expr>,
-	pub slice: SliceDesc,
-}
-
-/// Syntax base
-#[derive(Debug, PartialEq, Acyclic)]
-pub enum Expr {
-	Literal(LiteralType),
-
-	/// String value: "hello"
-	Str(IStr),
-	/// Number: 1, 2.0, 2e+20
-	Num(f64),
-	/// Variable name: test
-	Var(IStr),
-
-	/// Array of expressions: [1, 2, "Hello"]
-	Arr(Rc<Vec<Spanned<Expr>>>),
-	/// Array comprehension:
-	/// ```jsonnet
-	///  ingredients: [
-	///    { kind: kind, qty: 4 / 3 }
-	///    for kind in [
-	///      'Honey Syrup',
-	///      'Lemon Juice',
-	///      'Farmers Gin',
-	///    ]
-	///  ],
-	/// ```
-	ArrComp(Rc<Spanned<Expr>>, Vec<CompSpec>),
-
-	/// Object: {a: 2}
-	Obj(ObjBody),
-	/// Object extension: var1 {b: 2}
-	ObjExtend(Rc<Spanned<Expr>>, ObjBody),
-
-	/// -2
-	UnaryOp(UnaryOpType, Box<Spanned<Expr>>),
-	/// 2 - 2
-	BinaryOp(Box<BinaryOp>),
-	/// assert 2 == 2 : "Math is broken"
-	AssertExpr(Rc<AssertExpr>),
-	/// local a = 2; { b: a }
-	LocalExpr(Vec<BindSpec>, Box<Spanned<Expr>>),
-
-	/// import* "hello"
-	Import(ImportKind, Box<Spanned<Expr>>),
-	/// error "I'm broken"
-	ErrorStmt(Box<Spanned<Expr>>),
-	/// a(b, c)
-	Apply(Box<Spanned<Expr>>, ArgsDesc, bool),
-	/// a[b], a.b, a?.b
-	Index {
-		indexable: Box<Spanned<Expr>>,
-		parts: Vec<IndexPart>,
-	},
-	/// function(x) x
-	Function(ExprParams, Rc<Spanned<Expr>>),
-	/// if true == false then 1 else 2
-	IfElse(Box<IfElse>),
-	Slice(Box<Slice>),
-}
-
-#[derive(Debug, PartialEq, Acyclic)]
-pub struct IndexPart {
-	pub value: Spanned<Expr>,
-	#[cfg(feature = "exp-null-coaelse")]
-	pub null_coaelse: bool,
-}
-
-/// file, begin offset, end offset
-#[derive(Clone, PartialEq, Eq, Acyclic)]
-#[repr(C)]
-pub struct Span(pub Source, pub u32, pub u32);
-impl Span {
-	pub fn belongs_to(&self, other: &Span) -> bool {
-		other.0 == self.0 && other.1 <= self.1 && other.2 >= self.2
-	}
-}
-
-static_assertions::assert_eq_size!(Span, (usize, usize));
-
-impl Debug for Span {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		write!(f, "{:?}:{:?}-{:?}", self.0, self.1, self.2)
-	}
-}
-
-#[derive(Clone, PartialEq, Acyclic)]
-pub struct Spanned<T: Acyclic>(T, Span);
-impl<T: Acyclic> Deref for Spanned<T> {
-	type Target = T;
-	fn deref(&self) -> &Self::Target {
-		&self.0
-	}
-}
-impl<T: Acyclic> Spanned<T> {
-	#[inline]
-	pub fn new(v: T, s: Span) -> Self {
-		Self(v, s)
-	}
-	#[inline]
-	pub fn span(&self) -> Span {
-		self.1.clone()
-	}
-}
-
-impl<T: Debug + Acyclic> Debug for Spanned<T> {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		let expr = &**self;
-		if f.alternate() {
-			write!(f, "{:#?}", expr)?;
-		} else {
-			write!(f, "{:?}", expr)?;
-		}
-		write!(f, " from {:?}", self.span())?;
-		Ok(())
-	}
-}
deletedcrates/jrsonnet-parser/src/function.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/function.rs
+++ /dev/null
@@ -1,132 +0,0 @@
-use std::fmt;
-use std::ops::Deref;
-use std::rc::Rc;
-
-use jrsonnet_gcmodule::Acyclic;
-use jrsonnet_interner::IStr;
-
-#[derive(Clone, Acyclic, Debug, PartialEq, Eq)]
-pub enum ParamName {
-	Unnamed,
-	Named(IStr),
-}
-impl ParamName {
-	pub fn as_str(&self) -> Option<&str> {
-		match self {
-			ParamName::Unnamed => None,
-			ParamName::Named(istr) => Some(istr),
-		}
-	}
-	pub fn is_anonymous(&self) -> bool {
-		matches!(self, Self::Unnamed)
-	}
-	pub fn is_named(&self) -> bool {
-		matches!(self, Self::Named(_))
-	}
-}
-impl PartialEq<IStr> for ParamName {
-	fn eq(&self, other: &IStr) -> bool {
-		match self {
-			ParamName::Unnamed => false,
-			ParamName::Named(istr) => istr == other,
-		}
-	}
-}
-
-impl fmt::Display for ParamName {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		match &self {
-			Self::Named(v) => write!(f, "{v}"),
-			Self::Unnamed => write!(f, "<unnamed>"),
-		}
-	}
-}
-
-#[derive(Clone, Copy, Debug, Acyclic, PartialEq, Eq)]
-pub enum ParamDefault {
-	None,
-	Exists,
-	Literal(&'static str),
-}
-impl ParamDefault {
-	pub const fn exists(is_exists: bool) -> Self {
-		if is_exists {
-			Self::Exists
-		} else {
-			Self::None
-		}
-	}
-}
-impl fmt::Display for ParamDefault {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		match self {
-			ParamDefault::None => Ok(()),
-			ParamDefault::Exists => write!(f, " = <default>"),
-			ParamDefault::Literal(lit) => write!(f, " = {lit}"),
-		}
-	}
-}
-
-#[derive(Clone, Acyclic, Debug, PartialEq, Eq)]
-pub struct ParamParse {
-	name: ParamName,
-	default: ParamDefault,
-}
-impl ParamParse {
-	pub fn new(name: ParamName, default: ParamDefault) -> Self {
-		Self { name, default }
-	}
-	/// Parameter name for named call parsing
-	pub fn name(&self) -> &ParamName {
-		&self.name
-	}
-	pub fn default(&self) -> ParamDefault {
-		self.default
-	}
-	pub fn has_default(&self) -> bool {
-		!matches!(self.default, ParamDefault::None)
-	}
-}
-impl fmt::Display for ParamParse {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		write!(f, "{}{}", self.name, self.default)
-	}
-}
-
-#[derive(Debug, Clone, Acyclic, PartialEq, Eq)]
-pub struct FunctionSignature(Rc<[ParamParse]>);
-impl Deref for FunctionSignature {
-	type Target = [ParamParse];
-
-	fn deref(&self) -> &Self::Target {
-		&self.0
-	}
-}
-
-thread_local! {
-	static EMPTY_SIGNATURE: FunctionSignature = FunctionSignature::new([].into());
-}
-
-impl FunctionSignature {
-	pub fn new(v: Rc<[ParamParse]>) -> Self {
-		Self(v)
-	}
-	pub fn empty() -> Self {
-		EMPTY_SIGNATURE.with(|p| p.clone())
-	}
-}
-impl fmt::Display for FunctionSignature {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		if self.0.is_empty() {
-			return write!(f, "(/*no arguments*/)");
-		}
-		write!(f, "(")?;
-		for (i, par) in self.0.iter().enumerate() {
-			if i != 0 {
-				write!(f, ", ")?;
-			}
-			write!(f, "{par}")?;
-		}
-		write!(f, ")")
-	}
-}
deletedcrates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/lib.rs
+++ /dev/null
@@ -1,553 +0,0 @@
-#![allow(clippy::redundant_closure_call, clippy::derive_partial_eq_without_eq)]
-
-use std::rc::Rc;
-
-use peg::parser;
-mod expr;
-pub use expr::*;
-pub use jrsonnet_interner::IStr;
-pub use peg;
-pub mod function;
-mod location;
-mod source;
-mod unescape;
-
-pub use location::CodeLocation;
-pub use source::{
-	Source, SourceDefaultIgnoreJpath, SourceDirectory, SourceFifo, SourceFile, SourcePath,
-	SourcePathT, SourceVirtual,
-};
-
-pub struct ParserSettings {
-	pub source: Source,
-}
-
-macro_rules! expr_bin {
-	($a:ident $op:ident $b:ident) => {
-		Expr::BinaryOp(Box::new(BinaryOp {
-			lhs: $a,
-			op: $op,
-			rhs: $b,
-		}))
-	};
-}
-macro_rules! expr_un {
-	($op:ident $a:ident) => {
-		Expr::UnaryOp($op, Box::new($a))
-	};
-}
-
-parser! {
-	grammar jsonnet_parser() for str {
-		use peg::ParseLiteral;
-
-		rule eof() = quiet!{![_]} / expected!("<eof>")
-		rule eol() = "\n" / eof()
-
-		/// Standard C-like comments
-		rule comment()
-			= "//" (!eol()[_])* eol()
-			/ "/*" (!("*/")[_])* "*/"
-			/ "#" (!eol()[_])* eol()
-
-		rule single_whitespace() = quiet!{([' ' | '\r' | '\n' | '\t'] / comment())} / expected!("<whitespace>")
-		rule _() = quiet!{([' ' | '\r' | '\n' | '\t']+) / comment()}* / expected!("<whitespace>")
-
-		/// For comma-delimited elements
-		rule comma() = quiet!{_ "," _} / expected!("<comma>")
-		rule alpha() -> char = c:$(['_' | 'a'..='z' | 'A'..='Z']) {c.chars().next().unwrap()}
-		rule digit() -> char = d:$(['0'..='9']) {d.chars().next().unwrap()}
-		rule end_of_ident() = !['0'..='9' | '_' | 'a'..='z' | 'A'..='Z']
-		/// Sequence of digits
-		rule uint_str() -> &'input str = a:$(digit()+ ("_" digit()+)*) { a }
-		/// Number in scientific notation format
-		rule number() -> f64 = quiet!{a:$(uint_str() ("." uint_str())? (['e'|'E'] (s:['+'|'-'])? uint_str())?) {? a.replace("_","").parse().map_err(|_| "<number>") }} / expected!("<number>")
-
-		/// Reserved word followed by any non-alphanumberic
-		rule reserved() = ("assert" / "else" / "error" / "false" / "for" / "function" / "if" / "import" / "importstr" / "importbin" / "in" / "local" / "null" / "tailstrict" / "then" / "self" / "super" / "true") end_of_ident()
-		rule id() -> IStr = v:$(quiet!{ !reserved() alpha() (alpha() / digit())*} / expected!("<identifier>")) { v.into() }
-
-		rule keyword(id: &'static str) -> ()
-			= ##parse_string_literal(id) end_of_ident()
-
-		pub rule param(s: &ParserSettings) -> expr::ExprParam = destruct:destruct(s) expr:(_ "=" _ expr:expr(s){expr})? { expr::ExprParam { destruct, default: expr.map(Rc::new) } }
-		pub rule params(s: &ParserSettings) -> expr::ExprParams
-			= params:param(s) ** comma() comma()? { expr::ExprParams::new(params) }
-			/ { expr::ExprParams::new(Vec::new()) }
-
-		pub rule arg(s: &ParserSettings) -> (Option<IStr>, Rc<Spanned<Expr>>)
-			= name:(quiet! { (s:id() _ "=" !['='] _ {s})? } / expected!("<argument name>")) expr:expr(s) {(name, Rc::new(expr))}
-
-		pub rule args(s: &ParserSettings) -> expr::ArgsDesc
-			= args:arg(s)**comma() comma()? {?
-				let unnamed_count = args.iter().take_while(|(n, _)| n.is_none()).count();
-				let mut unnamed = Vec::with_capacity(unnamed_count);
-				let mut named = Vec::with_capacity(args.len() - unnamed_count);
-				let mut named_started = false;
-				for (name, value) in args {
-					if let Some(name) = name {
-						named_started = true;
-						named.push((name, value));
-					} else {
-						if named_started {
-							return Err("<named argument>")
-						}
-						unnamed.push(value);
-					}
-				}
-				Ok(expr::ArgsDesc::new(unnamed, named))
-			}
-
-		pub rule destruct_rest() -> expr::DestructRest
-			= "..." into:(_ into:id() {into})? {if let Some(into) = into {
-				expr::DestructRest::Keep(into)
-			} else {expr::DestructRest::Drop}}
-		pub rule destruct_array(s: &ParserSettings) -> expr::Destruct
-			= "[" _ start:destruct(s)**comma() rest:(
-				comma() _ rest:destruct_rest()? end:(
-					comma() end:destruct(s)**comma() (_ comma())? {end}
-					/ comma()? {Vec::new()}
-				) {(rest, end)}
-				/ comma()? {(None, Vec::new())}
-			) _ "]" {?
-				#[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Array {
-					start,
-					rest: rest.0,
-					end: rest.1,
-				});
-				#[cfg(not(feature = "exp-destruct"))] Err("!!!experimental destructuring was not enabled")
-			}
-		pub rule destruct_object(s: &ParserSettings) -> expr::Destruct
-			= "{" _
-				fields:(name:id() into:(_ ":" _ into:destruct(s) {into})? default:(_ "=" _ v:expr(s) {v})? {(name, into, default.map(Rc::new))})**comma()
-				rest:(
-					comma() rest:destruct_rest()? {rest}
-					/ comma()? {None}
-				)
-			_ "}" {?
-				#[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Object {
-					fields,
-					rest,
-				});
-				#[cfg(not(feature = "exp-destruct"))] Err("!!!experimental destructuring was not enabled")
-			}
-		pub rule destruct(s: &ParserSettings) -> expr::Destruct
-			= v:id() {expr::Destruct::Full(v)}
-			/ "?" {?
-				#[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Skip);
-				#[cfg(not(feature = "exp-destruct"))] Err("!!!experimental destructuring was not enabled")
-			}
-			/ arr:destruct_array(s) {arr}
-			/ obj:destruct_object(s) {obj}
-
-		pub rule bind(s: &ParserSettings) -> expr::BindSpec
-			= into:destruct(s) _ "=" _ value:expr(s) {expr::BindSpec::Field{into, value: Rc::new(value)}}
-			/ name:id() _ "(" _ params:params(s) _ ")" _ "=" _ value:expr(s) {expr::BindSpec::Function{name, params, value: Rc::new(value)}}
-
-		pub rule assertion(s: &ParserSettings) -> expr::AssertStmt
-			= keyword("assert") _ cond:expr(s) msg:(_ ":" _ e:expr(s) {e})? { expr::AssertStmt(cond, msg) }
-
-		pub rule whole_line() -> &'input str
-			= str:$((!['\n'][_])* "\n") {str}
-		pub rule string_block() -> String
-			= "|||" chomped:"-"? (!['\n']single_whitespace())* "\n"
-			empty_lines:$(['\n']*)
-			prefix:[' ' | '\t']+ first_line:whole_line()
-			lines:("\n" {"\n"} / [' ' | '\t']*<{prefix.len()}> s:whole_line() {s})*
-			[' ' | '\t']*<, {prefix.len() - 1}> "|||"
-			{
-				let mut l = empty_lines.to_owned();
-				l.push_str(first_line);
-				l.extend(lines);
-				if chomped.is_some() {
-					debug_assert!(l.ends_with('\n'));
-					l.truncate(l.len() - 1);
-				}
-				l
-			}
-
-		rule hex_char()
-			= quiet! { ['0'..='9' | 'a'..='f' | 'A'..='F'] } / expected!("<hex char>")
-
-		rule string_char(c: rule<()>)
-			= (!['\\']!c()[_])+
-			/ "\\\\"
-			/ "\\u" hex_char() hex_char() hex_char() hex_char()
-			/ "\\x" hex_char() hex_char()
-			/ ['\\'] (quiet! { ['b' | 'f' | 'n' | 'r' | 't' | '"' | '\''] } / expected!("<escape character>"))
-		pub rule string() -> String
-			= ['"'] str:$(string_char(<"\"">)*) ['"'] {? unescape::unescape(str).ok_or("<escaped string>")}
-			/ ['\''] str:$(string_char(<"\'">)*) ['\''] {? unescape::unescape(str).ok_or("<escaped string>")}
-			/ quiet!{ "@'" str:$(("''" / (!['\''][_]))*) "'" {str.replace("''", "'")}
-			/ "@\"" str:$(("\"\"" / (!['"'][_]))*) "\"" {str.replace("\"\"", "\"")}
-			/ string_block() } / expected!("<string>")
-
-		pub rule field_name(s: &ParserSettings) -> expr::FieldName
-			= name:id() {expr::FieldName::Fixed(name)}
-			/ name:string() {expr::FieldName::Fixed(name.into())}
-			/ "[" _ expr:expr(s) _ "]" {expr::FieldName::Dyn(expr)}
-		pub rule visibility() -> expr::Visibility
-			= ":::" {expr::Visibility::Unhide}
-			/ "::" {expr::Visibility::Hidden}
-			/ ":" {expr::Visibility::Normal}
-		pub rule field(s: &ParserSettings) -> expr::FieldMember
-			= name:field_name(s) _ plus:"+"? _ visibility:visibility() _ value:expr(s) {expr::FieldMember{
-				name,
-				plus: plus.is_some(),
-				params: None,
-				visibility,
-				value: Rc::new(value),
-			}}
-			/ name:field_name(s) _ "(" _ params:params(s) _ ")" _ visibility:visibility() _ value:expr(s) {expr::FieldMember{
-				name,
-				plus: false,
-				params: Some(params),
-				visibility,
-				value: Rc::new(value),
-			}}
-		pub rule obj_local(s: &ParserSettings) -> BindSpec
-			= keyword("local") _ bind:bind(s) {bind}
-		pub rule member(s: &ParserSettings) -> expr::Member
-			= bind:obj_local(s) {expr::Member::BindStmt(bind)}
-			/ assertion:assertion(s) {expr::Member::AssertStmt(assertion)}
-			/ field:field(s) {expr::Member::Field(field)}
-		pub rule objinside(s: &ParserSettings) -> expr::ObjBody
-			= pre_locals:(b: obj_local(s) comma() {b})* &"[" field:field(s) post_locals:(comma() b:obj_local(s) {b})* _ ("," _)? forspec:forspec(s) others:(_ rest:compspec(s) {rest})? {
-				let mut compspecs = vec![CompSpec::ForSpec(forspec)];
-				compspecs.extend(others.unwrap_or_default());
-				let mut locals = pre_locals;
-				locals.extend(post_locals);
-				expr::ObjBody::ObjComp(expr::ObjComp{
-					locals: Rc::new(locals),
-					field: Rc::new(field),
-					compspecs,
-				})
-			}
-			/ members:(member(s) ** comma()) comma()? {
-				let mut locals = Vec::new();
-				let mut asserts = Vec::new();
-				let mut fields = Vec::new();
-				for member in members {
-					match member {
-						Member::Field(field_member) => fields.push(field_member),
-						Member::BindStmt(bind_spec) => locals.push(bind_spec),
-						Member::AssertStmt(assert_stmt) => asserts.push(assert_stmt),
-					}
-				}
-				expr::ObjBody::MemberList(ObjMembers {
-					locals: Rc::new(locals), asserts: Rc::new(asserts), fields
-				})
-			}
-		pub rule ifspec(s: &ParserSettings) -> IfSpecData
-			= keyword("if") _ expr:expr(s) {IfSpecData(expr)}
-		pub rule forspec(s: &ParserSettings) -> ForSpecData
-			= keyword("for") _ id:destruct(s) _ keyword("in") _ cond:expr(s) {ForSpecData(id, cond)}
-		pub rule compspec(s: &ParserSettings) -> Vec<expr::CompSpec>
-			= s:(i:ifspec(s) { expr::CompSpec::IfSpec(i) } / f:forspec(s) {expr::CompSpec::ForSpec(f)} ) ** _ {s}
-		pub rule local_expr(s: &ParserSettings) -> Expr
-			= keyword("local") _ binds:bind(s) ** comma() (_ ",")? _ ";" _ expr:expr(s) { Expr::LocalExpr(binds, Box::new(expr)) }
-		pub rule string_expr(s: &ParserSettings) -> Expr
-			= s:string() {Expr::Str(s.into())}
-		pub rule obj_expr(s: &ParserSettings) -> Expr
-			= "{" _ body:objinside(s) _ "}" {Expr::Obj(body)}
-		pub rule array_expr(s: &ParserSettings) -> Expr
-			= "[" _ elems:(expr(s) ** comma()) _ comma()? "]" {Expr::Arr(Rc::new(elems))}
-		pub rule array_comp_expr(s: &ParserSettings) -> Expr
-			= "[" _ expr:expr(s) _ comma()? _ forspec:forspec(s) _ others:(others: compspec(s) _ {others})? "]" {
-				let mut specs = vec![CompSpec::ForSpec(forspec)];
-				specs.extend(others.unwrap_or_default());
-				Expr::ArrComp(Rc::new(expr), specs)
-			}
-		pub rule number_expr(s: &ParserSettings) -> Expr
-			= n:number() {? if n.is_finite() {
-				Ok(expr::Expr::Num(n))
-			} else {
-				Err("!!!numbers are finite")
-			}}
-		pub rule var_expr(s: &ParserSettings) -> Expr
-			= n:id() { expr::Expr::Var(n) }
-		pub rule id_loc(s: &ParserSettings) -> Spanned<Expr>
-			= a:position!() n:id() b:position!() { Spanned::new(expr::Expr::Str(n), Span(s.source.clone(), a as u32,b as u32)) }
-		pub rule if_then_else_expr(s: &ParserSettings) -> Expr
-			= cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse(Box::new(IfElse{
-				cond,
-				cond_then,
-				cond_else,
-			}))}
-
-		pub rule literal(s: &ParserSettings) -> Expr
-			= v:(
-				keyword("null") {LiteralType::Null}
-				/ keyword("true") {LiteralType::True}
-				/ keyword("false") {LiteralType::False}
-				/ keyword("self") {LiteralType::This}
-				/ keyword("$") {LiteralType::Dollar}
-				/ keyword("super") {LiteralType::Super}
-			) {Expr::Literal(v)}
-
-		rule import_kind() -> ImportKind
-			= keyword("importstr") { ImportKind::Str }
-			/ keyword("importbin") { ImportKind::Bin }
-			/ keyword("import") { ImportKind::Normal }
-
-		pub rule expr_basic(s: &ParserSettings) -> Expr
-			= literal(s)
-
-			/ string_expr(s) / number_expr(s)
-			/ array_expr(s)
-			/ obj_expr(s)
-			/ array_expr(s)
-			/ array_comp_expr(s)
-
-			/ kind:import_kind() _ path:expr(s) {Expr::Import(kind, Box::new(path))}
-
-			/ var_expr(s)
-			/ local_expr(s)
-			/ if_then_else_expr(s)
-
-			/ keyword("function") _ "(" _ params:params(s) _ ")" _ expr:expr(s) {Expr::Function(params, Rc::new(expr))}
-			/ assert:assertion(s) _ ";" _ rest:expr(s) { Expr::AssertExpr(Rc::new(AssertExpr{
-				assert, rest
-			})) }
-
-			/ keyword("error") _ expr:expr(s) { Expr::ErrorStmt(Box::new(expr)) }
-
-		rule slice_part(s: &ParserSettings) -> Option<Spanned<Expr>>
-			= _ e:(e:expr(s) _{e})? {e}
-		pub rule slice_desc(s: &ParserSettings) -> SliceDesc
-			= start:slice_part(s) ":" pair:(end:slice_part(s) step:(":" e:slice_part(s){e})? {(end, step.flatten())})? {
-				let (end, step) = if let Some((end, step)) = pair {
-					(end, step)
-				}else{
-					(None, None)
-				};
-
-				SliceDesc { start, end, step }
-			}
-
-		rule binop(x: rule<()>) -> ()
-			= quiet!{ x() } / expected!("<binary op>")
-		rule unaryop(x: rule<()>) -> ()
-			= quiet!{ x() } / expected!("<unary op>")
-
-		rule ensure_null_coaelse()
-			= "" {?
-				#[cfg(not(feature = "exp-null-coaelse"))] return Err("!!!experimental null coaelscing was not enabled");
-				#[cfg(feature = "exp-null-coaelse")] Ok(())
-			}
-		use BinaryOpType::*;
-		use UnaryOpType::*;
-		rule expr(s: &ParserSettings) -> Spanned<Expr>
-			= precedence! {
-				"(" _ e:expr(s) _ ")" {e}
-				start:position!() v:@ end:position!() { Spanned::new(v, Span(s.source.clone(), start as u32, end as u32)) }
-				--
-				a:(@) _ binop(<"||">) _ b:@ {expr_bin!(a Or b)}
-				a:(@) _ binop(<"??">) _ ensure_null_coaelse() b:@ {
-					#[cfg(feature = "exp-null-coaelse")] return expr_bin!(a NullCoaelse b);
-					unreachable!("ensure_null_coaelse will fail if feature is not enabled")
-				}
-				--
-				a:(@) _ binop(<"&&">) _ b:@ {expr_bin!(a And b)}
-				--
-				a:(@) _ binop(<"|">) _ b:@ {expr_bin!(a BitOr b)}
-				--
-				a:@ _ binop(<"^">) _ b:(@) {expr_bin!(a BitXor b)}
-				--
-				a:(@) _ binop(<"&">) _ b:@ {expr_bin!(a BitAnd b)}
-				--
-				a:(@) _ binop(<"==">) _ b:@ {expr_bin!(a Eq b)}
-				a:(@) _ binop(<"!=">) _ b:@ {expr_bin!(a Neq b)}
-				--
-				a:(@) _ binop(<"<">) _ b:@ {expr_bin!(a Lt b)}
-				a:(@) _ binop(<">">) _ b:@ {expr_bin!(a Gt b)}
-				a:(@) _ binop(<"<=">) _ b:@ {expr_bin!(a Lte b)}
-				a:(@) _ binop(<">=">) _ b:@ {expr_bin!(a Gte b)}
-				a:(@) _ binop(<keyword("in")>) _ b:@ {expr_bin!(a In b)}
-				--
-				a:(@) _ binop(<"<<">) _ b:@ {expr_bin!(a Lhs b)}
-				a:(@) _ binop(<">>">) _ b:@ {expr_bin!(a Rhs b)}
-				--
-				a:(@) _ binop(<"+">) _ b:@ {expr_bin!(a Add b)}
-				a:(@) _ binop(<"-">) _ b:@ {expr_bin!(a Sub b)}
-				--
-				a:(@) _ binop(<"*">) _ b:@ {expr_bin!(a Mul b)}
-				a:(@) _ binop(<"/">) _ b:@ {expr_bin!(a Div b)}
-				a:(@) _ binop(<"%">) _ b:@ {expr_bin!(a Mod b)}
-				--
-						unaryop(<"+">) _ b:@ {expr_un!(Plus b)}
-						unaryop(<"-">) _ b:@ {expr_un!(Minus b)}
-						unaryop(<"!">) _ b:@ {expr_un!(Not b)}
-						unaryop(<"~">) _ b:@ {expr_un!(BitNot b)}
-				--
-				value:(@) _ "[" _ slice:slice_desc(s) _ "]" {Expr::Slice(Box::new(Slice{value, slice}))}
-				indexable:(@) _ parts:index_part(s)+ {Expr::Index{indexable: Box::new(indexable), parts}}
-				a:(@) _ "(" _ args:args(s) _ ")" ts:(_ keyword("tailstrict"))? {Expr::Apply(Box::new(a), args, ts.is_some())}
-				a:(@) _ "{" _ body:objinside(s) _ "}" {Expr::ObjExtend(Rc::new(a), body)}
-				--
-				e:expr_basic(s) {e}
-			}
-		pub rule index_part(s: &ParserSettings) -> IndexPart
-		= n:("?" _ ensure_null_coaelse())? "." _ value:id_loc(s) {IndexPart {
-			value,
-			#[cfg(feature = "exp-null-coaelse")]
-			null_coaelse: n.is_some(),
-		}}
-		/ n:("?" _ "." _ ensure_null_coaelse())? "[" _ value:expr(s) _ "]" {IndexPart {
-			value,
-			#[cfg(feature = "exp-null-coaelse")]
-			null_coaelse: n.is_some(),
-		}}
-
-		pub rule jsonnet(s: &ParserSettings) -> Spanned<Expr> = _ e:expr(s) _ {e}
-	}
-}
-
-pub type ParseError = peg::error::ParseError<peg::str::LineCol>;
-pub fn parse(str: &str, settings: &ParserSettings) -> Result<Spanned<Expr>, ParseError> {
-	jsonnet_parser::jsonnet(str, settings)
-}
-/// Used for importstr values
-pub fn string_to_expr(str: IStr, settings: &ParserSettings) -> Spanned<Expr> {
-	let len = str.len();
-	Spanned::new(Expr::Str(str), Span(settings.source.clone(), 0, len as u32))
-}
-
-#[cfg(test)]
-pub mod tests {
-	use insta::assert_snapshot;
-	use jrsonnet_interner::IStr;
-
-	use super::parse;
-	use crate::{source::Source, ParserSettings};
-
-	fn parsep(s: &str) -> String {
-		let v = parse(
-			s,
-			&ParserSettings {
-				source: Source::new_virtual("<test>".into(), IStr::empty()),
-			},
-		)
-		.unwrap();
-		format!("{v:#?}")
-	}
-
-	macro_rules! parse {
-		($s:expr) => {
-			assert_snapshot!(parsep($s));
-		};
-	}
-
-	#[test]
-	fn multiline_string() {
-		parse!("|||\n    Hello world!\n     a\n|||");
-		parse!("|||\n  Hello world!\n   a\n|||");
-		parse!("|||\n\t\tHello world!\n\t\t\ta\n|||");
-		parse!("|||\n   Hello world!\n    a\n |||");
-	}
-
-	#[test]
-	fn slice() {
-		parse!("a[1:]");
-		parse!("a[1::]");
-		parse!("a[:1:]");
-		parse!("a[::1]");
-		parse!("str[:len - 1]");
-	}
-
-	#[test]
-	fn string_escaping() {
-		parse!(r#""Hello, \"world\"!""#);
-		parse!(r#"'Hello \'world\'!'"#);
-		parse!(r#"'\\\\'"#);
-	}
-
-	#[test]
-	fn string_unescaping() {
-		parse!(r#""Hello\nWorld""#);
-	}
-
-	#[test]
-	fn string_verbantim() {
-		parse!(r#"@"Hello\n""World""""#);
-	}
-
-	#[test]
-	fn imports() {
-		parse!("import \"hello\"");
-		parse!("importstr \"garnish.txt\"");
-		parse!("importbin \"garnish.bin\"");
-	}
-
-	#[test]
-	fn empty_object() {
-		parse!("{}");
-	}
-
-	#[test]
-	fn basic_math() {
-		parse!("2+2*2");
-		parse!("2	+ 	  2	  *	2   	");
-		parse!("2+(2+2*2)");
-		parse!("2//comment\n+//comment\n3/*test*/*/*test*/4");
-	}
-
-	#[test]
-	fn suffix() {
-		parse!("std.test");
-		parse!("std(2)");
-		parse!("std.test(2)");
-		parse!("a[b]");
-	}
-
-	#[test]
-	fn array_comp() {
-		parse!("[std.deepJoin(x) for x in arr]");
-	}
-
-	#[test]
-	fn reserved() {
-		parse!("null");
-		parse!("nulla");
-	}
-
-	#[test]
-	fn multiple_args_buf() {
-		parse!("a(b, null_fields)");
-	}
-
-	#[test]
-	fn infix_precedence() {
-		parse!("!a && !b");
-		parse!("!a / !b");
-	}
-
-	#[test]
-	fn double_negation() {
-		parse!("!!a");
-	}
-
-	#[test]
-	fn array_test_error() {
-		parse!("[a for a in b if c for e in f]");
-	}
-
-	#[test]
-	fn missing_newline_between_comment_and_eof() {
-		parse!(
-			"{a:1}
-
-			//+213"
-		);
-	}
-
-	#[test]
-	fn default_param_before_nondefault() {
-		parse!("local x(foo = 'foo', bar) = null; null");
-	}
-
-	#[test]
-	fn add_location_info_to_all_sub_expressions() {
-		parse!("{} { local x = 1, x: x } + {}");
-	}
-}
deletedcrates/jrsonnet-parser/src/location.rsdiffbeforeafterboth

no changes

deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__add_location_info_to_all_sub_expressions.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__add_location_info_to_all_sub_expressions.snap
+++ /dev/null
@@ -1,57 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"{} { local x = 1, x: x } + {}\")"
----
-BinaryOp(
-    BinaryOp {
-        lhs: ObjExtend(
-            Obj(
-                MemberList(
-                    ObjMembers {
-                        locals: [],
-                        asserts: [],
-                        fields: [],
-                    },
-                ),
-            ) from virtual:<test>:0-2,
-            MemberList(
-                ObjMembers {
-                    locals: [
-                        Field {
-                            into: Full(
-                                "x",
-                            ),
-                            value: Num(
-                                1.0,
-                            ) from virtual:<test>:15-16,
-                        },
-                    ],
-                    asserts: [],
-                    fields: [
-                        FieldMember {
-                            name: Fixed(
-                                "x",
-                            ),
-                            plus: false,
-                            params: None,
-                            visibility: Normal,
-                            value: Var(
-                                "x",
-                            ) from virtual:<test>:21-22,
-                        },
-                    ],
-                },
-            ),
-        ) from virtual:<test>:0-24,
-        op: Add,
-        rhs: Obj(
-            MemberList(
-                ObjMembers {
-                    locals: [],
-                    asserts: [],
-                    fields: [],
-                },
-            ),
-        ) from virtual:<test>:27-29,
-    },
-) from virtual:<test>:0-29
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__array_comp.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__array_comp.snap
+++ /dev/null
@@ -1,41 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"[std.deepJoin(x) for x in arr]\")"
----
-ArrComp(
-    Apply(
-        Index {
-            indexable: Var(
-                "std",
-            ) from virtual:<test>:1-4,
-            parts: [
-                IndexPart {
-                    value: Str(
-                        "deepJoin",
-                    ) from virtual:<test>:5-13,
-                },
-            ],
-        } from virtual:<test>:1-13,
-        ArgsDesc {
-            unnamed: [
-                Var(
-                    "x",
-                ) from virtual:<test>:14-15,
-            ],
-            named: [],
-        },
-        false,
-    ) from virtual:<test>:1-16,
-    [
-        ForSpec(
-            ForSpecData(
-                Full(
-                    "x",
-                ),
-                Var(
-                    "arr",
-                ) from virtual:<test>:26-29,
-            ),
-        ),
-    ],
-) from virtual:<test>:0-30
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__array_test_error.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__array_test_error.snap
+++ /dev/null
@@ -1,38 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"[a for a in b if c for e in f]\")"
----
-ArrComp(
-    Var(
-        "a",
-    ) from virtual:<test>:1-2,
-    [
-        ForSpec(
-            ForSpecData(
-                Full(
-                    "a",
-                ),
-                Var(
-                    "b",
-                ) from virtual:<test>:12-13,
-            ),
-        ),
-        IfSpec(
-            IfSpecData(
-                Var(
-                    "c",
-                ) from virtual:<test>:17-18,
-            ),
-        ),
-        ForSpec(
-            ForSpecData(
-                Full(
-                    "e",
-                ),
-                Var(
-                    "f",
-                ) from virtual:<test>:28-29,
-            ),
-        ),
-    ],
-) from virtual:<test>:0-30
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__basic_math-2.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__basic_math-2.snap
+++ /dev/null
@@ -1,23 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"2\t+ \t  2\t  *\t2   \t\")"
----
-BinaryOp(
-    BinaryOp {
-        lhs: Num(
-            2.0,
-        ) from virtual:<test>:0-1,
-        op: Add,
-        rhs: BinaryOp(
-            BinaryOp {
-                lhs: Num(
-                    2.0,
-                ) from virtual:<test>:7-8,
-                op: Mul,
-                rhs: Num(
-                    2.0,
-                ) from virtual:<test>:13-14,
-            },
-        ) from virtual:<test>:7-14,
-    },
-) from virtual:<test>:0-14
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__basic_math-3.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__basic_math-3.snap
+++ /dev/null
@@ -1,31 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"2+(2+2*2)\")"
----
-BinaryOp(
-    BinaryOp {
-        lhs: Num(
-            2.0,
-        ) from virtual:<test>:0-1,
-        op: Add,
-        rhs: BinaryOp(
-            BinaryOp {
-                lhs: Num(
-                    2.0,
-                ) from virtual:<test>:3-4,
-                op: Add,
-                rhs: BinaryOp(
-                    BinaryOp {
-                        lhs: Num(
-                            2.0,
-                        ) from virtual:<test>:5-6,
-                        op: Mul,
-                        rhs: Num(
-                            2.0,
-                        ) from virtual:<test>:7-8,
-                    },
-                ) from virtual:<test>:5-8,
-            },
-        ) from virtual:<test>:3-8,
-    },
-) from virtual:<test>:0-9
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__basic_math-4.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__basic_math-4.snap
+++ /dev/null
@@ -1,23 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"2//comment\\n+//comment\\n3/*test*/*/*test*/4\")"
----
-BinaryOp(
-    BinaryOp {
-        lhs: Num(
-            2.0,
-        ) from virtual:<test>:0-1,
-        op: Add,
-        rhs: BinaryOp(
-            BinaryOp {
-                lhs: Num(
-                    3.0,
-                ) from virtual:<test>:22-23,
-                op: Mul,
-                rhs: Num(
-                    4.0,
-                ) from virtual:<test>:40-41,
-            },
-        ) from virtual:<test>:22-41,
-    },
-) from virtual:<test>:0-41
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__basic_math.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__basic_math.snap
+++ /dev/null
@@ -1,23 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"2+2*2\")"
----
-BinaryOp(
-    BinaryOp {
-        lhs: Num(
-            2.0,
-        ) from virtual:<test>:0-1,
-        op: Add,
-        rhs: BinaryOp(
-            BinaryOp {
-                lhs: Num(
-                    2.0,
-                ) from virtual:<test>:2-3,
-                op: Mul,
-                rhs: Num(
-                    2.0,
-                ) from virtual:<test>:4-5,
-            },
-        ) from virtual:<test>:2-5,
-    },
-) from virtual:<test>:0-5
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__default_param_before_nondefault.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__default_param_before_nondefault.snap
+++ /dev/null
@@ -1,54 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"local x(foo = 'foo', bar) = null; null\")"
----
-LocalExpr(
-    [
-        Function {
-            name: "x",
-            params: ExprParams {
-                exprs: [
-                    ExprParam {
-                        destruct: Full(
-                            "foo",
-                        ),
-                        default: Some(
-                            Str(
-                                "foo",
-                            ) from virtual:<test>:14-19,
-                        ),
-                    },
-                    ExprParam {
-                        destruct: Full(
-                            "bar",
-                        ),
-                        default: None,
-                    },
-                ],
-                signature: FunctionSignature(
-                    [
-                        ParamParse {
-                            name: Named(
-                                "foo",
-                            ),
-                            default: Exists,
-                        },
-                        ParamParse {
-                            name: Named(
-                                "bar",
-                            ),
-                            default: None,
-                        },
-                    ],
-                ),
-                binds_len: 2,
-            },
-            value: Literal(
-                Null,
-            ) from virtual:<test>:28-32,
-        },
-    ],
-    Literal(
-        Null,
-    ) from virtual:<test>:34-38,
-) from virtual:<test>:0-38
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__double_negation.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__double_negation.snap
+++ /dev/null
@@ -1,13 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"!!a\")"
----
-UnaryOp(
-    Not,
-    UnaryOp(
-        Not,
-        Var(
-            "a",
-        ) from virtual:<test>:2-3,
-    ) from virtual:<test>:1-3,
-) from virtual:<test>:0-3
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__empty_object.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__empty_object.snap
+++ /dev/null
@@ -1,13 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"{}\")"
----
-Obj(
-    MemberList(
-        ObjMembers {
-            locals: [],
-            asserts: [],
-            fields: [],
-        },
-    ),
-) from virtual:<test>:0-2
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__imports-2.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__imports-2.snap
+++ /dev/null
@@ -1,10 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"importstr \\\"garnish.txt\\\"\")"
----
-Import(
-    Str,
-    Str(
-        "garnish.txt",
-    ) from virtual:<test>:10-23,
-) from virtual:<test>:0-23
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__imports-3.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__imports-3.snap
+++ /dev/null
@@ -1,10 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"importbin \\\"garnish.bin\\\"\")"
----
-Import(
-    Bin,
-    Str(
-        "garnish.bin",
-    ) from virtual:<test>:10-23,
-) from virtual:<test>:0-23
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__imports.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__imports.snap
+++ /dev/null
@@ -1,10 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"import \\\"hello\\\"\")"
----
-Import(
-    Normal,
-    Str(
-        "hello",
-    ) from virtual:<test>:7-14,
-) from virtual:<test>:0-14
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__infix_precedence-2.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__infix_precedence-2.snap
+++ /dev/null
@@ -1,21 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"!a / !b\")"
----
-BinaryOp(
-    BinaryOp {
-        lhs: UnaryOp(
-            Not,
-            Var(
-                "a",
-            ) from virtual:<test>:1-2,
-        ) from virtual:<test>:0-2,
-        op: Div,
-        rhs: UnaryOp(
-            Not,
-            Var(
-                "b",
-            ) from virtual:<test>:6-7,
-        ) from virtual:<test>:5-7,
-    },
-) from virtual:<test>:0-7
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__infix_precedence.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__infix_precedence.snap
+++ /dev/null
@@ -1,21 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"!a && !b\")"
----
-BinaryOp(
-    BinaryOp {
-        lhs: UnaryOp(
-            Not,
-            Var(
-                "a",
-            ) from virtual:<test>:1-2,
-        ) from virtual:<test>:0-2,
-        op: And,
-        rhs: UnaryOp(
-            Not,
-            Var(
-                "b",
-            ) from virtual:<test>:7-8,
-        ) from virtual:<test>:6-8,
-    },
-) from virtual:<test>:0-8
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__missing_newline_between_comment_and_eof.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__missing_newline_between_comment_and_eof.snap
+++ /dev/null
@@ -1,25 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"{a:1}\n\n\t\t\t//+213\")"
----
-Obj(
-    MemberList(
-        ObjMembers {
-            locals: [],
-            asserts: [],
-            fields: [
-                FieldMember {
-                    name: Fixed(
-                        "a",
-                    ),
-                    plus: false,
-                    params: None,
-                    visibility: Normal,
-                    value: Num(
-                        1.0,
-                    ) from virtual:<test>:3-4,
-                },
-            ],
-        },
-    ),
-) from virtual:<test>:0-5
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__multiline_string-2.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__multiline_string-2.snap
+++ /dev/null
@@ -1,7 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"|||\\n  Hello world!\\n   a\\n|||\")"
----
-Str(
-    "Hello world!\n a\n",
-) from virtual:<test>:0-27
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__multiline_string-3.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__multiline_string-3.snap
+++ /dev/null
@@ -1,7 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"|||\\n\\t\\tHello world!\\n\\t\\t\\ta\\n|||\")"
----
-Str(
-    "Hello world!\n\ta\n",
-) from virtual:<test>:0-27
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__multiline_string-4.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__multiline_string-4.snap
+++ /dev/null
@@ -1,7 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"|||\\n   Hello world!\\n    a\\n |||\")"
----
-Str(
-    "Hello world!\n a\n",
-) from virtual:<test>:0-30
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__multiline_string.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__multiline_string.snap
+++ /dev/null
@@ -1,7 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"|||\\n    Hello world!\\n     a\\n|||\")"
----
-Str(
-    "Hello world!\n a\n",
-) from virtual:<test>:0-31
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__multiple_args_buf.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__multiple_args_buf.snap
+++ /dev/null
@@ -1,21 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"a(b, null_fields)\")"
----
-Apply(
-    Var(
-        "a",
-    ) from virtual:<test>:0-1,
-    ArgsDesc {
-        unnamed: [
-            Var(
-                "b",
-            ) from virtual:<test>:2-3,
-            Var(
-                "null_fields",
-            ) from virtual:<test>:5-16,
-        ],
-        named: [],
-    },
-    false,
-) from virtual:<test>:0-17
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__reserved-2.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__reserved-2.snap
+++ /dev/null
@@ -1,7 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"nulla\")"
----
-Var(
-    "nulla",
-) from virtual:<test>:0-5
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__reserved.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__reserved.snap
+++ /dev/null
@@ -1,7 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"null\")"
----
-Literal(
-    Null,
-) from virtual:<test>:0-4
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__slice-2.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__slice-2.snap
+++ /dev/null
@@ -1,20 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"a[1::]\")"
----
-Slice(
-    Slice {
-        value: Var(
-            "a",
-        ) from virtual:<test>:0-1,
-        slice: SliceDesc {
-            start: Some(
-                Num(
-                    1.0,
-                ) from virtual:<test>:2-3,
-            ),
-            end: None,
-            step: None,
-        },
-    },
-) from virtual:<test>:0-6
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__slice-3.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__slice-3.snap
+++ /dev/null
@@ -1,20 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"a[:1:]\")"
----
-Slice(
-    Slice {
-        value: Var(
-            "a",
-        ) from virtual:<test>:0-1,
-        slice: SliceDesc {
-            start: None,
-            end: Some(
-                Num(
-                    1.0,
-                ) from virtual:<test>:3-4,
-            ),
-            step: None,
-        },
-    },
-) from virtual:<test>:0-6
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__slice-4.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__slice-4.snap
+++ /dev/null
@@ -1,20 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"a[::1]\")"
----
-Slice(
-    Slice {
-        value: Var(
-            "a",
-        ) from virtual:<test>:0-1,
-        slice: SliceDesc {
-            start: None,
-            end: None,
-            step: Some(
-                Num(
-                    1.0,
-                ) from virtual:<test>:4-5,
-            ),
-        },
-    },
-) from virtual:<test>:0-6
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__slice-5.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__slice-5.snap
+++ /dev/null
@@ -1,28 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"str[:len - 1]\")"
----
-Slice(
-    Slice {
-        value: Var(
-            "str",
-        ) from virtual:<test>:0-3,
-        slice: SliceDesc {
-            start: None,
-            end: Some(
-                BinaryOp(
-                    BinaryOp {
-                        lhs: Var(
-                            "len",
-                        ) from virtual:<test>:5-8,
-                        op: Sub,
-                        rhs: Num(
-                            1.0,
-                        ) from virtual:<test>:11-12,
-                    },
-                ) from virtual:<test>:5-12,
-            ),
-            step: None,
-        },
-    },
-) from virtual:<test>:0-13
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__slice.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__slice.snap
+++ /dev/null
@@ -1,20 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"a[1:]\")"
----
-Slice(
-    Slice {
-        value: Var(
-            "a",
-        ) from virtual:<test>:0-1,
-        slice: SliceDesc {
-            start: Some(
-                Num(
-                    1.0,
-                ) from virtual:<test>:2-3,
-            ),
-            end: None,
-            step: None,
-        },
-    },
-) from virtual:<test>:0-5
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__string_escaping-2.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__string_escaping-2.snap
+++ /dev/null
@@ -1,7 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(r#\"'Hello \\'world\\'!'\"#)"
----
-Str(
-    "Hello 'world'!",
-) from virtual:<test>:0-18
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__string_escaping-3.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__string_escaping-3.snap
+++ /dev/null
@@ -1,7 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(r#\"'\\\\\\\\'\"#)"
----
-Str(
-    "\\\\",
-) from virtual:<test>:0-6
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__string_escaping.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__string_escaping.snap
+++ /dev/null
@@ -1,7 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(r#\"\"Hello, \\\"world\\\"!\"\"#)"
----
-Str(
-    "Hello, \"world\"!",
-) from virtual:<test>:0-19
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__string_unescaping.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__string_unescaping.snap
+++ /dev/null
@@ -1,7 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(r#\"\"Hello\\nWorld\"\"#)"
----
-Str(
-    "Hello\nWorld",
-) from virtual:<test>:0-14
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__string_verbantim.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__string_verbantim.snap
+++ /dev/null
@@ -1,7 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(r#\"@\"Hello\\n\"\"World\"\"\"\"#)"
----
-Str(
-    "Hello\\n\"World\"",
-) from virtual:<test>:0-19
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__suffix-2.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__suffix-2.snap
+++ /dev/null
@@ -1,18 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"std(2)\")"
----
-Apply(
-    Var(
-        "std",
-    ) from virtual:<test>:0-3,
-    ArgsDesc {
-        unnamed: [
-            Num(
-                2.0,
-            ) from virtual:<test>:4-5,
-        ],
-        named: [],
-    },
-    false,
-) from virtual:<test>:0-6
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__suffix-3.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__suffix-3.snap
+++ /dev/null
@@ -1,27 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"std.test(2)\")"
----
-Apply(
-    Index {
-        indexable: Var(
-            "std",
-        ) from virtual:<test>:0-3,
-        parts: [
-            IndexPart {
-                value: Str(
-                    "test",
-                ) from virtual:<test>:4-8,
-            },
-        ],
-    } from virtual:<test>:0-8,
-    ArgsDesc {
-        unnamed: [
-            Num(
-                2.0,
-            ) from virtual:<test>:9-10,
-        ],
-        named: [],
-    },
-    false,
-) from virtual:<test>:0-11
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__suffix-4.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__suffix-4.snap
+++ /dev/null
@@ -1,16 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"a[b]\")"
----
-Index {
-    indexable: Var(
-        "a",
-    ) from virtual:<test>:0-1,
-    parts: [
-        IndexPart {
-            value: Var(
-                "b",
-            ) from virtual:<test>:2-3,
-        },
-    ],
-} from virtual:<test>:0-4
deletedcrates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__suffix.snapdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__suffix.snap
+++ /dev/null
@@ -1,16 +0,0 @@
----
-source: crates/jrsonnet-parser/src/lib.rs
-expression: "parsep(\"std.test\")"
----
-Index {
-    indexable: Var(
-        "std",
-    ) from virtual:<test>:0-3,
-    parts: [
-        IndexPart {
-            value: Str(
-                "test",
-            ) from virtual:<test>:4-8,
-        },
-    ],
-} from virtual:<test>:0-8
deletedcrates/jrsonnet-parser/src/source.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/source.rs
+++ /dev/null
@@ -1,307 +0,0 @@
-use std::{
-	any::Any,
-	fmt::{self, Debug, Display},
-	hash::{Hash, Hasher},
-	path::{Path, PathBuf},
-	rc::Rc,
-};
-
-use jrsonnet_gcmodule::Acyclic;
-use jrsonnet_interner::{IBytes, IStr};
-
-use crate::location::{location_to_offset, offset_to_location, CodeLocation};
-
-macro_rules! any_ext_methods {
-	($T:ident) => {
-		fn as_any(&self) -> &dyn Any;
-		fn dyn_hash(&self, hasher: &mut dyn Hasher);
-		fn dyn_eq(&self, other: &dyn $T) -> bool;
-		fn dyn_debug(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
-	};
-}
-macro_rules! any_ext_impl {
-	($T:ident) => {
-		fn as_any(&self) -> &dyn Any {
-			self
-		}
-		fn dyn_hash(&self, mut hasher: &mut dyn Hasher) {
-			self.hash(&mut hasher)
-		}
-		fn dyn_eq(&self, other: &dyn $T) -> bool {
-			let Some(other) = other.as_any().downcast_ref::<Self>() else {
-				return false;
-			};
-			let this = <Self as $T>::as_any(self)
-				.downcast_ref::<Self>()
-				.expect("restricted by impl");
-			this == other
-		}
-		fn dyn_debug(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-			<Self as std::fmt::Debug>::fmt(self, fmt)
-		}
-	};
-}
-macro_rules! any_ext {
-	($T:ident) => {
-		impl Hash for dyn $T {
-			fn hash<H: Hasher>(&self, state: &mut H) {
-				self.dyn_hash(state)
-			}
-		}
-		impl PartialEq for dyn $T {
-			fn eq(&self, other: &Self) -> bool {
-				self.dyn_eq(other)
-			}
-		}
-		impl Eq for dyn $T {}
-	};
-}
-pub trait SourcePathT: Acyclic + Debug + Display {
-	/// This method should be checked by resolver before panicking with bad SourcePath input
-	/// if `true` - then resolver may threat this path as default, and default is usally a CWD
-	fn is_default(&self) -> bool;
-	fn path(&self) -> Option<&Path>;
-	any_ext_methods!(SourcePathT);
-}
-any_ext!(SourcePathT);
-
-/// Represents location of a file
-///
-/// Standard CLI only operates using
-/// - [`SourceFile`] - for any file
-/// - [`SourceDirectory`] - for resolution from CWD
-/// - [`SourceVirtual`] - for stdlib/ext-str
-/// - [`SourceFifo`] - for /dev/fd/X (This path may appear with `jrsonnet <(command_that_produces_jsonnet)`)
-///
-/// From all of those, only [`SourceVirtual`] may be constructed manually, any other path kind should be only obtained
-/// from assigned `ImportResolver`
-/// However, you should always check `is_default` method return, as it will return true for any paths, where default
-/// search location is applicable
-///
-/// Resolver may also return custom implementations of this trait, for example it may return http url in case of remotely loaded files
-#[derive(Eq, Clone, Acyclic)]
-pub struct SourcePath(Rc<dyn SourcePathT>);
-impl SourcePath {
-	pub fn new(inner: impl SourcePathT) -> Self {
-		Self(Rc::new(inner))
-	}
-	pub fn downcast_ref<T: SourcePathT>(&self) -> Option<&T> {
-		self.0.as_any().downcast_ref()
-	}
-	pub fn is_default(&self) -> bool {
-		self.0.is_default()
-	}
-	pub fn path(&self) -> Option<&Path> {
-		self.0.path()
-	}
-}
-impl Hash for SourcePath {
-	fn hash<H: Hasher>(&self, state: &mut H) {
-		self.0.hash(state);
-	}
-}
-impl PartialEq for SourcePath {
-	#[allow(clippy::op_ref)]
-	fn eq(&self, other: &Self) -> bool {
-		&*self.0 == &*other.0
-	}
-}
-impl Display for SourcePath {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		write!(f, "{}", self.0)
-	}
-}
-impl Debug for SourcePath {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		write!(f, "{:?}", self.0)
-	}
-}
-impl Default for SourcePath {
-	fn default() -> Self {
-		Self(Rc::new(SourceDefault))
-	}
-}
-
-#[derive(Acyclic, Hash, PartialEq, Eq, Debug)]
-struct SourceDefault;
-impl Display for SourceDefault {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		write!(f, "<default>")
-	}
-}
-impl SourcePathT for SourceDefault {
-	fn is_default(&self) -> bool {
-		true
-	}
-	fn path(&self) -> Option<&Path> {
-		None
-	}
-	any_ext_impl!(SourcePathT);
-}
-
-#[derive(Acyclic, Hash, PartialEq, Eq, Debug)]
-pub struct SourceDefaultIgnoreJpath;
-impl Display for SourceDefaultIgnoreJpath {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		write!(f, "<default (ignoring jpath)>")
-	}
-}
-impl SourcePathT for SourceDefaultIgnoreJpath {
-	fn is_default(&self) -> bool {
-		true
-	}
-	fn path(&self) -> Option<&Path> {
-		None
-	}
-	any_ext_impl!(SourcePathT);
-}
-
-/// Represents path to the file on the disk
-/// Directories shouldn't be put here, as resolution for files differs from resolution for directories:
-///
-/// When `file` is being resolved from `SourceFile(a/b/c)`, it should be resolved to `SourceFile(a/b/file)`,
-/// however if it is being resolved from `SourceDirectory(a/b/c)`, then it should be resolved to `SourceDirectory(a/b/c/file)`
-#[derive(Acyclic, Hash, PartialEq, Eq, Debug)]
-pub struct SourceFile(PathBuf);
-impl SourceFile {
-	pub fn new(path: PathBuf) -> Self {
-		Self(path)
-	}
-	pub fn path(&self) -> &Path {
-		&self.0
-	}
-}
-impl Display for SourceFile {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		write!(f, "{}", self.0.display())
-	}
-}
-impl SourcePathT for SourceFile {
-	fn is_default(&self) -> bool {
-		false
-	}
-	fn path(&self) -> Option<&Path> {
-		Some(&self.0)
-	}
-	any_ext_impl!(SourcePathT);
-}
-
-/// Represents path to the directory on the disk
-///
-/// See also [`SourceFile`]
-#[derive(Acyclic, Hash, PartialEq, Eq, Debug)]
-pub struct SourceDirectory(PathBuf);
-impl SourceDirectory {
-	pub fn new(path: PathBuf) -> Self {
-		Self(path)
-	}
-	pub fn path(&self) -> &Path {
-		&self.0
-	}
-}
-impl Display for SourceDirectory {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		write!(f, "{}", self.0.display())
-	}
-}
-impl SourcePathT for SourceDirectory {
-	fn is_default(&self) -> bool {
-		false
-	}
-	fn path(&self) -> Option<&Path> {
-		Some(&self.0)
-	}
-	any_ext_impl!(SourcePathT);
-}
-
-/// Represents virtual file, whose are located in memory, and shouldn't be cached
-///
-/// It is used for --ext-code=.../--tla-code=.../standard library source code by default,
-/// and user can construct arbitrary values by hand, without asking import resolver
-#[derive(Acyclic, Hash, PartialEq, Eq, Clone)]
-pub struct SourceVirtual(pub IStr);
-impl Display for SourceVirtual {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		write!(f, "virtual:{}", self.0)
-	}
-}
-impl fmt::Debug for SourceVirtual {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		write!(f, "virtual:{}", self.0)
-	}
-}
-impl SourcePathT for SourceVirtual {
-	fn is_default(&self) -> bool {
-		true
-	}
-	fn path(&self) -> Option<&Path> {
-		None
-	}
-	any_ext_impl!(SourcePathT);
-}
-
-/// Represents resolved FIFO file, those files may only be read once, and this type is only used for
-/// unix, where user might want to do `jrsonnet <(command_that_produces_jsonnet_source)`
-/// In most cases, user most probably want to use `jrsonnet -` instead of `jrsonnet /dev/stdin`
-/// for better cross-platform support.
-// PartialEq is limited to ptr equality
-#[allow(clippy::derived_hash_with_manual_eq)]
-#[derive(Acyclic, Debug, Hash)]
-pub struct SourceFifo(pub String, pub IBytes);
-impl PartialEq for SourceFifo {
-	fn eq(&self, other: &Self) -> bool {
-		std::ptr::eq(self, other)
-	}
-}
-impl fmt::Display for SourceFifo {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		write!(f, "fifo({:?})", self.0)
-	}
-}
-impl SourcePathT for SourceFifo {
-	fn is_default(&self) -> bool {
-		// In case of FD input, user won't expect relative paths to be resolved from /dev/fd/
-		true
-	}
-
-	fn path(&self) -> Option<&Path> {
-		None
-	}
-
-	any_ext_impl!(SourcePathT);
-}
-
-/// Either real file, or virtual
-/// Hash of FileName always have same value as raw Path, to make it possible to use with raw_entry_mut
-#[derive(Clone, PartialEq, Eq, Acyclic)]
-pub struct Source(pub Rc<(SourcePath, IStr)>);
-
-impl Source {
-	pub fn new(path: SourcePath, code: IStr) -> Self {
-		Self(Rc::new((path, code)))
-	}
-
-	pub fn new_virtual(name: IStr, code: IStr) -> Self {
-		Self::new(SourcePath::new(SourceVirtual(name)), code)
-	}
-
-	pub fn code(&self) -> &str {
-		&self.0 .1
-	}
-
-	pub fn source_path(&self) -> &SourcePath {
-		&self.0 .0
-	}
-
-	pub fn map_source_locations<const S: usize>(&self, locs: &[u32; S]) -> [CodeLocation; S] {
-		offset_to_location(&self.0 .1, locs)
-	}
-	pub fn map_from_source_location(&self, line: usize, column: usize) -> Option<usize> {
-		location_to_offset(&self.0 .1, line, column)
-	}
-}
-impl fmt::Debug for Source {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		write!(f, "{:?}", self.0 .0)
-	}
-}
deletedcrates/jrsonnet-parser/src/unescape.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/unescape.rs
+++ /dev/null
@@ -1,55 +0,0 @@
-use std::str::Chars;
-
-fn decode_unicode(chars: &mut Chars) -> Option<u16> {
-	IntoIterator::into_iter([chars.next()?, chars.next()?, chars.next()?, chars.next()?])
-		.map(|c| c.to_digit(16).map(|f| f as u16))
-		.try_fold(0u16, |acc, v| Some((acc << 4) | (v?)))
-}
-
-pub fn unescape(s: &str) -> Option<String> {
-	let mut chars = s.chars();
-	let mut out = String::with_capacity(s.len());
-
-	while let Some(c) = chars.next() {
-		if c != '\\' {
-			out.push(c);
-			continue;
-		}
-		match chars.next()? {
-			c @ ('\\' | '"' | '\'') => out.push(c),
-			'b' => out.push('\u{0008}'),
-			'f' => out.push('\u{000c}'),
-			'n' => out.push('\n'),
-			'r' => out.push('\r'),
-			't' => out.push('\t'),
-			'u' => match decode_unicode(&mut chars)? {
-				// May only be second byte
-				0xDC00..=0xDFFF => return None,
-				// Surrogate pair
-				n1 @ 0xD800..=0xDBFF => {
-					if chars.next() != Some('\\') {
-						return None;
-					}
-					if chars.next() != Some('u') {
-						return None;
-					}
-					let n2 = decode_unicode(&mut chars)?;
-					if !matches!(n2, 0xDC00..=0xDFFF) {
-						return None;
-					}
-					let n = (((n1 - 0xD800) as u32) << 10 | (n2 - 0xDC00) as u32) + 0x1_0000;
-					out.push(char::from_u32(n)?);
-				}
-				n => out.push(char::from_u32(n as u32)?),
-			},
-			'x' => {
-				let c = IntoIterator::into_iter([chars.next()?, chars.next()?])
-					.map(|c| c.to_digit(16))
-					.try_fold(0u32, |acc, v| Some((acc << 8) | (v?)))?;
-				out.push(char::from_u32(c)?)
-			}
-			_ => return None,
-		}
-	}
-	Some(out)
-}
addedcrates/jrsonnet-peg-parser/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "jrsonnet-peg-parser"
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+version.workspace = true
+
+[dependencies]
+jrsonnet-ir.workspace = true
+peg.workspace = true
+
+[lints]
+workspace = true
+
+[dev-dependencies]
+insta.workspace = true
+
+[features]
+default = []
+exp-destruct = ["jrsonnet-ir/exp-destruct"]
+exp-null-coaelse = ["jrsonnet-ir/exp-null-coaelse"]
addedcrates/jrsonnet-peg-parser/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/src/lib.rs
@@ -0,0 +1,556 @@
+use jrsonnet_ir::{
+	BinaryOp, Expr, ExprParams, IStr, IndexPart, Member, Slice, SliceDesc, Source, Span, Spanned,
+	ExprParam, ArgsDesc, AssertExpr, ImportKind, LiteralType, IfElse, CompSpec, ForSpecData, IfSpecData, ObjMembers, ObjBody,
+	ObjComp, FieldMember, Visibility, FieldName, unescape, AssertStmt, BindSpec, Destruct, DestructRest,
+};
+use peg::parser;
+use std::rc::Rc;
+
+pub struct ParserSettings {
+	pub source: Source,
+}
+
+macro_rules! expr_bin {
+	($a:ident $op:ident $b:ident) => {
+		Expr::BinaryOp(Box::new(BinaryOp {
+			lhs: $a,
+			op: $op,
+			rhs: $b,
+		}))
+	};
+}
+macro_rules! expr_un {
+	($op:ident $a:ident) => {
+		Expr::UnaryOp($op, Box::new($a))
+	};
+}
+
+parser! {
+	grammar jsonnet_parser() for str {
+		use peg::ParseLiteral;
+
+		rule eof() = quiet!{![_]} / expected!("<eof>")
+		rule eol() = "\n" / eof()
+
+		/// Standard C-like comments
+		rule comment()
+			= "//" (!eol()[_])* eol()
+			/ "/*" (!("*/")[_])* "*/"
+			/ "#" (!eol()[_])* eol()
+
+		rule single_whitespace() = quiet!{([' ' | '\r' | '\n' | '\t'] / comment())} / expected!("<whitespace>")
+		rule _() = quiet!{([' ' | '\r' | '\n' | '\t']+) / comment()}* / expected!("<whitespace>")
+
+		/// For comma-delimited elements
+		rule comma() = quiet!{_ "," _} / expected!("<comma>")
+		rule alpha() -> char = c:$(['_' | 'a'..='z' | 'A'..='Z']) {c.chars().next().unwrap()}
+		rule digit() -> char = d:$(['0'..='9']) {d.chars().next().unwrap()}
+		rule end_of_ident() = !['0'..='9' | '_' | 'a'..='z' | 'A'..='Z']
+		/// Sequence of digits
+		rule uint_str() -> &'input str = a:$(digit()+ ("_" digit()+)*) { a }
+		/// Number in scientific notation format
+		rule number() -> f64 = quiet!{a:$(uint_str() ("." uint_str())? (['e'|'E'] (s:['+'|'-'])? uint_str())?) {? a.replace("_","").parse().map_err(|_| "<number>") }} / expected!("<number>")
+
+		/// Reserved word followed by any non-alphanumberic
+		rule reserved() = ("assert" / "else" / "error" / "false" / "for" / "function" / "if" / "import" / "importstr" / "importbin" / "in" / "local" / "null" / "tailstrict" / "then" / "self" / "super" / "true") end_of_ident()
+		rule id() -> IStr = v:$(quiet!{ !reserved() alpha() (alpha() / digit())*} / expected!("<identifier>")) { v.into() }
+
+		rule keyword(id: &'static str) -> ()
+			= ##parse_string_literal(id) end_of_ident()
+
+		pub rule param(s: &ParserSettings) -> ExprParam = destruct:destruct(s) expr:(_ "=" _ expr:expr(s){expr})? { ExprParam { destruct, default: expr.map(Rc::new) } }
+		pub rule params(s: &ParserSettings) -> ExprParams
+			= params:param(s) ** comma() comma()? { ExprParams::new(params) }
+			/ { ExprParams::new(Vec::new()) }
+
+		pub rule arg(s: &ParserSettings) -> (Option<IStr>, Rc<Spanned<Expr>>)
+			= name:(quiet! { (s:id() _ "=" !['='] _ {s})? } / expected!("<argument name>")) expr:expr(s) {(name, Rc::new(expr))}
+
+		pub rule args(s: &ParserSettings) -> ArgsDesc
+			= args:arg(s)**comma() comma()? {?
+				let unnamed_count = args.iter().take_while(|(n, _)| n.is_none()).count();
+				let mut unnamed = Vec::with_capacity(unnamed_count);
+				let mut named = Vec::with_capacity(args.len() - unnamed_count);
+				let mut named_started = false;
+				for (name, value) in args {
+					if let Some(name) = name {
+						named_started = true;
+						named.push((name, value));
+					} else {
+						if named_started {
+							return Err("<named argument>")
+						}
+						unnamed.push(value);
+					}
+				}
+				Ok(ArgsDesc::new(unnamed, named))
+			}
+
+		pub rule destruct_rest() -> DestructRest
+			= "..." into:(_ into:id() {into})? {if let Some(into) = into {
+				DestructRest::Keep(into)
+			} else {DestructRest::Drop}}
+		pub rule destruct_array(s: &ParserSettings) -> Destruct
+			= "[" _ start:destruct(s)**comma() rest:(
+				comma() _ rest:destruct_rest()? end:(
+					comma() end:destruct(s)**comma() (_ comma())? {end}
+					/ comma()? {Vec::new()}
+				) {(rest, end)}
+				/ comma()? {(None, Vec::new())}
+			) _ "]" {?
+				#[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Array {
+					start,
+					rest: rest.0,
+					end: rest.1,
+				});
+				#[cfg(not(feature = "exp-destruct"))] Err("!!!experimental destructuring was not enabled")
+			}
+		pub rule destruct_object(s: &ParserSettings) -> Destruct
+			= "{" _
+				fields:(name:id() into:(_ ":" _ into:destruct(s) {into})? default:(_ "=" _ v:expr(s) {v})? {(name, into, default.map(Rc::new))})**comma()
+				rest:(
+					comma() rest:destruct_rest()? {rest}
+					/ comma()? {None}
+				)
+			_ "}" {?
+				#[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Object {
+					fields,
+					rest,
+				});
+				#[cfg(not(feature = "exp-destruct"))] Err("!!!experimental destructuring was not enabled")
+			}
+		pub rule destruct(s: &ParserSettings) -> Destruct
+			= v:id() {Destruct::Full(v)}
+			/ "?" {?
+				#[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Skip);
+				#[cfg(not(feature = "exp-destruct"))] Err("!!!experimental destructuring was not enabled")
+			}
+			/ arr:destruct_array(s) {arr}
+			/ obj:destruct_object(s) {obj}
+
+		pub rule bind(s: &ParserSettings) -> BindSpec
+			= into:destruct(s) _ "=" _ value:expr(s) {BindSpec::Field{into, value: Rc::new(value)}}
+			/ name:id() _ "(" _ params:params(s) _ ")" _ "=" _ value:expr(s) {BindSpec::Function{name, params, value: Rc::new(value)}}
+
+		pub rule assertion(s: &ParserSettings) -> AssertStmt
+			= keyword("assert") _ cond:expr(s) msg:(_ ":" _ e:expr(s) {e})? { AssertStmt(cond, msg) }
+
+		pub rule whole_line() -> &'input str
+			= str:$((!['\n'][_])* "\n") {str}
+		pub rule string_block() -> String
+			= "|||" chomped:"-"? (!['\n']single_whitespace())* "\n"
+			empty_lines:$(['\n']*)
+			prefix:[' ' | '\t']+ first_line:whole_line()
+			lines:("\n" {"\n"} / [' ' | '\t']*<{prefix.len()}> s:whole_line() {s})*
+			[' ' | '\t']*<, {prefix.len() - 1}> "|||"
+			{
+				let mut l = empty_lines.to_owned();
+				l.push_str(first_line);
+				l.extend(lines);
+				if chomped.is_some() {
+					debug_assert!(l.ends_with('\n'));
+					l.truncate(l.len() - 1);
+				}
+				l
+			}
+
+		rule hex_char()
+			= quiet! { ['0'..='9' | 'a'..='f' | 'A'..='F'] } / expected!("<hex char>")
+
+		rule string_char(c: rule<()>)
+			= (!['\\']!c()[_])+
+			/ "\\\\"
+			/ "\\u" hex_char() hex_char() hex_char() hex_char()
+			/ "\\x" hex_char() hex_char()
+			/ ['\\'] (quiet! { ['b' | 'f' | 'n' | 'r' | 't' | '"' | '\''] } / expected!("<escape character>"))
+		pub rule string() -> String
+			= ['"'] str:$(string_char(<"\"">)*) ['"'] {? unescape::unescape(str).ok_or("<escaped string>")}
+			/ ['\''] str:$(string_char(<"\'">)*) ['\''] {? unescape::unescape(str).ok_or("<escaped string>")}
+			/ quiet!{ "@'" str:$(("''" / (!['\''][_]))*) "'" {str.replace("''", "'")}
+			/ "@\"" str:$(("\"\"" / (!['"'][_]))*) "\"" {str.replace("\"\"", "\"")}
+			/ string_block() } / expected!("<string>")
+
+		pub rule field_name(s: &ParserSettings) -> FieldName
+			= name:id() {FieldName::Fixed(name)}
+			/ name:string() {FieldName::Fixed(name.into())}
+			/ "[" _ expr:expr(s) _ "]" {FieldName::Dyn(expr)}
+		pub rule visibility() -> Visibility
+			= ":::" {Visibility::Unhide}
+			/ "::" {Visibility::Hidden}
+			/ ":" {Visibility::Normal}
+		pub rule field(s: &ParserSettings) -> FieldMember
+			= name:field_name(s) _ plus:"+"? _ visibility:visibility() _ value:expr(s) {FieldMember{
+				name,
+				plus: plus.is_some(),
+				params: None,
+				visibility,
+				value: Rc::new(value),
+			}}
+			/ name:field_name(s) _ "(" _ params:params(s) _ ")" _ visibility:visibility() _ value:expr(s) {FieldMember{
+				name,
+				plus: false,
+				params: Some(params),
+				visibility,
+				value: Rc::new(value),
+			}}
+		pub rule obj_local(s: &ParserSettings) -> BindSpec
+			= keyword("local") _ bind:bind(s) {bind}
+		pub rule member(s: &ParserSettings) -> Member
+			= bind:obj_local(s) {Member::BindStmt(bind)}
+			/ assertion:assertion(s) {Member::AssertStmt(assertion)}
+			/ field:field(s) {Member::Field(field)}
+		pub rule objinside(s: &ParserSettings) -> ObjBody
+			=  members:(member(s) ** comma()) comma()? _ compspecs:compspecs(s)? {?
+				Ok(if let Some(compspecs) = compspecs {
+					let mut locals = Vec::new();
+					let mut field = None;
+					for member in members {
+						match member {
+							Member::Field(field_member) => if field.replace(field_member).is_some() {
+								return Err("<object comprehension can only contain one field>")
+							},
+							Member::BindStmt(bind_spec) => locals.push(bind_spec),
+							Member::AssertStmt(assert_stmt) => return Err("<asserts are unsupported in object comprehension>"),
+						}
+					}
+					ObjBody::ObjComp(ObjComp {
+						locals: Rc::new(locals),
+						field: field.map(Rc::new).ok_or("<missing object comprehension field>")?,
+						compspecs
+					})
+				} else {
+					let mut locals = Vec::new();
+					let mut asserts = Vec::new();
+					let mut fields = Vec::new();
+					for member in members {
+						match member {
+							Member::Field(field_member) => fields.push(field_member),
+							Member::BindStmt(bind_spec) => locals.push(bind_spec),
+							Member::AssertStmt(assert_stmt) => asserts.push(assert_stmt),
+						}
+					}
+					ObjBody::MemberList(ObjMembers {
+						locals: Rc::new(locals),
+						asserts: Rc::new(asserts),
+						fields
+					})
+				})
+			}
+		pub rule ifspec(s: &ParserSettings) -> IfSpecData
+			= keyword("if") _ expr:expr(s) {IfSpecData(expr)}
+		pub rule forspec(s: &ParserSettings) -> ForSpecData
+			= keyword("for") _ id:destruct(s) _ keyword("in") _ cond:expr(s) {ForSpecData(id, cond)}
+		rule compspec(s: &ParserSettings) -> CompSpec
+			= i:ifspec(s) { CompSpec::IfSpec(i) } / f:forspec(s) {CompSpec::ForSpec(f)}
+		pub rule compspecs(s: &ParserSettings) -> Vec<CompSpec>
+			= specs:compspec(s) ++ _ {?
+				if !matches!(specs[0], CompSpec::ForSpec(_)) {
+					return Err("<first compspec should be for>")
+				}
+				Ok(specs)
+			}
+		pub rule local_expr(s: &ParserSettings) -> Expr
+			= keyword("local") _ binds:bind(s) ** comma() (_ ",")? _ ";" _ expr:expr(s) { Expr::LocalExpr(binds, Box::new(expr)) }
+		pub rule string_expr(s: &ParserSettings) -> Expr
+			= s:string() {Expr::Str(s.into())}
+		pub rule obj_expr(s: &ParserSettings) -> Expr
+			= "{" _ body:objinside(s) _ "}" {Expr::Obj(body)}
+		pub rule array_expr(s: &ParserSettings) -> Expr
+			= "[" _ elems:(expr(s) ** comma()) _ comma()? "]" {Expr::Arr(Rc::new(elems))}
+		pub rule array_comp_expr(s: &ParserSettings) -> Expr
+			= "[" _ expr:expr(s) _ comma()? _ specs:(r: compspecs(s) _ {r}) "]" {
+				Expr::ArrComp(Rc::new(expr), specs)
+			}
+		pub rule number_expr(s: &ParserSettings) -> Expr
+			= n:number() {? if n.is_finite() {
+				Ok(Expr::Num(n))
+			} else {
+				Err("!!!numbers are finite")
+			}}
+		pub rule var_expr(s: &ParserSettings) -> Expr
+			= n:id() { Expr::Var(n) }
+		pub rule id_loc(s: &ParserSettings) -> Spanned<Expr>
+			= a:position!() n:id() b:position!() { Spanned::new(Expr::Str(n), Span(s.source.clone(), a as u32,b as u32)) }
+		pub rule if_then_else_expr(s: &ParserSettings) -> Expr
+			= cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse(Box::new(IfElse{
+				cond,
+				cond_then,
+				cond_else,
+			}))}
+
+		pub rule literal(s: &ParserSettings) -> Expr
+			= v:(
+				keyword("null") {LiteralType::Null}
+				/ keyword("true") {LiteralType::True}
+				/ keyword("false") {LiteralType::False}
+				/ keyword("self") {LiteralType::This}
+				/ keyword("$") {LiteralType::Dollar}
+				/ keyword("super") {LiteralType::Super}
+			) {Expr::Literal(v)}
+
+		rule import_kind() -> ImportKind
+			= keyword("importstr") { ImportKind::Str }
+			/ keyword("importbin") { ImportKind::Bin }
+			/ keyword("import") { ImportKind::Normal }
+
+		pub rule expr_basic(s: &ParserSettings) -> Expr
+			= literal(s)
+
+			/ string_expr(s) / number_expr(s)
+			/ array_expr(s)
+			/ obj_expr(s)
+			/ array_expr(s)
+			/ array_comp_expr(s)
+
+			/ kind:import_kind() _ path:expr(s) {Expr::Import(kind, Box::new(path))}
+
+			/ var_expr(s)
+			/ local_expr(s)
+			/ if_then_else_expr(s)
+
+			/ keyword("function") _ "(" _ params:params(s) _ ")" _ expr:expr(s) {Expr::Function(params, Rc::new(expr))}
+			/ assert:assertion(s) _ ";" _ rest:expr(s) { Expr::AssertExpr(Rc::new(AssertExpr{
+				assert, rest
+			})) }
+
+			/ keyword("error") _ expr:expr(s) { Expr::ErrorStmt(Box::new(expr)) }
+
+		rule slice_part(s: &ParserSettings) -> Option<Spanned<Expr>>
+			= _ e:(e:expr(s) _{e})? {e}
+		pub rule slice_desc(s: &ParserSettings) -> SliceDesc
+			= start:slice_part(s) ":" pair:(end:slice_part(s) step:(":" e:slice_part(s){e})? {(end, step.flatten())})? {
+				let (end, step) = if let Some((end, step)) = pair {
+					(end, step)
+				}else{
+					(None, None)
+				};
+
+				SliceDesc { start, end, step }
+			}
+
+		rule binop(x: rule<()>) -> ()
+			= quiet!{ x() } / expected!("<binary op>")
+		rule unaryop(x: rule<()>) -> ()
+			= quiet!{ x() } / expected!("<unary op>")
+
+		rule ensure_null_coaelse()
+			= "" {?
+				#[cfg(not(feature = "exp-null-coaelse"))] return Err("!!!experimental null coaelscing was not enabled");
+				#[cfg(feature = "exp-null-coaelse")] Ok(())
+			}
+		use jrsonnet_ir::BinaryOpType::*;
+		use jrsonnet_ir::UnaryOpType::*;
+		rule expr(s: &ParserSettings) -> Spanned<Expr>
+			= precedence! {
+				"(" _ e:expr(s) _ ")" {e}
+				start:position!() v:@ end:position!() { Spanned::new(v, Span(s.source.clone(), start as u32, end as u32)) }
+				--
+				a:(@) _ binop(<"||">) _ b:@ {expr_bin!(a Or b)}
+				a:(@) _ binop(<"??">) _ ensure_null_coaelse() b:@ {
+					#[cfg(feature = "exp-null-coaelse")] return expr_bin!(a NullCoaelse b);
+					unreachable!("ensure_null_coaelse will fail if feature is not enabled")
+				}
+				--
+				a:(@) _ binop(<"&&">) _ b:@ {expr_bin!(a And b)}
+				--
+				a:(@) _ binop(<"|">) _ b:@ {expr_bin!(a BitOr b)}
+				--
+				a:@ _ binop(<"^">) _ b:(@) {expr_bin!(a BitXor b)}
+				--
+				a:(@) _ binop(<"&">) _ b:@ {expr_bin!(a BitAnd b)}
+				--
+				a:(@) _ binop(<"==">) _ b:@ {expr_bin!(a Eq b)}
+				a:(@) _ binop(<"!=">) _ b:@ {expr_bin!(a Neq b)}
+				--
+				a:(@) _ binop(<"<">) _ b:@ {expr_bin!(a Lt b)}
+				a:(@) _ binop(<">">) _ b:@ {expr_bin!(a Gt b)}
+				a:(@) _ binop(<"<=">) _ b:@ {expr_bin!(a Lte b)}
+				a:(@) _ binop(<">=">) _ b:@ {expr_bin!(a Gte b)}
+				a:(@) _ binop(<keyword("in")>) _ b:@ {expr_bin!(a In b)}
+				--
+				a:(@) _ binop(<"<<">) _ b:@ {expr_bin!(a Lhs b)}
+				a:(@) _ binop(<">>">) _ b:@ {expr_bin!(a Rhs b)}
+				--
+				a:(@) _ binop(<"+">) _ b:@ {expr_bin!(a Add b)}
+				a:(@) _ binop(<"-">) _ b:@ {expr_bin!(a Sub b)}
+				--
+				a:(@) _ binop(<"*">) _ b:@ {expr_bin!(a Mul b)}
+				a:(@) _ binop(<"/">) _ b:@ {expr_bin!(a Div b)}
+				a:(@) _ binop(<"%">) _ b:@ {expr_bin!(a Mod b)}
+				--
+						unaryop(<"+">) _ b:@ {expr_un!(Plus b)}
+						unaryop(<"-">) _ b:@ {expr_un!(Minus b)}
+						unaryop(<"!">) _ b:@ {expr_un!(Not b)}
+						unaryop(<"~">) _ b:@ {expr_un!(BitNot b)}
+				--
+				value:(@) _ "[" _ slice:slice_desc(s) _ "]" {Expr::Slice(Box::new(Slice{value, slice}))}
+				indexable:(@) _ parts:index_part(s)+ {Expr::Index{indexable: Box::new(indexable), parts}}
+				a:(@) _ "(" _ args:args(s) _ ")" ts:(_ keyword("tailstrict"))? {Expr::Apply(Box::new(a), args, ts.is_some())}
+				a:(@) _ "{" _ body:objinside(s) _ "}" {Expr::ObjExtend(Rc::new(a), body)}
+				--
+				e:expr_basic(s) {e}
+			}
+		pub rule index_part(s: &ParserSettings) -> IndexPart
+		= n:("?" _ ensure_null_coaelse())? "." _ value:id_loc(s) {IndexPart {
+			value,
+			#[cfg(feature = "exp-null-coaelse")]
+			null_coaelse: n.is_some(),
+		}}
+		/ n:("?" _ "." _ ensure_null_coaelse())? "[" _ value:expr(s) _ "]" {IndexPart {
+			value,
+			#[cfg(feature = "exp-null-coaelse")]
+			null_coaelse: n.is_some(),
+		}}
+
+		pub rule jsonnet(s: &ParserSettings) -> Spanned<Expr> = _ e:expr(s) _ {e}
+	}
+}
+
+pub type ParseError = peg::error::ParseError<peg::str::LineCol>;
+pub fn parse(str: &str, settings: &ParserSettings) -> Result<Spanned<Expr>, ParseError> {
+	jsonnet_parser::jsonnet(str, settings)
+}
+/// Used for importstr values
+pub fn string_to_expr(str: IStr, settings: &ParserSettings) -> Spanned<Expr> {
+	let len = str.len();
+	Spanned::new(Expr::Str(str), Span(settings.source.clone(), 0, len as u32))
+}
+
+#[cfg(test)]
+pub mod tests {
+	use insta::assert_snapshot;
+	use jrsonnet_ir::{IStr, Source};
+
+	use super::parse;
+	use crate::ParserSettings;
+
+	fn parsep(s: &str) -> String {
+		let v = parse(
+			s,
+			&ParserSettings {
+				source: Source::new_virtual("<test>".into(), IStr::empty()),
+			},
+		)
+		.unwrap();
+		format!("{v:#?}")
+	}
+
+	macro_rules! parse {
+		($s:expr) => {
+			assert_snapshot!(parsep($s));
+		};
+	}
+
+	#[test]
+	fn multiline_string() {
+		parse!("|||\n    Hello world!\n     a\n|||");
+		parse!("|||\n  Hello world!\n   a\n|||");
+		parse!("|||\n\t\tHello world!\n\t\t\ta\n|||");
+		parse!("|||\n   Hello world!\n    a\n |||");
+	}
+
+	#[test]
+	fn slice() {
+		parse!("a[1:]");
+		parse!("a[1::]");
+		parse!("a[:1:]");
+		parse!("a[::1]");
+		parse!("str[:len - 1]");
+	}
+
+	#[test]
+	fn string_escaping() {
+		parse!(r#""Hello, \"world\"!""#);
+		parse!(r#"'Hello \'world\'!'"#);
+		parse!(r#"'\\\\'"#);
+	}
+
+	#[test]
+	fn string_unescaping() {
+		parse!(r#""Hello\nWorld""#);
+	}
+
+	#[test]
+	fn string_verbantim() {
+		parse!(r#"@"Hello\n""World""""#);
+	}
+
+	#[test]
+	fn imports() {
+		parse!("import \"hello\"");
+		parse!("importstr \"garnish.txt\"");
+		parse!("importbin \"garnish.bin\"");
+	}
+
+	#[test]
+	fn empty_object() {
+		parse!("{}");
+	}
+
+	#[test]
+	fn basic_math() {
+		parse!("2+2*2");
+		parse!("2	+ 	  2	  *	2   	");
+		parse!("2+(2+2*2)");
+		parse!("2//comment\n+//comment\n3/*test*/*/*test*/4");
+	}
+
+	#[test]
+	fn suffix() {
+		parse!("std.test");
+		parse!("std(2)");
+		parse!("std.test(2)");
+		parse!("a[b]");
+	}
+
+	#[test]
+	fn array_comp() {
+		parse!("[std.deepJoin(x) for x in arr]");
+	}
+
+	#[test]
+	fn reserved() {
+		parse!("null");
+		parse!("nulla");
+	}
+
+	#[test]
+	fn multiple_args_buf() {
+		parse!("a(b, null_fields)");
+	}
+
+	#[test]
+	fn infix_precedence() {
+		parse!("!a && !b");
+		parse!("!a / !b");
+	}
+
+	#[test]
+	fn double_negation() {
+		parse!("!!a");
+	}
+
+	#[test]
+	fn array_test_error() {
+		parse!("[a for a in b if c for e in f]");
+	}
+
+	#[test]
+	fn missing_newline_between_comment_and_eof() {
+		parse!(
+			"{a:1}
+
+			//+213"
+		);
+	}
+
+	#[test]
+	fn default_param_before_nondefault() {
+		parse!("local x(foo = 'foo', bar) = null; null");
+	}
+
+	#[test]
+	fn add_location_info_to_all_sub_expressions() {
+		parse!("{} { local x = 1, x: x } + {}");
+	}
+}
addedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__add_location_info_to_all_sub_expressions.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__add_location_info_to_all_sub_expressions.snap
@@ -0,0 +1,57 @@
+---
+source: crates/jrsonnet-peg-parser/src/lib.rs
+expression: "parsep(\"{} { local x = 1, x: x } + {}\")"
+---
+BinaryOp(
+    BinaryOp {
+        lhs: ObjExtend(
+            Obj(
+                MemberList(
+                    ObjMembers {
+                        locals: [],
+                        asserts: [],
+                        fields: [],
+                    },
+                ),
+            ) from virtual:<test>:0-2,
+            MemberList(
+                ObjMembers {
+                    locals: [
+                        Field {
+                            into: Full(
+                                "x",
+                            ),
+                            value: Num(
+                                1.0,
+                            ) from virtual:<test>:15-16,
+                        },
+                    ],
+                    asserts: [],
+                    fields: [
+                        FieldMember {
+                            name: Fixed(
+                                "x",
+                            ),
+                            plus: false,
+                            params: None,
+                            visibility: Normal,
+                            value: Var(
+                                "x",
+                            ) from virtual:<test>:21-22,
+                        },
+                    ],
+                },
+            ),
+        ) from virtual:<test>:0-24,
+        op: Add,
+        rhs: Obj(
+            MemberList(
+                ObjMembers {
+                    locals: [],
+                    asserts: [],
+                    fields: [],
+                },
+            ),
+        ) from virtual:<test>:27-29,
+    },
+) from virtual:<test>:0-29
addedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__array_comp.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__array_comp.snap
@@ -0,0 +1,41 @@
+---
+source: crates/jrsonnet-peg-parser/src/lib.rs
+expression: "parsep(\"[std.deepJoin(x) for x in arr]\")"
+---
+ArrComp(
+    Apply(
+        Index {
+            indexable: Var(
+                "std",
+            ) from virtual:<test>:1-4,
+            parts: [
+                IndexPart {
+                    value: Str(
+                        "deepJoin",
+                    ) from virtual:<test>:5-13,
+                },
+            ],
+        } from virtual:<test>:1-13,
+        ArgsDesc {
+            unnamed: [
+                Var(
+                    "x",
+                ) from virtual:<test>:14-15,
+            ],
+            named: [],
+        },
+        false,
+    ) from virtual:<test>:1-16,
+    [
+        ForSpec(
+            ForSpecData(
+                Full(
+                    "x",
+                ),
+                Var(
+                    "arr",
+                ) from virtual:<test>:26-29,
+            ),
+        ),
+    ],
+) from virtual:<test>:0-30
addedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__array_test_error.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__array_test_error.snap
@@ -0,0 +1,38 @@
+---
+source: crates/jrsonnet-peg-parser/src/lib.rs
+expression: "parsep(\"[a for a in b if c for e in f]\")"
+---
+ArrComp(
+    Var(
+        "a",
+    ) from virtual:<test>:1-2,
+    [
+        ForSpec(
+            ForSpecData(
+                Full(
+                    "a",
+                ),
+                Var(
+                    "b",
+                ) from virtual:<test>:12-13,
+            ),
+        ),
+        IfSpec(
+            IfSpecData(
+                Var(
+                    "c",
+                ) from virtual:<test>:17-18,
+            ),
+        ),
+        ForSpec(
+            ForSpecData(
+                Full(
+                    "e",
+                ),
+                Var(
+                    "f",
+                ) from virtual:<test>:28-29,
+            ),
+        ),
+    ],
+) from virtual:<test>:0-30
addedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__basic_math.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__basic_math.snap
@@ -0,0 +1,23 @@
+---
+source: crates/jrsonnet-peg-parser/src/lib.rs
+expression: "parsep(\"2+2*2\")"
+---
+BinaryOp(
+    BinaryOp {
+        lhs: Num(
+            2.0,
+        ) from virtual:<test>:0-1,
+        op: Add,
+        rhs: BinaryOp(
+            BinaryOp {
+                lhs: Num(
+                    2.0,
+                ) from virtual:<test>:2-3,
+                op: Mul,
+                rhs: Num(
+                    2.0,
+                ) from virtual:<test>:4-5,
+            },
+        ) from virtual:<test>:2-5,
+    },
+) from virtual:<test>:0-5
addedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__default_param_before_nondefault.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__default_param_before_nondefault.snap
@@ -0,0 +1,54 @@
+---
+source: crates/jrsonnet-peg-parser/src/lib.rs
+expression: "parsep(\"local x(foo = 'foo', bar) = null; null\")"
+---
+LocalExpr(
+    [
+        Function {
+            name: "x",
+            params: ExprParams {
+                exprs: [
+                    ExprParam {
+                        destruct: Full(
+                            "foo",
+                        ),
+                        default: Some(
+                            Str(
+                                "foo",
+                            ) from virtual:<test>:14-19,
+                        ),
+                    },
+                    ExprParam {
+                        destruct: Full(
+                            "bar",
+                        ),
+                        default: None,
+                    },
+                ],
+                signature: FunctionSignature(
+                    [
+                        ParamParse {
+                            name: Named(
+                                "foo",
+                            ),
+                            default: Exists,
+                        },
+                        ParamParse {
+                            name: Named(
+                                "bar",
+                            ),
+                            default: None,
+                        },
+                    ],
+                ),
+                binds_len: 2,
+            },
+            value: Literal(
+                Null,
+            ) from virtual:<test>:28-32,
+        },
+    ],
+    Literal(
+        Null,
+    ) from virtual:<test>:34-38,
+) from virtual:<test>:0-38
addedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__double_negation.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__double_negation.snap
@@ -0,0 +1,13 @@
+---
+source: crates/jrsonnet-peg-parser/src/lib.rs
+expression: "parsep(\"!!a\")"
+---
+UnaryOp(
+    Not,
+    UnaryOp(
+        Not,
+        Var(
+            "a",
+        ) from virtual:<test>:2-3,
+    ) from virtual:<test>:1-3,
+) from virtual:<test>:0-3
addedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__empty_object.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__empty_object.snap
@@ -0,0 +1,13 @@
+---
+source: crates/jrsonnet-peg-parser/src/lib.rs
+expression: "parsep(\"{}\")"
+---
+Obj(
+    MemberList(
+        ObjMembers {
+            locals: [],
+            asserts: [],
+            fields: [],
+        },
+    ),
+) from virtual:<test>:0-2
addedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__imports.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__imports.snap
@@ -0,0 +1,10 @@
+---
+source: crates/jrsonnet-peg-parser/src/lib.rs
+expression: "parsep(\"import \\\"hello\\\"\")"
+---
+Import(
+    Normal,
+    Str(
+        "hello",
+    ) from virtual:<test>:7-14,
+) from virtual:<test>:0-14
addedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__infix_precedence.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__infix_precedence.snap
@@ -0,0 +1,21 @@
+---
+source: crates/jrsonnet-peg-parser/src/lib.rs
+expression: "parsep(\"!a && !b\")"
+---
+BinaryOp(
+    BinaryOp {
+        lhs: UnaryOp(
+            Not,
+            Var(
+                "a",
+            ) from virtual:<test>:1-2,
+        ) from virtual:<test>:0-2,
+        op: And,
+        rhs: UnaryOp(
+            Not,
+            Var(
+                "b",
+            ) from virtual:<test>:7-8,
+        ) from virtual:<test>:6-8,
+    },
+) from virtual:<test>:0-8
addedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__missing_newline_between_comment_and_eof.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__missing_newline_between_comment_and_eof.snap
@@ -0,0 +1,25 @@
+---
+source: crates/jrsonnet-peg-parser/src/lib.rs
+expression: "parsep(\"{a:1}\n\n\t\t\t//+213\")"
+---
+Obj(
+    MemberList(
+        ObjMembers {
+            locals: [],
+            asserts: [],
+            fields: [
+                FieldMember {
+                    name: Fixed(
+                        "a",
+                    ),
+                    plus: false,
+                    params: None,
+                    visibility: Normal,
+                    value: Num(
+                        1.0,
+                    ) from virtual:<test>:3-4,
+                },
+            ],
+        },
+    ),
+) from virtual:<test>:0-5
addedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__multiline_string.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__multiline_string.snap
@@ -0,0 +1,7 @@
+---
+source: crates/jrsonnet-peg-parser/src/lib.rs
+expression: "parsep(\"|||\\n    Hello world!\\n     a\\n|||\")"
+---
+Str(
+    "Hello world!\n a\n",
+) from virtual:<test>:0-31
addedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__multiple_args_buf.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__multiple_args_buf.snap
@@ -0,0 +1,21 @@
+---
+source: crates/jrsonnet-peg-parser/src/lib.rs
+expression: "parsep(\"a(b, null_fields)\")"
+---
+Apply(
+    Var(
+        "a",
+    ) from virtual:<test>:0-1,
+    ArgsDesc {
+        unnamed: [
+            Var(
+                "b",
+            ) from virtual:<test>:2-3,
+            Var(
+                "null_fields",
+            ) from virtual:<test>:5-16,
+        ],
+        named: [],
+    },
+    false,
+) from virtual:<test>:0-17
addedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__reserved.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__reserved.snap
@@ -0,0 +1,7 @@
+---
+source: crates/jrsonnet-peg-parser/src/lib.rs
+expression: "parsep(\"null\")"
+---
+Literal(
+    Null,
+) from virtual:<test>:0-4
addedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__slice.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__slice.snap
@@ -0,0 +1,20 @@
+---
+source: crates/jrsonnet-peg-parser/src/lib.rs
+expression: "parsep(\"a[1:]\")"
+---
+Slice(
+    Slice {
+        value: Var(
+            "a",
+        ) from virtual:<test>:0-1,
+        slice: SliceDesc {
+            start: Some(
+                Num(
+                    1.0,
+                ) from virtual:<test>:2-3,
+            ),
+            end: None,
+            step: None,
+        },
+    },
+) from virtual:<test>:0-5
addedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__string_escaping.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__string_escaping.snap
@@ -0,0 +1,7 @@
+---
+source: crates/jrsonnet-peg-parser/src/lib.rs
+expression: "parsep(r#\"\"Hello, \\\"world\\\"!\"\"#)"
+---
+Str(
+    "Hello, \"world\"!",
+) from virtual:<test>:0-19
addedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__string_unescaping.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__string_unescaping.snap
@@ -0,0 +1,7 @@
+---
+source: crates/jrsonnet-peg-parser/src/lib.rs
+expression: "parsep(r#\"\"Hello\\nWorld\"\"#)"
+---
+Str(
+    "Hello\nWorld",
+) from virtual:<test>:0-14
addedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__string_verbantim.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__string_verbantim.snap
@@ -0,0 +1,7 @@
+---
+source: crates/jrsonnet-peg-parser/src/lib.rs
+expression: "parsep(r#\"@\"Hello\\n\"\"World\"\"\"\"#)"
+---
+Str(
+    "Hello\\n\"World\"",
+) from virtual:<test>:0-19
addedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__suffix.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__suffix.snap
@@ -0,0 +1,16 @@
+---
+source: crates/jrsonnet-peg-parser/src/lib.rs
+expression: "parsep(\"std.test\")"
+---
+Index {
+    indexable: Var(
+        "std",
+    ) from virtual:<test>:0-3,
+    parts: [
+        IndexPart {
+            value: Str(
+                "test",
+            ) from virtual:<test>:4-8,
+        },
+    ],
+} from virtual:<test>:0-8
modifiedcrates/jrsonnet-stdlib/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/Cargo.toml
+++ b/crates/jrsonnet-stdlib/Cargo.toml
@@ -16,14 +16,17 @@
 # Bigint type
 exp-bigint = ["dep:num-bigint", "jrsonnet-evaluator/exp-bigint"]
 
-exp-null-coaelse = ["jrsonnet-parser/exp-null-coaelse", "jrsonnet-evaluator/exp-null-coaelse"]
+exp-null-coaelse = [
+	"jrsonnet-ir/exp-null-coaelse",
+	"jrsonnet-evaluator/exp-null-coaelse",
+]
 # std.regexMatch and other helpers
 exp-regex = ["dep:regex", "dep:lru", "dep:rustc-hash"]
 
 [dependencies]
 jrsonnet-evaluator.workspace = true
 jrsonnet-macros.workspace = true
-jrsonnet-parser.workspace = true
+jrsonnet-ir.workspace = true
 jrsonnet-gcmodule.workspace = true
 
 # Used for std.parseJson/std.parseYaml
@@ -50,6 +53,3 @@
 regex = { workspace = true, optional = true }
 lru = { workspace = true, optional = true }
 rustc-hash = { workspace = true, optional = true }
-
-[build-dependencies]
-jrsonnet-parser.workspace = true
addedcrates/jrsonnet-stdlib/resultdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-stdlib/result
@@ -0,0 +1 @@
+/nix/store/2fxmjvz4f6c8g6gi37y35h356vj0n8k6-benchmarks
\ No newline at end of file
modifiedcrates/jrsonnet-stdlib/src/compat.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/compat.rs
+++ b/crates/jrsonnet-stdlib/src/compat.rs
@@ -8,7 +8,7 @@
 #[allow(non_snake_case)]
 pub fn builtin___compare(v1: Val, v2: Val) -> Result<i32> {
 	Ok(
-		match evaluate_compare_op(&v1, &v2, jrsonnet_parser::BinaryOpType::Lt)? {
+		match evaluate_compare_op(&v1, &v2, jrsonnet_ir::BinaryOpType::Lt)? {
 			Ordering::Less => -1,
 			Ordering::Equal => 0,
 			Ordering::Greater => 1,
@@ -30,7 +30,7 @@
 			let ordering = evaluate_compare_op(
 				&Val::Arr(arr1),
 				&Val::Arr(arr2),
-				jrsonnet_parser::BinaryOpType::Lt,
+				jrsonnet_ir::BinaryOpType::Lt,
 			)?;
 			Ok($operator.contains(&ordering))
 		}
modifiedcrates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -20,7 +20,7 @@
 	ContextBuilder, IStr, ObjValue, ObjValueBuilder, Thunk, Val,
 };
 use jrsonnet_gcmodule::{Acyclic, Cc, Trace};
-use jrsonnet_parser::Source;
+use jrsonnet_ir::Source;
 pub use manifest::*;
 pub use math::*;
 pub use misc::*;
modifiedcrates/jrsonnet-stdlib/src/manifest/ini.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/manifest/ini.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/ini.rs
@@ -4,8 +4,8 @@
 	manifest::{ManifestFormat, ToStringFormat},
 	typed::{FromUntyped, Typed},
 	ObjValue, Result, ResultExt, Val,
+	IStr,
 };
-use jrsonnet_parser::IStr;
 
 pub struct IniFormat {
 	#[cfg(feature = "exp-preserve-order")]
modifiedcrates/jrsonnet-stdlib/src/sets.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/sets.rs
+++ b/crates/jrsonnet-stdlib/src/sets.rs
@@ -3,7 +3,7 @@
 use jrsonnet_evaluator::{
 	function::builtin, operator::evaluate_compare_op, val::ArrValue, Result, Thunk, Val,
 };
-use jrsonnet_parser::BinaryOpType;
+use jrsonnet_ir::BinaryOpType;
 
 use crate::keyf::KeyF;
 
modifiedcrates/jrsonnet-stdlib/src/sort.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/sort.rs
+++ b/crates/jrsonnet-stdlib/src/sort.rs
@@ -9,7 +9,7 @@
 	val::{equals, ArrValue},
 	Result, Thunk, Val,
 };
-use jrsonnet_parser::BinaryOpType;
+use jrsonnet_ir::BinaryOpType;
 
 use crate::{eval_on_empty, keyf::KeyF};
 
modifiedflake.nixdiffbeforeafterboth
--- a/flake.nix
+++ b/flake.nix
@@ -160,6 +160,7 @@
               ]
               ++ lib.optionals (!stdenv.isDarwin) [
                 valgrind
+                kdePackages.kcachegrind
               ];
           };
         };
modifiedxtask/src/main.rsdiffbeforeafterboth
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -52,10 +52,9 @@
 		} => {
 			let out = sh.create_temp_dir()?;
 
-			// build-std
 			cmd!(
 				sh,
-				"cargo build -Zbuild-std --target={target} --profile releasedebug"
+				"cargo build --target={target} --profile releasedebug"
 			)
 			.run()?;
 			let built = format!("./target/{target}/releasedebug/jrsonnet");