git.delta.rocks / jrsonnet / refs/commits / b8bef138ffa9

difftreelog

Merge pull request #84 from CertainLach/split-stdlib

Yaroslav Bolyukin2022-10-17parents: #e831ab7 #c39582b.patch.diff
in: master
Separate jrsonnet-evaluator and stdlib implementation #82

173 files changed

deleted.envrcdiffbeforeafterboth
--- a/.envrc
+++ /dev/null
@@ -1,3 +0,0 @@
-source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/1.2.3/direnvrc" "sha256-/aHqL/6nLpHcZJcB5/7/5+mO338l28uFbq88DMfWJn4="
-
-use flake
deleted.gitlab-ci.ymldiffbeforeafterboth
--- a/.gitlab-ci.yml
+++ /dev/null
@@ -1,58 +0,0 @@
-variables:
-  CARGO_HOME: $CI_PROJECT_DIR/cache
-
-stages:
-  - prepare
-  - build
-
-build-container:
-  image: docker:19.03.11
-  stage: prepare
-  services:
-    - docker:19.03.11-dind
-  before_script:
-    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
-  script:
-    - docker pull $CI_REGISTRY_IMAGE:build || true
-    - docker build -t $CI_REGISTRY_IMAGE:build -f build/Dockerfile .
-    - docker push $CI_REGISTRY_IMAGE:build
-
-test-library:
-  image: $CI_REGISTRY_IMAGE:build
-  stage: build
-  script:
-    - cargo clippy
-  cache:
-    key: test
-    paths:
-      - ./cache
-
-build-linux:
-  image: $CI_REGISTRY_IMAGE:build
-  stage: build
-  script:
-    - cargo build --release
-  cache:
-    key: linux
-    paths:
-      - ./cache
-      - ./target
-  artifacts:
-    paths:
-      - ./target/release/jrsonnet
-    expire_in: 30 days
-
-build-wasm:
-  image: $CI_REGISTRY_IMAGE:build
-  stage: build
-  script:
-    - cargo build --target=wasm32-wasi --release
-  cache:
-    key: wasm
-    paths:
-      - ./cache
-      - ./target
-  artifacts:
-    paths:
-      - ./target/wasm32-wasi/release/jsonnet.wasm
-    expire_in: 30 days
modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -87,16 +87,16 @@
 
 [[package]]
 name = "clap"
-version = "3.1.18"
+version = "3.2.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b"
+checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83"
 dependencies = [
  "atty",
  "bitflags",
  "clap_derive",
  "clap_lex",
  "indexmap",
- "lazy_static",
+ "once_cell",
  "strsim",
  "termcolor",
  "textwrap",
@@ -104,18 +104,18 @@
 
 [[package]]
 name = "clap_complete"
-version = "3.1.4"
+version = "3.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da92e6facd8d73c22745a5d3cbb59bdf8e46e3235c923e516527d8e81eec14a4"
+checksum = "ead064480dfc4880a10764488415a97fdd36a4cf1bb022d372f02e8faf8386e1"
 dependencies = [
  "clap",
 ]
 
 [[package]]
 name = "clap_derive"
-version = "3.1.18"
+version = "3.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c"
+checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902"
 dependencies = [
  "heck",
  "proc-macro-error",
@@ -126,9 +126,9 @@
 
 [[package]]
 name = "clap_lex"
-version = "0.2.0"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
+checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
 dependencies = [
  "os_str_bytes",
 ]
@@ -221,6 +221,7 @@
  "jrsonnet-evaluator",
  "jrsonnet-gcmodule",
  "jrsonnet-parser",
+ "jrsonnet-stdlib",
 ]
 
 [[package]]
@@ -229,21 +230,17 @@
 dependencies = [
  "annotate-snippets",
  "anyhow",
- "base64",
  "bincode",
  "hashbrown 0.12.1",
  "jrsonnet-gcmodule",
  "jrsonnet-interner",
  "jrsonnet-macros",
  "jrsonnet-parser",
- "jrsonnet-stdlib",
  "jrsonnet-types",
- "md5",
  "pathdiff",
  "rustc-hash",
  "serde",
  "serde_json",
- "serde_yaml_with_quirks",
  "static_assertions",
  "strsim",
  "thiserror",
@@ -278,6 +275,7 @@
  "jrsonnet-gcmodule",
  "rustc-hash",
  "serde",
+ "structdump",
 ]
 
 [[package]]
@@ -295,15 +293,28 @@
 dependencies = [
  "jrsonnet-gcmodule",
  "jrsonnet-interner",
- "jrsonnet-stdlib",
  "peg",
  "serde",
  "static_assertions",
+ "structdump",
 ]
 
 [[package]]
 name = "jrsonnet-stdlib"
 version = "0.4.2"
+dependencies = [
+ "base64",
+ "bincode",
+ "jrsonnet-evaluator",
+ "jrsonnet-gcmodule",
+ "jrsonnet-macros",
+ "jrsonnet-parser",
+ "md5",
+ "serde",
+ "serde_json",
+ "serde_yaml_with_quirks",
+ "structdump",
+]
 
 [[package]]
 name = "jrsonnet-types"
@@ -314,31 +325,26 @@
 ]
 
 [[package]]
-name = "jsonnet"
+name = "libc"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
+
+[[package]]
+name = "libjsonnet"
 version = "0.4.2"
 dependencies = [
  "jrsonnet-evaluator",
  "jrsonnet-gcmodule",
  "jrsonnet-parser",
+ "jrsonnet-stdlib",
 ]
-
-[[package]]
-name = "lazy_static"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
-name = "libc"
-version = "0.2.126"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
-
-[[package]]
 name = "linked-hash-map"
-version = "0.5.4"
+version = "0.5.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
+checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
 
 [[package]]
 name = "lock_api"
@@ -511,18 +517,18 @@
 
 [[package]]
 name = "serde"
-version = "1.0.137"
+version = "1.0.142"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
+checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.137"
+version = "1.0.142"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
+checksum = "34b5b8d809babe02f538c2cfec6f2c1ed10804c0e5a6a041a049a4f5588ccc2e"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -531,9 +537,9 @@
 
 [[package]]
 name = "serde_json"
-version = "1.0.81"
+version = "1.0.82"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
+checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
 dependencies = [
  "indexmap",
  "itoa",
@@ -572,6 +578,28 @@
 checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
 
 [[package]]
+name = "structdump"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0570327507bf281d8a6e6b0d4c082b12cb6bcee27efce755aa5efacd44076c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "structdump-derive",
+]
+
+[[package]]
+name = "structdump-derive"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29cc0b59cfa11f1bceda09a9a7e37e6a6c3138575fd24ade8aa9af6d09aedf28"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
 name = "syn"
 version = "1.0.96"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -592,6 +620,16 @@
 ]
 
 [[package]]
+name = "tests"
+version = "0.1.0"
+dependencies = [
+ "jrsonnet-evaluator",
+ "jrsonnet-gcmodule",
+ "jrsonnet-stdlib",
+ "serde",
+]
+
+[[package]]
 name = "textwrap"
 version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
 [workspace]
-members = ["crates/*", "bindings/jsonnet", "cmds/jrsonnet"]
+members = ["crates/*", "bindings/jsonnet", "cmds/jrsonnet", "tests"]
 
 [profile.test]
 opt-level = 1
modifiedbindings/jsonnet/Cargo.tomldiffbeforeafterboth
--- a/bindings/jsonnet/Cargo.toml
+++ b/bindings/jsonnet/Cargo.toml
@@ -1,5 +1,5 @@
 [package]
-name = "jsonnet"
+name = "libjsonnet"
 description = "Rust implementation of libjsonnet.so"
 version = "0.4.2"
 authors = ["Yaroslav Bolyukin <iam@lach.pw>"]
@@ -10,12 +10,15 @@
 [dependencies]
 jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.4.2" }
 jrsonnet-parser = { path = "../../crates/jrsonnet-parser", version = "0.4.2" }
+jrsonnet-stdlib = { path = "../../crates/jrsonnet-stdlib", version = "0.4.2" }
 jrsonnet-gcmodule = { version = "0.3.4" }
 
 [lib]
+name = "jsonnet"
 crate-type = ["cdylib"]
 
 [features]
+# Export additional functions for native integration, i.e ability to set custom trace format
 interop = []
 experimental = ["exp-preserve-order", "exp-destruct"]
 exp-preserve-order = ["jrsonnet-evaluator/exp-preserve-order"]
modifiedbindings/jsonnet/src/import.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/import.rs
+++ b/bindings/jsonnet/src/import.rs
@@ -4,18 +4,18 @@
 	any::Any,
 	cell::RefCell,
 	collections::HashMap,
+	env::current_dir,
 	ffi::{c_void, CStr, CString},
-	fs::File,
-	io::Read,
 	os::raw::{c_char, c_int},
-	path::{Path, PathBuf},
+	path::PathBuf,
 	ptr::null_mut,
 };
 
 use jrsonnet_evaluator::{
 	error::{Error::*, Result},
-	throw, ImportResolver, State,
+	throw, FileImportResolver, ImportResolver, State,
 };
+use jrsonnet_parser::{SourceDirectory, SourceFile, SourcePath};
 
 pub type JsonnetImportCallback = unsafe extern "C" fn(
 	ctx: *mut c_void,
@@ -29,28 +29,34 @@
 pub struct CallbackImportResolver {
 	cb: JsonnetImportCallback,
 	ctx: *mut c_void,
-	out: RefCell<HashMap<PathBuf, Vec<u8>>>,
+	out: RefCell<HashMap<SourcePath, Vec<u8>>>,
 }
 impl ImportResolver for CallbackImportResolver {
-	fn resolve_file(&self, from: &Path, path: &str) -> Result<PathBuf> {
-		let base = CString::new(from.to_str().unwrap()).unwrap().into_raw();
-		let rel = CString::new(path).unwrap().into_raw();
+	fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {
+		let base = if let Some(p) = from.downcast_ref::<SourceFile>() {
+			let mut o = p.path().to_owned();
+			o.pop();
+			o
+		} else if let Some(d) = from.downcast_ref::<SourceDirectory>() {
+			d.path().to_owned()
+		} else if from.is_default() {
+			current_dir().map_err(|e| ImportIo(e.to_string()))?
+		} else {
+			unreachable!("can't resolve this path");
+		};
+		let base = unsafe { crate::unparse_path(&base) };
+		let rel = CString::new(path).unwrap();
 		let found_here: *mut c_char = null_mut();
 		let mut success: i32 = 0;
 		let result_ptr = unsafe {
 			(self.cb)(
 				self.ctx,
-				base,
-				rel,
+				base.as_ptr(),
+				rel.as_ptr(),
 				&mut (found_here as *const _),
 				&mut success,
 			)
 		};
-		// Release memory occipied by arguments passed
-		unsafe {
-			let _ = CString::from_raw(base);
-			let _ = CString::from_raw(rel);
-		}
 		let result_raw = unsafe { CStr::from_ptr(result_ptr) };
 		let result_str = result_raw.to_str().unwrap();
 		assert!(success == 0 || success == 1);
@@ -61,7 +67,9 @@
 		}
 
 		let found_here_raw = unsafe { CStr::from_ptr(found_here) };
-		let found_here_buf = PathBuf::from(found_here_raw.to_str().unwrap());
+		let found_here_buf = SourcePath::new(SourceFile::new(PathBuf::from(
+			found_here_raw.to_str().unwrap(),
+		)));
 		unsafe {
 			let _ = CString::from_raw(found_here);
 		}
@@ -74,16 +82,18 @@
 
 		Ok(found_here_buf)
 	}
-	fn load_file_contents(&self, resolved: &Path) -> Result<Vec<u8>> {
+	fn load_file_contents(&self, resolved: &SourcePath) -> Result<Vec<u8>> {
 		Ok(self.out.borrow().get(resolved).unwrap().clone())
 	}
 
-	unsafe fn as_any(&self) -> &dyn Any {
+	fn as_any(&self) -> &dyn Any {
 		self
 	}
 }
 
 /// # Safety
+///
+/// It should be safe to call `cb` using valid values with passed `ctx`
 #[no_mangle]
 pub unsafe extern "C" fn jsonnet_import_callback(
 	vm: &State,
@@ -97,56 +107,17 @@
 	}))
 }
 
-/// Standard FS import resolver
-#[derive(Default)]
-pub struct NativeImportResolver {
-	library_paths: RefCell<Vec<PathBuf>>,
-}
-impl NativeImportResolver {
-	fn add_jpath(&self, path: PathBuf) {
-		self.library_paths.borrow_mut().push(path);
-	}
-}
-impl ImportResolver for NativeImportResolver {
-	fn resolve_file(&self, from: &Path, path: &str) -> Result<PathBuf> {
-		let mut new_path = from.to_owned();
-		new_path.push(path);
-		if new_path.exists() {
-			Ok(new_path)
-		} else {
-			for library_path in self.library_paths.borrow().iter() {
-				let mut cloned = library_path.clone();
-				cloned.push(path);
-				if cloned.exists() {
-					return Ok(cloned);
-				}
-			}
-			throw!(ImportFileNotFound(from.to_owned(), path.to_owned()))
-		}
-	}
-	fn load_file_contents(&self, id: &Path) -> Result<Vec<u8>> {
-		let mut file = File::open(id).map_err(|_e| ResolvedFileNotFound(id.to_owned()))?;
-		let mut out = Vec::new();
-		file.read_to_end(&mut out)
-			.map_err(|e| ImportIo(e.to_string()))?;
-		Ok(out)
-	}
-	unsafe fn as_any(&self) -> &dyn Any {
-		self
-	}
-}
-
 /// # Safety
 ///
-/// This function is safe, if received v is a pointer to normal C string
+/// `path` should be a NUL-terminated string
 #[no_mangle]
-pub unsafe extern "C" fn jsonnet_jpath_add(vm: &State, v: *const c_char) {
-	let cstr = CStr::from_ptr(v);
+pub unsafe extern "C" fn jsonnet_jpath_add(vm: &State, path: *const c_char) {
+	let cstr = CStr::from_ptr(path);
 	let path = PathBuf::from(cstr.to_str().unwrap());
-	let any_resolver = &vm.settings().import_resolver;
+	let any_resolver = vm.import_resolver();
 	let resolver = any_resolver
 		.as_any()
-		.downcast_ref::<NativeImportResolver>()
+		.downcast_ref::<FileImportResolver>()
 		.expect("jpaths are not compatible with callback imports!");
 	resolver.add_jpath(path);
 }
modifiedbindings/jsonnet/src/lib.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/lib.rs
+++ b/bindings/jsonnet/src/lib.rs
@@ -10,50 +10,98 @@
 
 use std::{
 	alloc::Layout,
-	ffi::{CStr, CString},
+	borrow::Cow,
+	ffi::{CStr, CString, OsStr},
 	os::raw::{c_char, c_double, c_int, c_uint},
-	path::PathBuf,
+	path::Path,
 };
 
-use import::NativeImportResolver;
-use jrsonnet_evaluator::{IStr, ManifestFormat, State, Val};
+use jrsonnet_evaluator::{
+	trace::PathResolver, FileImportResolver, IStr, ManifestFormat, State, Val,
+};
 
 /// WASM stub
 #[cfg(target_arch = "wasm32")]
 #[no_mangle]
 pub extern "C" fn _start() {}
 
+/// Return the version string of the Jsonnet interpreter.
+/// Conforms to [semantic versioning](http://semver.org/).
+/// If this does not match `LIB_JSONNET_VERSION`
+/// then there is a mismatch between header and compiled library.
 #[no_mangle]
 pub extern "C" fn jsonnet_version() -> &'static [u8; 8] {
 	b"v0.16.0\0"
 }
 
+unsafe fn parse_path(input: &CStr) -> Cow<Path> {
+	#[cfg(target_family = "unix")]
+	{
+		use std::os::unix::ffi::OsStrExt;
+		let str = OsStr::from_bytes(input.to_bytes());
+		Cow::Borrowed(Path::new(str))
+	}
+	#[cfg(not(target_family = "unix"))]
+	{
+		let string = input.to_str().expect("bad utf-8");
+		Cow::Borrowed(string.as_ref())
+	}
+}
+
+unsafe fn unparse_path(input: &Path) -> Cow<CStr> {
+	#[cfg(target_family = "unix")]
+	{
+		use std::os::unix::ffi::OsStrExt;
+		let str = CString::new(input.as_os_str().as_bytes()).expect("input has zero byte in it");
+		Cow::Owned(str)
+	}
+	#[cfg(not(target_family = "unix"))]
+	{
+		let str = input.as_os_str().to_str().expect("bad utf-8");
+		let cstr = CString::new(str).expect("input has NUL inside");
+		Cow::Owned(cstr)
+	}
+}
+
+/// Creates a new Jsonnet virtual machine.
 #[no_mangle]
+#[allow(clippy::box_default)]
 pub extern "C" fn jsonnet_make() -> *mut State {
 	let state = State::default();
-	state.with_stdlib();
-	state.settings_mut().import_resolver = Box::new(NativeImportResolver::default());
+	state.settings_mut().import_resolver = Box::new(FileImportResolver::default());
+	state.settings_mut().context_initializer = Box::new(jrsonnet_stdlib::ContextInitializer::new(
+		state.clone(),
+		PathResolver::new_cwd_fallback(),
+	));
 	Box::into_raw(Box::new(state))
 }
 
-/// # Safety
+/// Complement of [`jsonnet_vm_make`].
 #[no_mangle]
 #[allow(clippy::boxed_local)]
-pub unsafe extern "C" fn jsonnet_destroy(vm: *mut State) {
-	Box::from_raw(vm);
+pub extern "C" fn jsonnet_destroy(vm: Box<State>) {
+	drop(vm);
 }
 
+/// Set the maximum stack depth.
 #[no_mangle]
 pub extern "C" fn jsonnet_max_stack(vm: &State, v: c_uint) {
 	vm.settings_mut().max_stack = v as usize;
 }
 
-// jrsonnet currently have no GC, so these functions is no-op
+/// Set the number of objects required before a garbage collection cycle is allowed.
+///
+/// No-op for now
 #[no_mangle]
 pub extern "C" fn jsonnet_gc_min_objects(_vm: &State, _v: c_uint) {}
+
+/// Run the garbage collector after this amount of growth in the number of objects
+///
+/// No-op for now
 #[no_mangle]
 pub extern "C" fn jsonnet_gc_growth_trigger(_vm: &State, _v: c_double) {}
 
+/// Expect a string as output and don't JSON encode it.
 #[no_mangle]
 pub extern "C" fn jsonnet_string_output(vm: &State, v: c_int) {
 	match v {
@@ -67,13 +115,20 @@
 	}
 }
 
+/// Allocate, resize, or free a buffer.  This will abort if the memory cannot be allocated. It will
+/// only return NULL if sz was zero.
+///
 /// # Safety
 ///
+/// `buf` should be either previosly allocated by this library, or NULL
+///
 /// This function is most definitely broken, but it works somehow, see TODO inside
 #[no_mangle]
 pub unsafe extern "C" fn jsonnet_realloc(_vm: &State, buf: *mut u8, sz: usize) -> *mut u8 {
 	if buf.is_null() {
-		assert!(sz != 0);
+		if sz == 0 {
+			return std::ptr::null_mut();
+		}
 		return std::alloc::alloc(Layout::from_size_align(sz, std::mem::align_of::<u8>()).unwrap());
 	}
 	// TODO: Somehow store size of allocation, because its real size is probally not 16 :D
@@ -88,30 +143,37 @@
 	std::alloc::realloc(buf, old_layout, sz)
 }
 
-/// # Safety
+/// Clean up a JSON subtree.
+///
+/// This is useful if you want to abort with an error mid-way through building a complex value.
 #[no_mangle]
 #[allow(clippy::boxed_local)]
-pub unsafe extern "C" fn jsonnet_json_destroy(_vm: &State, v: *mut Val) {
-	Box::from_raw(v);
+pub extern "C" fn jsonnet_json_destroy(_vm: &State, v: Box<Val>) {
+	drop(v);
 }
 
+/// Set the number of lines of stack trace to display (0 for all of them).
 #[no_mangle]
 pub extern "C" fn jsonnet_max_trace(vm: &State, v: c_uint) {
 	vm.set_max_trace(v as usize)
 }
 
+/// Evaluate a file containing Jsonnet code, return a JSON string.
+///
+/// The returned string should be cleaned up with jsonnet_realloc.
+///
 /// # Safety
 ///
-/// This function is safe, if received v is a pointer to normal C string
+/// `filename` should be a NUL-terminated string
 #[no_mangle]
 pub unsafe extern "C" fn jsonnet_evaluate_file(
 	vm: &State,
 	filename: *const c_char,
 	error: &mut c_int,
 ) -> *const c_char {
-	let filename = CStr::from_ptr(filename);
+	let filename = parse_path(CStr::from_ptr(filename));
 	match vm
-		.import(PathBuf::from(filename.to_str().unwrap()))
+		.import(&filename)
 		.and_then(|v| vm.with_tla(v))
 		.and_then(|v| vm.manifest(v))
 	{
@@ -127,9 +189,13 @@
 	}
 }
 
+/// Evaluate a string containing Jsonnet code, return a JSON string.
+///
+/// The returned string should be cleaned up with jsonnet_realloc.
+///
 /// # Safety
 ///
-/// This function is safe, if received v is a pointer to normal C string
+/// `filename`, `snippet` should be a NUL-terminated strings
 #[no_mangle]
 pub unsafe extern "C" fn jsonnet_evaluate_snippet(
 	vm: &State,
@@ -140,10 +206,7 @@
 	let filename = CStr::from_ptr(filename);
 	let snippet = CStr::from_ptr(snippet);
 	match vm
-		.evaluate_snippet(
-			filename.to_str().unwrap().into(),
-			snippet.to_str().unwrap().into(),
-		)
+		.evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())
 		.and_then(|v| vm.with_tla(v))
 		.and_then(|v| vm.manifest(v))
 	{
@@ -183,9 +246,9 @@
 	filename: *const c_char,
 	error: &mut c_int,
 ) -> *const c_char {
-	let filename = CStr::from_ptr(filename);
+	let filename = parse_path(CStr::from_ptr(filename));
 	match vm
-		.import(PathBuf::from(filename.to_str().unwrap()))
+		.import(&filename)
 		.and_then(|v| vm.with_tla(v))
 		.and_then(|v| vm.manifest_multi(v))
 	{
@@ -212,10 +275,7 @@
 	let filename = CStr::from_ptr(filename);
 	let snippet = CStr::from_ptr(snippet);
 	match vm
-		.evaluate_snippet(
-			filename.to_str().unwrap().into(),
-			snippet.to_str().unwrap().into(),
-		)
+		.evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())
 		.and_then(|v| vm.with_tla(v))
 		.and_then(|v| vm.manifest_multi(v))
 	{
@@ -253,9 +313,9 @@
 	filename: *const c_char,
 	error: &mut c_int,
 ) -> *const c_char {
-	let filename = CStr::from_ptr(filename);
+	let filename = parse_path(CStr::from_ptr(filename));
 	match vm
-		.import(PathBuf::from(filename.to_str().unwrap()))
+		.import(&filename)
 		.and_then(|v| vm.with_tla(v))
 		.and_then(|v| vm.manifest_stream(v))
 	{
@@ -266,7 +326,9 @@
 		Err(e) => {
 			*error = 1;
 			let out = vm.stringify_err(&e);
-			CString::new(&out as &str).unwrap().into_raw()
+			CString::new(&out as &str)
+				.expect("there should be no \\0 in the error string")
+				.into_raw()
 		}
 	}
 }
@@ -283,8 +345,8 @@
 	let snippet = CStr::from_ptr(snippet);
 	match vm
 		.evaluate_snippet(
-			filename.to_str().unwrap().into(),
-			snippet.to_str().unwrap().into(),
+			filename.to_str().expect("filename is not utf-8"),
+			snippet.to_str().expect("snippet is not utf-8"),
 		)
 		.and_then(|v| vm.with_tla(v))
 		.and_then(|v| vm.manifest_stream(v))
@@ -296,7 +358,9 @@
 		Err(e) => {
 			*error = 1;
 			let out = vm.stringify_err(&e);
-			CString::new(&out as &str).unwrap().into_raw()
+			CString::new(&out as &str)
+				.expect("there should be no \\0 in the error string")
+				.into_raw()
 		}
 	}
 }
modifiedbindings/jsonnet/src/native.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/native.rs
+++ b/bindings/jsonnet/src/native.rs
@@ -1,17 +1,27 @@
 use std::{
+	borrow::Cow,
 	ffi::{c_void, CStr},
 	os::raw::{c_char, c_int},
 };
 
 use jrsonnet_evaluator::{
 	error::{Error, LocError},
-	function::builtin::{BuiltinParam, NativeCallback, NativeCallbackHandler},
+	function::builtin::{NativeCallback, NativeCallbackHandler},
 	tb,
 	typed::Typed,
 	IStr, State, Val,
 };
 use jrsonnet_gcmodule::Cc;
 
+/// The returned `JsonnetJsonValue*` should be allocated with `jsonnet_realloc`. It will be cleaned up
+/// along with the objects rooted at `argv` by `libjsonnet` when no-longer needed. Return a string upon
+/// failure, which will appear in Jsonnet as an error. The `argv` pointer is an array whose size
+/// matches the array of parameters supplied when the native callback was originally registered.
+///
+/// - `ctx` User pointer, given in jsonnet_native_callback.
+/// - `argv` Array of arguments from Jsonnet code.
+/// - `param` success Set this byref param to 1 to indicate success and 0 for failure.
+/// Returns the content of the imported file, or an error message.
 type JsonnetNativeCallback = unsafe extern "C" fn(
 	ctx: *const c_void,
 	argv: *const *const Val,
@@ -44,13 +54,20 @@
 		if success == 1 {
 			Ok(v)
 		} else {
-			let e = IStr::from_untyped(v, s).expect("error msg");
+			let e = IStr::from_untyped(v, s).expect("error msg should be a string");
 			Err(Error::RuntimeError(e).into())
 		}
 	}
 }
 
+/// Callback to provide native extensions to Jsonnet.
+///
 /// # Safety
+///
+/// `vm` should be a vm allocated by `jsonnet_make`
+/// `name` should be a NUL-terminated string
+/// `cb` should be a function pointer
+/// `raw_params` should point to a NULL-terminated array of NUL-terminated strings
 #[no_mangle]
 pub unsafe extern "C" fn jsonnet_native_callback(
 	vm: &State,
@@ -59,26 +76,33 @@
 	ctx: *const c_void,
 	mut raw_params: *const *const c_char,
 ) {
-	let name = CStr::from_ptr(name).to_str().expect("utf8 name").into();
+	let name = CStr::from_ptr(name)
+		.to_str()
+		.expect("name is not utf-8")
+		.into();
 	let mut params = Vec::new();
 	loop {
 		if (*raw_params).is_null() {
 			break;
 		}
-		let param = CStr::from_ptr(*raw_params).to_str().expect("not utf8");
-		params.push(BuiltinParam {
-			name: param.into(),
-			has_default: false,
-		});
+		let param = CStr::from_ptr(*raw_params)
+			.to_str()
+			.expect("param name is not utf-8");
+		params.push(Cow::Owned(param.into()));
 		raw_params = raw_params.offset(1);
 	}
 
-	vm.add_native(
-		name,
-		#[allow(deprecated)]
-		Cc::new(tb!(NativeCallback::new(
-			params,
-			tb!(JsonnetNativeCallbackHandler { ctx, cb }),
-		))),
-	)
+	let any_resolver = vm.context_initializer();
+	any_resolver
+		.as_any()
+		.downcast_ref::<jrsonnet_stdlib::ContextInitializer>()
+		.expect("only stdlib context initializer supported")
+		.add_native(
+			name,
+			#[allow(deprecated)]
+			Cc::new(tb!(NativeCallback::new(
+				params,
+				tb!(JsonnetNativeCallbackHandler { ctx, cb }),
+			))),
+		)
 }
modifiedbindings/jsonnet/src/val_extract.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/val_extract.rs
+++ b/bindings/jsonnet/src/val_extract.rs
@@ -7,13 +7,16 @@
 
 use jrsonnet_evaluator::{State, Val};
 
+/// If the value is a string, return it as UTF-8, otherwise return `NULL`.
 #[no_mangle]
 pub extern "C" fn jsonnet_json_extract_string(_vm: &State, v: &Val) -> *mut c_char {
 	match v {
-		Val::Str(s) => CString::new(&*s as &str).unwrap().into_raw(),
+		Val::Str(s) => CString::new(s as &str).unwrap().into_raw(),
 		_ => std::ptr::null_mut(),
 	}
 }
+
+/// If the value is a number, return `1` and store the number in out, otherwise return `0`.
 #[no_mangle]
 pub extern "C" fn jsonnet_json_extract_number(_vm: &State, v: &Val, out: &mut c_double) -> c_int {
 	match v {
@@ -24,6 +27,8 @@
 		_ => 0,
 	}
 }
+
+/// Return `0` if the value is `false`, `1` if it is `true`, and `2` if it is not a `bool`.
 #[no_mangle]
 pub extern "C" fn jsonnet_json_extract_bool(_vm: &State, v: &Val) -> c_int {
 	match v {
@@ -32,6 +37,8 @@
 		_ => 2,
 	}
 }
+
+/// Return `1` if the value is `null`, otherwise return `0`.
 #[no_mangle]
 pub extern "C" fn jsonnet_json_extract_null(_vm: &State, v: &Val) -> c_int {
 	match v {
modifiedbindings/jsonnet/src/val_make.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/val_make.rs
+++ b/bindings/jsonnet/src/val_make.rs
@@ -8,37 +8,46 @@
 use jrsonnet_evaluator::{val::ArrValue, ObjValue, State, Val};
 use jrsonnet_gcmodule::Cc;
 
+/// Convert the given `UTF-8` string to a `JsonnetJsonValue`.
+///
 /// # Safety
 ///
-/// This function is safe, if received v is a pointer to normal C string
+/// `v` should be a NUL-terminated string
 #[no_mangle]
-pub unsafe extern "C" fn jsonnet_json_make_string(_vm: &State, v: *const c_char) -> *mut Val {
-	let cstr = CStr::from_ptr(v);
-	let str = cstr.to_str().unwrap();
-	Box::into_raw(Box::new(Val::Str(str.into())))
+pub unsafe extern "C" fn jsonnet_json_make_string(_vm: &State, val: *const c_char) -> *mut Val {
+	let val = CStr::from_ptr(val);
+	let val = val.to_str().expect("string is not utf-8");
+	Box::into_raw(Box::new(Val::Str(val.into())))
 }
 
+/// Convert the given double to a `JsonnetJsonValue`.
 #[no_mangle]
 pub extern "C" fn jsonnet_json_make_number(_vm: &State, v: c_double) -> *mut Val {
 	Box::into_raw(Box::new(Val::Num(v)))
 }
 
+/// Convert the given `bool` (`1` or `0`) to a `JsonnetJsonValue`.
 #[no_mangle]
 pub extern "C" fn jsonnet_json_make_bool(_vm: &State, v: c_int) -> *mut Val {
-	assert!(v == 0 || v == 1);
+	assert!(v == 0 || v == 1, "bad boolean value");
 	Box::into_raw(Box::new(Val::Bool(v == 1)))
 }
 
+/// Make a `JsonnetJsonValue` representing `null`.
 #[no_mangle]
 pub extern "C" fn jsonnet_json_make_null(_vm: &State) -> *mut Val {
 	Box::into_raw(Box::new(Val::Null))
 }
 
+/// Make a `JsonnetJsonValue` representing an array.
+///
+/// Assign elements with [`jsonnet_json_array_append`].
 #[no_mangle]
 pub extern "C" fn jsonnet_json_make_array(_vm: &State) -> *mut Val {
 	Box::into_raw(Box::new(Val::Arr(ArrValue::Eager(Cc::new(Vec::new())))))
 }
 
+/// Make a `JsonnetJsonValue` representing an object.
 #[no_mangle]
 pub extern "C" fn jsonnet_json_make_object(_vm: &State) -> *mut Val {
 	Box::into_raw(Box::new(Val::Obj(ObjValue::new_empty())))
modifiedbindings/jsonnet/src/val_modify.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/val_modify.rs
+++ b/bindings/jsonnet/src/val_modify.rs
@@ -7,9 +7,12 @@
 use jrsonnet_evaluator::{val::ArrValue, State, Thunk, Val};
 use jrsonnet_gcmodule::Cc;
 
+/// Adds value to the end of the array `arr`.
+///
 /// # Safety
 ///
-/// Received arr value should be correct pointer to array allocated by make_array
+/// `arr` should be a pointer to array value allocated by make_array, or returned by other library call
+/// `val` should be a pointer to value allocated using this library
 #[no_mangle]
 pub unsafe extern "C" fn jsonnet_json_array_append(_vm: &State, arr: &mut Val, val: &Val) {
 	match arr {
@@ -26,9 +29,14 @@
 	}
 }
 
+/// Adds the field to the object, bound to value.
+///
+/// This shadows any previous binding of the field.
+///
 /// # Safety
 ///
-/// This function is safe if passed name is ok
+/// `obj` should be a pointer to object value allocated by `make_object`, or returned by other library call
+/// `name` should be NUL-terminated string
 #[no_mangle]
 pub unsafe extern "C" fn jsonnet_json_object_append(
 	_vm: &State,
modifiedbindings/jsonnet/src/vars_tlas.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/vars_tlas.rs
+++ b/bindings/jsonnet/src/vars_tlas.rs
@@ -4,40 +4,84 @@
 
 use jrsonnet_evaluator::State;
 
+/// Binds a Jsonnet external variable to the given string.
+///
+/// Argument values are copied so memory should be managed by the caller.
+///
 /// # Safety
+///
+/// `name`, `code` should be a NUL-terminated strings
 #[no_mangle]
 pub unsafe extern "C" fn jsonnet_ext_var(vm: &State, name: *const c_char, value: *const c_char) {
 	let name = CStr::from_ptr(name);
 	let value = CStr::from_ptr(value);
-	vm.add_ext_str(
-		name.to_str().unwrap().into(),
-		value.to_str().unwrap().into(),
-	)
+
+	let any_initializer = vm.context_initializer();
+	any_initializer
+		.as_any()
+		.downcast_ref::<jrsonnet_stdlib::ContextInitializer>()
+		.expect("only stdlib context initializer supported")
+		.add_ext_str(
+			name.to_str().expect("name is not utf-8").into(),
+			value.to_str().expect("value is not utf-8").into(),
+		)
 }
 
+/// Binds a Jsonnet external variable to the given code.
+///
+/// Argument values are copied so memory should be managed by the caller.
+///
 /// # Safety
+///
+/// `name`, `code` should be a NUL-terminated strings
 #[no_mangle]
-pub unsafe extern "C" fn jsonnet_ext_code(vm: &State, name: *const c_char, value: *const c_char) {
+pub unsafe extern "C" fn jsonnet_ext_code(vm: &State, name: *const c_char, code: *const c_char) {
 	let name = CStr::from_ptr(name);
-	let value = CStr::from_ptr(value);
-	vm.add_ext_code(name.to_str().unwrap(), value.to_str().unwrap().into())
-		.unwrap()
+	let code = CStr::from_ptr(code);
+
+	let any_initializer = vm.context_initializer();
+	any_initializer
+		.as_any()
+		.downcast_ref::<jrsonnet_stdlib::ContextInitializer>()
+		.expect("only stdlib context initializer supported")
+		.add_ext_code(
+			name.to_str().expect("name is not utf-8"),
+			code.to_str().expect("code is not utf-8"),
+		)
+		.expect("can't parse ext code")
 }
+
+/// Binds a top-level string argument for a top-level parameter.
+///
+/// Argument values are copied so memory should be managed by the caller.
+///
 /// # Safety
+///
+/// `name`, `value` should be a NUL-terminated strings
 #[no_mangle]
 pub unsafe extern "C" fn jsonnet_tla_var(vm: &State, name: *const c_char, value: *const c_char) {
 	let name = CStr::from_ptr(name);
 	let value = CStr::from_ptr(value);
 	vm.add_tla_str(
-		name.to_str().unwrap().into(),
-		value.to_str().unwrap().into(),
+		name.to_str().expect("name is not utf-8").into(),
+		value.to_str().expect("value is not utf-8").into(),
 	)
 }
+
+/// Binds a top-level code argument for a top-level parameter.
+///
+/// Argument values are copied so memory should be managed by the caller.
+///
 /// # Safety
+///
+/// `name`, `code` should be a NUL-terminated strings
 #[no_mangle]
-pub unsafe extern "C" fn jsonnet_tla_code(vm: &State, name: *const c_char, value: *const c_char) {
+pub unsafe extern "C" fn jsonnet_tla_code(vm: &State, name: *const c_char, code: *const c_char) {
 	let name = CStr::from_ptr(name);
-	let value = CStr::from_ptr(value);
-	vm.add_tla_code(name.to_str().unwrap().into(), value.to_str().unwrap())
-		.unwrap()
+	let code = CStr::from_ptr(code);
+	vm.add_tla_code(
+		name.to_str().expect("name is not utf-8").into(),
+		code.to_str().expect("code is not utf-8"),
+	)
+	.expect("can't parse tla code")
 }
deletedbuild/Dockerfilediffbeforeafterboth
--- a/build/Dockerfile
+++ /dev/null
@@ -1,8 +0,0 @@
-FROM alpine:edge
-
-RUN apk add --no-cache \
-	clang gcc g++ make cmake curl \
-	openjdk8-jre-base \
-	rustup && \
-	rustup-init --default-toolchain nightly -y -t wasm32-wasi
-ENV PATH /root/.rustup/toolchains/nightly-x86_64-unknown-linux-musl/bin/:/root/.cargo/bin/:${PATH}
deletedbuild/make-docker.shdiffbeforeafterboth
--- a/build/make-docker.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/env sh
-export DOCKER_BUILDKIT=1
-docker build -t jrsonnet -f build/Dockerfile build/
-docker run --rm -it -v $PWD:/build -e CARGO_HOME=/build/cache jrsonnet:latest ash -c "cd /build&&make $@"
deletedbuild/run-docker.shdiffbeforeafterboth
--- a/build/run-docker.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/env sh
-export DOCKER_BUILDKIT=1
-docker build -t jrsonnet -f build/Dockerfile build/
-docker run --rm -it -v $PWD:/build -e CARGO_HOME=/build/cache jrsonnet:latest ash -c "cd /build&&$@"
deletedcmds/jrsonnet-fmt/Cargo.tomldiffbeforeafterboth
--- a/cmds/jrsonnet-fmt/Cargo.toml
+++ /dev/null
@@ -1,8 +0,0 @@
-[package]
-name = "jrsonnet-fmt"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-dprint-core = "0.47.1"
-jrsonnet-parser = { path = "../../crates/jrsonnet-parser" }
deletedcmds/jrsonnet-fmt/src/main.rsdiffbeforeafterboth
--- a/cmds/jrsonnet-fmt/src/main.rs
+++ /dev/null
@@ -1,373 +0,0 @@
-use std::path::PathBuf;
-
-use dprint_core::formatting::{PrintItems, PrintOptions, Signal};
-use jrsonnet_parser::{
-	ArgsDesc, BinaryOpType, BindSpec, Expr, FieldName, LocExpr, Member, ObjBody, Param, ParamsDesc,
-	ParserSettings, Visibility,
-};
-
-pub trait Printable {
-	fn print(&self) -> PrintItems;
-}
-
-macro_rules! pi {
-	(@i; $($t:tt)*) => {{
-		let mut o = PrintItems::new();
-		pi!(@s; o: $($t)*);
-		o
-	}};
-	(@s; $o:ident: str($e:expr) $($t:tt)*) => {{
-		$o.push_str($e);
-		pi!(@s; $o: $($t)*);
-	}};
-	(@s; $o:ident: nl $($t:tt)*) => {{
-		$o.push_signal(Signal::NewLine);
-		pi!(@s; $o: $($t)*);
-	}};
-	(@s; $o:ident: >i $($t:tt)*) => {{
-		$o.push_signal(Signal::StartIndent);
-		pi!(@s; $o: $($t)*);
-	}};
-	(@s; $o:ident: <i $($t:tt)*) => {{
-		$o.push_signal(Signal::FinishIndent);
-		pi!(@s; $o: $($t)*);
-	}};
-	(@s; $o:ident: {$expr:expr} $($t:tt)*) => {{
-		$o.extend($expr.print());
-		pi!(@s; $o: $($t)*);
-	}};
-	(@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{
-		if $e {
-			pi!(@s; $o: $($then)*);
-		}
-		pi!(@s; $o: $($t)*);
-	}};
-	(@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{
-		if $e {
-			pi!(@s; $o: $($then)*);
-		} else {
-			pi!(@s; $o: $($else)*);
-		}
-		pi!(@s; $o: $($t)*);
-	}};
-	(@s; $i:ident:) => {}
-}
-macro_rules! p {
-	(new: $($t:tt)*) => {
-		pi!(@i; $($t)*)
-	};
-	($o:ident: $($t:tt)*) => {
-		pi!(@s; $o: $($t)*)
-	};
-}
-
-impl Printable for FieldName {
-	fn print(&self) -> PrintItems {
-		match self {
-			FieldName::Fixed(f) => {
-				p!(new: str(&f))
-			}
-			FieldName::Dyn(_) => todo!(),
-		}
-	}
-}
-
-impl Printable for Visibility {
-	fn print(&self) -> PrintItems {
-		match self {
-			Visibility::Normal => p!(new: str(":")),
-			Visibility::Hidden => p!(new: str("::")),
-			Visibility::Unhide => p!(new: str(":::")),
-		}
-	}
-}
-
-impl Printable for BinaryOpType {
-	fn print(&self) -> PrintItems {
-		let o = self.to_string();
-		p!(new: str(&o))
-	}
-}
-
-impl<T: Printable> Printable for Option<T> {
-	fn print(&self) -> PrintItems {
-		if let Some(v) = self {
-			v.print()
-		} else {
-			PrintItems::new()
-		}
-	}
-}
-
-impl Printable for Param {
-	fn print(&self) -> PrintItems {
-		p!(new:
-			str(&self.0)
-			if(self.1.is_some())(str(" = ") {self.1})
-		)
-	}
-}
-
-impl Printable for ParamsDesc {
-	fn print(&self) -> PrintItems {
-		let mut out = PrintItems::new();
-		for (i, item) in self.0.iter().enumerate() {
-			if i != 0 {
-				p!(out: str(", "));
-			}
-			out.extend(item.print());
-		}
-		out
-	}
-}
-
-impl Printable for ArgsDesc {
-	fn print(&self) -> PrintItems {
-		let mut out = PrintItems::new();
-		let mut first = Some(());
-		for u in self.unnamed.iter() {
-			if first.take().is_none() {
-				p!(out: str(", "));
-			}
-			p!(out: {u})
-		}
-		for (n, u) in self.named.iter() {
-			if first.take().is_none() {
-				p!(out: str(", "));
-			}
-			p!(out: str(&n) str(" = ") {u})
-		}
-
-		out
-	}
-}
-
-impl Printable for BindSpec {
-	fn print(&self) -> PrintItems {
-		p!(new: str(&self.name) if(self.params.is_some())(str("(") {self.params} str(")")) str(" = ") {self.value})
-	}
-}
-
-struct StrExpr<'s>(&'s str);
-
-impl<'s> Printable for StrExpr<'s> {
-	fn print(&self) -> PrintItems {
-		todo!()
-	}
-}
-
-impl Printable for ObjBody {
-	fn print(&self) -> PrintItems {
-		let mut pi = PrintItems::new();
-		p!(pi: str("{"));
-		match self {
-			ObjBody::MemberList(m) => {
-				if !m.is_empty() {
-					p!(pi: nl > i);
-					for m in m {
-						match m {
-							Member::Field(f) => {
-								p!(pi:
-									{f.name} {f.params}
-									if(f.plus)(str("+"))
-									{f.visibility} str(" ")
-									{f.value}
-									str(",") nl
-								);
-							}
-							Member::BindStmt(s) => {
-								p!(pi: str("local ") {s} str(",") nl)
-							}
-							Member::AssertStmt(a) => p!(pi: str("assert ") {a.0} if(a.1.is_some())(
-								str(" : ") {a.1}
-							) str(",") nl),
-						}
-					}
-					p!(pi: <i);
-				} else {
-					p!(pi: str(" "))
-				}
-			}
-			ObjBody::ObjComp(_) => todo!(),
-		}
-		p!(pi: str("}"));
-		pi
-	}
-}
-
-impl Printable for Expr {
-	fn print(&self) -> PrintItems {
-		let mut pi = PrintItems::new();
-		match self {
-			Expr::Literal(l) => match l {
-				jrsonnet_parser::LiteralType::This => p!(pi: str("self")),
-				jrsonnet_parser::LiteralType::Super => p!(pi: str("super")),
-				jrsonnet_parser::LiteralType::Dollar => p!(pi: str("$")),
-				jrsonnet_parser::LiteralType::Null => p!(pi: str("null")),
-				jrsonnet_parser::LiteralType::True => p!(pi: str("true")),
-				jrsonnet_parser::LiteralType::False => p!(pi: str("false")),
-			},
-			Expr::Str(s) => {
-				p!(pi: str("\"") str(s) str("\""))
-			}
-			Expr::Num(n) => {
-				let n = n.to_string();
-				p!(pi: str(&n));
-			}
-			Expr::Var(v) => p!(pi: str(&v)),
-			Expr::Arr(a) => {
-				p!(pi: str("["));
-				for (i, v) in a.iter().enumerate() {
-					if i != 0 {
-						p!(pi: str(", "));
-					}
-					p!(pi: {v})
-				}
-				p!(pi: str("]"));
-			}
-			Expr::ArrComp(_, _) => todo!(),
-			Expr::Obj(o) => {
-				p!(pi: {o});
-			}
-			Expr::ObjExtend(a, b) => p!(pi: {a} str(" ") {b}),
-			Expr::Parened(v) => {
-				if let Expr::Parened(_) = &v.0 as &Expr {
-					p!(pi: {v})
-				} else {
-					p!(pi: str("(") {v} str(")"))
-				}
-			}
-			Expr::UnaryOp(_, _) => todo!(),
-			Expr::BinaryOp(a, o, b) => {
-				p!(pi:
-					{a} str(" ") if(!matches!(&b.0 as &Expr, Expr::Obj(_)))({o} str(" ")) {b}
-				)
-			}
-			Expr::AssertExpr(_, _) => todo!(),
-			Expr::LocalExpr(s, v) => {
-				p!(pi:
-					str("local") nl >i
-				);
-				for spec in s.iter() {
-					p!(pi: {spec} str(";") nl)
-				}
-				p!(pi:
-					<i
-					{v}
-				);
-			}
-			Expr::Import(i) => {
-				let v = i.to_str().unwrap();
-				p!(pi: str("import \"") str(&v) str("\""));
-			}
-			Expr::ImportStr(_) => todo!(),
-			Expr::ErrorStmt(_) => todo!(),
-			Expr::Apply(f, a, t) => p!(pi:
-				{f} str("(") {a} str(")") if(*t)(str("tailstrict"))
-			),
-			Expr::Index(a, b) => p!(pi: {a} str("[") {b} str("]")),
-			Expr::Function(_, _) => todo!(),
-			Expr::Intrinsic(_) => todo!(),
-			Expr::IfElse {
-				cond,
-				cond_then,
-				cond_else,
-			} => p!(pi:
-				str("if ") {cond.0} str(" then") ifelse(cond_else.is_some())(
-					nl >i
-						{cond_then} nl
-					<i str("else") nl >i
-						{cond_else}
-					<i
-				)(str(" ") {cond_then})
-			),
-			Expr::Slice(v, d) => {
-				p!(pi:
-					{v}
-					str("[") {d.start} str(":") {d.end}
-					if(d.step.is_some())(
-						str(":")
-						{d.step}
-					)
-					str("]")
-				)
-			}
-		}
-		pi
-	}
-}
-
-impl Printable for LocExpr {
-	fn print(&self) -> PrintItems {
-		self.0.print()
-	}
-}
-
-fn main() {
-	let parsed = jrsonnet_parser::parse(
-		r#"
-	
-	
-		# Edit me!
-		local b = import "b.libsonnet";  # comment
-		local a = import "a.libsonnet";
-		
-			 local f(x,y)=x+y;
-		
-		
-		local Template = {z: "foo"};
-		
-		Template + {
-						local
-
-					h = 3,
-					assert self.a == 1
-		  
-					: "error",
-		"f": ((((((3)))))) ,
-		"g g":
-		f(4,2),
-		arr: [[
-		  1, 2,
-		  ],
-		  3,
-		  {
-			  b: {
-				  c: {
-					  k: [16]
-				  }
-			  }
-		  }
-		  ],
-		  m: a[1::],
-		  m: b[::],
-		  k: if a         == b    then 
-
-
-		  2
-
-		  else Template {}
-		}
-		
-	
-"#,
-		&ParserSettings {
-			file_name: PathBuf::from("example").into(),
-		},
-	)
-	.unwrap();
-
-	let o = dprint_core::formatting::format(
-		|| {
-			let print_items = parsed.print();
-			print_items
-		},
-		PrintOptions {
-			indent_width: 2,
-			max_width: 100,
-			use_tabs: false,
-			new_line_text: "\n",
-		},
-	);
-	println!("{}", o);
-}
modifiedcmds/jrsonnet/Cargo.tomldiffbeforeafterboth
--- a/cmds/jrsonnet/Cargo.toml
+++ b/cmds/jrsonnet/Cargo.toml
@@ -15,9 +15,12 @@
     "jrsonnet-evaluator/exp-preserve-order",
     "jrsonnet-evaluator/exp-serde-preserve-order",
     "jrsonnet-cli/exp-preserve-order",
+    "jrsonnet-cli/exp-serde-preserve-order",
 ]
 # Destructuring of locals
 exp-destruct = ["jrsonnet-evaluator/exp-destruct"]
+# std.thisFile support
+legacy-this-file = ["jrsonnet-cli/legacy-this-file"]
 
 [dependencies]
 jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.4.2" }
@@ -27,5 +30,5 @@
 
 mimallocator = { version = "0.1.3", optional = true }
 thiserror = "1.0"
-clap = { version = "3.1", features = ["derive"] }
-clap_complete = { version = "3.1" }
+clap = { version = "3.2", features = ["derive"] }
+clap_complete = { version = "3.2" }
modifiedcmds/jrsonnet/src/main.rsdiffbeforeafterboth
--- a/cmds/jrsonnet/src/main.rs
+++ b/cmds/jrsonnet/src/main.rs
@@ -1,5 +1,4 @@
 use std::{
-	env::current_dir,
 	fs::{create_dir_all, File},
 	io::{Read, Write},
 };
@@ -133,14 +132,14 @@
 
 	let input = opts.input.input.ok_or(Error::MissingInputArgument)?;
 	let val = if opts.input.exec {
-		s.evaluate_snippet("<cmdline>".to_owned(), (&input as &str).into())?
+		s.evaluate_snippet("<cmdline>".to_owned(), &input as &str)?
 	} else if input == "-" {
 		let mut input = Vec::new();
 		std::io::stdin().read_to_end(&mut input)?;
-		let input_str = std::str::from_utf8(&input)?.into();
+		let input_str = std::str::from_utf8(&input)?;
 		s.evaluate_snippet("<stdin>".to_owned(), input_str)?
 	} else {
-		s.import(s.resolve_file(&current_dir().expect("cwd"), &input)?)?
+		s.import(&input)?
 	};
 
 	let val = s.with_tla(val)?;
modifiedcrates/jrsonnet-cli/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-cli/Cargo.toml
+++ b/crates/jrsonnet-cli/Cargo.toml
@@ -7,7 +7,15 @@
 edition = "2021"
 
 [features]
-exp-preserve-order = ["jrsonnet-evaluator/exp-preserve-order"]
+exp-preserve-order = [
+    "jrsonnet-evaluator/exp-preserve-order",
+    "jrsonnet-stdlib/exp-preserve-order",
+]
+exp-serde-preserve-order = [
+    "jrsonnet-evaluator/exp-serde-preserve-order",
+    "jrsonnet-stdlib/exp-serde-preserve-order",
+]
+legacy-this-file = ["jrsonnet-stdlib/legacy-this-file"]
 
 [dependencies]
 jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.4.2", features = [
@@ -15,5 +23,6 @@
 ] }
 jrsonnet-parser = { path = "../../crates/jrsonnet-parser", version = "0.4.2" }
 jrsonnet-gcmodule = { version = "0.3.4" }
+jrsonnet-stdlib = { path = "../../crates/jrsonnet-stdlib", version = "0.4.2" }
 
-clap = { version = "3.1", features = ["derive"] }
+clap = { version = "3.2", features = ["derive"] }
deletedcrates/jrsonnet-cli/src/ext.rsdiffbeforeafterboth
--- a/crates/jrsonnet-cli/src/ext.rs
+++ /dev/null
@@ -1,118 +0,0 @@
-use std::{fs::read_to_string, str::FromStr};
-
-use clap::Parser;
-use jrsonnet_evaluator::{error::Result, State};
-
-use crate::ConfigureState;
-
-#[derive(Clone)]
-pub struct ExtStr {
-	pub name: String,
-	pub value: String,
-}
-
-impl FromStr for ExtStr {
-	type Err = &'static str;
-	fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
-		let out: Vec<_> = s.split('=').collect();
-		match out.len() {
-			1 => Ok(ExtStr {
-				name: out[0].to_owned(),
-				value: std::env::var(out[0]).or(Err("missing env var"))?,
-			}),
-			2 => Ok(ExtStr {
-				name: out[0].to_owned(),
-				value: out[1].to_owned(),
-			}),
-
-			_ => Err("bad ext-str syntax"),
-		}
-	}
-}
-
-#[derive(Clone)]
-pub struct ExtFile {
-	pub name: String,
-	pub value: String,
-}
-
-impl FromStr for ExtFile {
-	type Err = String;
-
-	fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
-		let out: Vec<&str> = s.split('=').collect();
-		if out.len() != 2 {
-			return Err("bad ext-file syntax".to_owned());
-		}
-		let file = read_to_string(&out[1]);
-		match file {
-			Ok(content) => Ok(Self {
-				name: out[0].into(),
-				value: content,
-			}),
-			Err(e) => Err(format!("{}", e)),
-		}
-	}
-}
-
-#[derive(Parser)]
-#[clap(next_help_heading = "EXTERNAL VARIABLES")]
-pub struct ExtVarOpts {
-	/// Add string external variable.
-	/// External variables are globally available so it is preferred
-	/// to use top level arguments whenever it's possible.
-	/// If [=data] is not set then it will be read from `name` env variable.
-	/// Can be accessed from code via `std.extVar("name")`.
-	#[clap(
-		long,
-		short = 'V',
-		name = "name[=var data]",
-		number_of_values = 1,
-		multiple_occurrences = true
-	)]
-	ext_str: Vec<ExtStr>,
-	/// Read string external variable from file.
-	/// See also `--ext-str`
-	#[clap(
-		long,
-		name = "name=var path",
-		number_of_values = 1,
-		multiple_occurrences = true
-	)]
-	ext_str_file: Vec<ExtFile>,
-	/// Add external variable from code.
-	/// See also `--ext-str`
-	#[clap(
-		long,
-		name = "name[=var source]",
-		number_of_values = 1,
-		multiple_occurrences = true
-	)]
-	ext_code: Vec<ExtStr>,
-	/// Read string external variable from file.
-	/// See also `--ext-str`
-	#[clap(
-		long,
-		name = "name=var code path",
-		number_of_values = 1,
-		multiple_occurrences = true
-	)]
-	ext_code_file: Vec<ExtFile>,
-}
-impl ConfigureState for ExtVarOpts {
-	fn configure(&self, s: &State) -> Result<()> {
-		for ext in self.ext_str.iter() {
-			s.add_ext_str((&ext.name as &str).into(), (&ext.value as &str).into());
-		}
-		for ext in self.ext_str_file.iter() {
-			s.add_ext_str((&ext.name as &str).into(), (&ext.value as &str).into());
-		}
-		for ext in self.ext_code.iter() {
-			s.add_ext_code(&ext.name as &str, (&ext.value as &str).into())?;
-		}
-		for ext in self.ext_code_file.iter() {
-			s.add_ext_code(&ext.name as &str, (&ext.value as &str).into())?;
-		}
-		Ok(())
-	}
-}
modifiedcrates/jrsonnet-cli/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-cli/src/lib.rs
+++ b/crates/jrsonnet-cli/src/lib.rs
@@ -1,15 +1,15 @@
-mod ext;
 mod manifest;
+mod stdlib;
 mod tla;
 mod trace;
 
 use std::{env, path::PathBuf};
 
 use clap::Parser;
-pub use ext::*;
 use jrsonnet_evaluator::{error::Result, FileImportResolver, State};
 use jrsonnet_gcmodule::with_thread_object_space;
 pub use manifest::*;
+pub use stdlib::*;
 pub use tla::*;
 pub use trace::*;
 
@@ -31,13 +31,6 @@
 #[derive(Parser)]
 #[clap(next_help_heading = "OPTIONS")]
 pub struct MiscOpts {
-	/// Disable standard library.
-	/// By default standard library will be available via global `std` variable.
-	/// Note that standard library will still be loaded
-	/// if chosen manifestification method is not `none`.
-	#[clap(long)]
-	no_stdlib: bool,
-
 	/// Maximal allowed number of stack frames,
 	/// stack overflow error will be raised if this number gets exceeded.
 	#[clap(long, short = 's', default_value = "200")]
@@ -52,17 +45,13 @@
 }
 impl ConfigureState for MiscOpts {
 	fn configure(&self, s: &State) -> Result<()> {
-		if !self.no_stdlib {
-			s.with_stdlib();
-		}
-
 		let mut library_paths = self.jpath.clone();
 		library_paths.reverse();
 		if let Some(path) = env::var_os("JSONNET_PATH") {
 			library_paths.extend(env::split_paths(path.as_os_str()));
 		}
 
-		s.set_import_resolver(Box::new(FileImportResolver { library_paths }));
+		s.set_import_resolver(Box::new(FileImportResolver::new(library_paths)));
 
 		s.set_max_stack(self.max_stack);
 		Ok(())
@@ -79,7 +68,7 @@
 	#[clap(flatten)]
 	tla: TLAOpts,
 	#[clap(flatten)]
-	ext: ExtVarOpts,
+	std: StdOpts,
 
 	#[clap(flatten)]
 	trace: TraceOpts,
@@ -91,7 +80,7 @@
 		self.trace.configure(s)?;
 		self.misc.configure(s)?;
 		self.tla.configure(s)?;
-		self.ext.configure(s)?;
+		self.std.configure(s)?;
 		Ok(())
 	}
 }
@@ -113,6 +102,8 @@
 }
 impl GcOpts {
 	pub fn stats_printer(&self) -> (Option<GcStatsPrinter>, Option<LeakSpace>) {
+		// Constructed structs have side-effects in Drop impl
+		#[allow(clippy::unnecessary_lazy_evaluations)]
 		(
 			self.gc_print_stats.then(|| GcStatsPrinter {
 				collect_before_printing_stats: self.gc_collect_before_printing_stats,
addedcrates/jrsonnet-cli/src/stdlib.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-cli/src/stdlib.rs
@@ -0,0 +1,130 @@
+use std::{fs::read_to_string, str::FromStr};
+
+use clap::Parser;
+use jrsonnet_evaluator::{error::Result, trace::PathResolver, State};
+
+use crate::ConfigureState;
+
+#[derive(Clone)]
+pub struct ExtStr {
+	pub name: String,
+	pub value: String,
+}
+
+impl FromStr for ExtStr {
+	type Err = &'static str;
+	fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+		let out: Vec<_> = s.split('=').collect();
+		match out.len() {
+			1 => Ok(ExtStr {
+				name: out[0].to_owned(),
+				value: std::env::var(out[0]).or(Err("missing env var"))?,
+			}),
+			2 => Ok(ExtStr {
+				name: out[0].to_owned(),
+				value: out[1].to_owned(),
+			}),
+
+			_ => Err("bad ext-str syntax"),
+		}
+	}
+}
+
+#[derive(Clone)]
+pub struct ExtFile {
+	pub name: String,
+	pub value: String,
+}
+
+impl FromStr for ExtFile {
+	type Err = String;
+
+	fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+		let out: Vec<&str> = s.split('=').collect();
+		if out.len() != 2 {
+			return Err("bad ext-file syntax".to_owned());
+		}
+		let file = read_to_string(out[1]);
+		match file {
+			Ok(content) => Ok(Self {
+				name: out[0].into(),
+				value: content,
+			}),
+			Err(e) => Err(format!("{}", e)),
+		}
+	}
+}
+
+#[derive(Parser)]
+#[clap(next_help_heading = "STANDARD LIBRARY")]
+pub struct StdOpts {
+	/// Disable standard library.
+	/// By default standard library will be available via global `std` variable.
+	/// Note that standard library will still be loaded
+	/// if chosen manifestification method is not `none`.
+	#[clap(long)]
+	no_stdlib: bool,
+	/// Add string external variable.
+	/// External variables are globally available so it is preferred
+	/// to use top level arguments whenever it's possible.
+	/// If [=data] is not set then it will be read from `name` env variable.
+	/// Can be accessed from code via `std.extVar("name")`.
+	#[clap(
+		long,
+		short = 'V',
+		name = "name[=var data]",
+		number_of_values = 1,
+		multiple_occurrences = true
+	)]
+	ext_str: Vec<ExtStr>,
+	/// Read string external variable from file.
+	/// See also `--ext-str`
+	#[clap(
+		long,
+		name = "name=var path",
+		number_of_values = 1,
+		multiple_occurrences = true
+	)]
+	ext_str_file: Vec<ExtFile>,
+	/// Add external variable from code.
+	/// See also `--ext-str`
+	#[clap(
+		long,
+		name = "name[=var source]",
+		number_of_values = 1,
+		multiple_occurrences = true
+	)]
+	ext_code: Vec<ExtStr>,
+	/// Read string external variable from file.
+	/// See also `--ext-str`
+	#[clap(
+		long,
+		name = "name=var code path",
+		number_of_values = 1,
+		multiple_occurrences = true
+	)]
+	ext_code_file: Vec<ExtFile>,
+}
+impl ConfigureState for StdOpts {
+	fn configure(&self, s: &State) -> Result<()> {
+		if self.no_stdlib {
+			return Ok(());
+		}
+		let ctx =
+			jrsonnet_stdlib::ContextInitializer::new(s.clone(), PathResolver::new_cwd_fallback());
+		for ext in self.ext_str.iter() {
+			ctx.add_ext_str((&ext.name as &str).into(), (&ext.value as &str).into());
+		}
+		for ext in self.ext_str_file.iter() {
+			ctx.add_ext_str((&ext.name as &str).into(), (&ext.value as &str).into());
+		}
+		for ext in self.ext_code.iter() {
+			ctx.add_ext_code(&ext.name as &str, &ext.value as &str)?;
+		}
+		for ext in self.ext_code_file.iter() {
+			ctx.add_ext_code(&ext.name as &str, &ext.value as &str)?;
+		}
+		s.settings_mut().context_initializer = Box::new(ctx);
+		Ok(())
+	}
+}
modifiedcrates/jrsonnet-cli/src/trace.rsdiffbeforeafterboth
--- a/crates/jrsonnet-cli/src/trace.rs
+++ b/crates/jrsonnet-cli/src/trace.rs
@@ -42,11 +42,7 @@
 }
 impl ConfigureState for TraceOpts {
 	fn configure(&self, s: &State) -> Result<()> {
-		let resolver = if let Ok(dir) = std::env::current_dir() {
-			PathResolver::Relative(dir)
-		} else {
-			PathResolver::Absolute
-		};
+		let resolver = PathResolver::new_cwd_fallback();
 		match self
 			.trace_format
 			.as_ref()
modifiedcrates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -7,9 +7,7 @@
 edition = "2021"
 
 [features]
-default = ["serialized-stdlib", "explaining-traces", "friendly-errors"]
-# Serializes standard library AST instead of parsing them every run
-serialized-stdlib = ["bincode", "jrsonnet-parser/serde"]
+default = ["explaining-traces", "friendly-errors"]
 # Rustc-like trace visualization
 explaining-traces = ["annotate-snippets"]
 # Allows library authors to throw custom errors
@@ -23,11 +21,12 @@
 exp-serde-preserve-order = ["serde_json/preserve_order"]
 # Implements field destructuring
 exp-destruct = ["jrsonnet-parser/exp-destruct"]
+# Provide Typed for conversions to/from serde_json::Value type
+serde_json = ["dep:serde_json"]
 
 [dependencies]
 jrsonnet-interner = { path = "../jrsonnet-interner", version = "0.4.2" }
 jrsonnet-parser = { path = "../jrsonnet-parser", version = "0.4.2" }
-jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.4.2" }
 jrsonnet-types = { path = "../jrsonnet-types", version = "0.4.2" }
 jrsonnet-macros = { path = "../jrsonnet-macros", version = "0.4.2" }
 jrsonnet-gcmodule = { version = "0.3.4" }
@@ -36,15 +35,13 @@
 hashbrown = "0.12.1"
 static_assertions = "1.1"
 
-md5 = "0.7.0"
-base64 = "0.13.0"
 rustc-hash = "1.1"
 
 thiserror = "1.0"
 
 serde = "1.0"
-serde_json = "1.0"
-serde_yaml_with_quirks = "0.8.24"
+# Optional integration
+serde_json = { version = "1.0.82", optional = true }
 
 anyhow = { version = "1.0", optional = true }
 # Friendly errors
@@ -53,9 +50,3 @@
 bincode = { version = "1.3", optional = true }
 # Explaining traces
 annotate-snippets = { version = "0.9.1", features = ["color"], optional = true }
-
-[build-dependencies]
-jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.4.2" }
-jrsonnet-parser = { path = "../jrsonnet-parser", version = "0.4.2" }
-serde = "1.0"
-bincode = "1.3"
deletedcrates/jrsonnet-evaluator/build.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/build.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-use std::{borrow::Cow, env, fs::File, io::Write, path::Path};
-
-use bincode::serialize;
-use jrsonnet_parser::{parse, ParserSettings, Source};
-use jrsonnet_stdlib::STDLIB_STR;
-
-fn main() {
-	let parsed = parse(
-		STDLIB_STR,
-		&ParserSettings {
-			file_name: Source::new_virtual(Cow::Borrowed("<std>")),
-		},
-	)
-	.expect("parse");
-
-	{
-		let out_dir = env::var("OUT_DIR").unwrap();
-		let dest_path = Path::new(&out_dir).join("stdlib.bincode");
-		let mut f = File::create(&dest_path).unwrap();
-		f.write_all(&serialize(&parsed).unwrap()).unwrap();
-	}
-}
modifiedcrates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/ctx.rs
+++ b/crates/jrsonnet-evaluator/src/ctx.rs
@@ -20,6 +20,9 @@
 	}
 }
 
+/// Context keeps information about current lexical code location
+///
+/// This information includes local variables, top-level object (`$`), current object (`this`), and super object (`super`)
 #[derive(Debug, Clone, Trace)]
 pub struct Context(Cc<ContextInternals>);
 impl Context {
@@ -138,3 +141,51 @@
 		Cc::ptr_eq(&self.0, &other.0)
 	}
 }
+
+pub struct ContextBuilder {
+	bindings: GcHashMap<IStr, Thunk<Val>>,
+	extend: Option<Context>,
+}
+
+impl ContextBuilder {
+	pub fn new() -> Self {
+		Self::with_capacity(0)
+	}
+	pub fn with_capacity(capacity: usize) -> Self {
+		Self {
+			bindings: GcHashMap::with_capacity(capacity),
+			extend: None,
+		}
+	}
+	pub fn extend(parent: Context) -> Self {
+		Self {
+			bindings: GcHashMap::new(),
+			extend: Some(parent),
+		}
+	}
+	/// # Panics
+	/// If `name` is already bound
+	pub fn bind(&mut self, name: IStr, value: Thunk<Val>) -> &mut Self {
+		let old = self.bindings.insert(name, value);
+		assert!(old.is_none(), "variable bound twice in single context call");
+		self
+	}
+	pub fn build(self) -> Context {
+		if let Some(parent) = self.extend {
+			parent.extend(self.bindings, None, None, None)
+		} else {
+			Context(Cc::new(ContextInternals {
+				bindings: LayeredHashMap::new(self.bindings),
+				dollar: None,
+				sup: None,
+				this: None,
+			}))
+		}
+	}
+}
+
+impl Default for ContextBuilder {
+	fn default() -> Self {
+		Self::new()
+	}
+}
modifiedcrates/jrsonnet-evaluator/src/dynamic.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/dynamic.rs
+++ b/crates/jrsonnet-evaluator/src/dynamic.rs
@@ -2,6 +2,7 @@
 
 use jrsonnet_gcmodule::{Cc, Trace};
 
+// TODO: Replace with OnceCell once in std
 #[derive(Clone, Trace)]
 pub struct Pending<V: Trace + 'static>(pub Cc<RefCell<Option<V>>>);
 impl<T: Trace + 'static> Pending<T> {
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -2,14 +2,11 @@
 
 use jrsonnet_gcmodule::Trace;
 use jrsonnet_interner::IStr;
-use jrsonnet_parser::{BinaryOpType, ExprLocation, Source, UnaryOpType};
+use jrsonnet_parser::{BinaryOpType, ExprLocation, Source, SourcePath, UnaryOpType};
 use jrsonnet_types::ValType;
 use thiserror::Error;
 
-use crate::{
-	stdlib::{format::FormatError, sort::SortError},
-	typed::TypeLocError,
-};
+use crate::{stdlib::format::FormatError, typed::TypeLocError};
 
 fn format_found(list: &[IStr], what: &str) -> String {
 	if list.is_empty() {
@@ -35,6 +32,31 @@
 	out
 }
 
+fn format_signature(sig: &FunctionSignature) -> String {
+	let mut out = String::new();
+	out.push_str("\nFunction has the following signature: ");
+	out.push('(');
+	if sig.is_empty() {
+		out.push_str("/*no arguments*/");
+	} else {
+		for (i, (name, has_default)) in sig.iter().enumerate() {
+			if i != 0 {
+				out.push_str(", ");
+			}
+			if let Some(name) = name {
+				out.push_str(name);
+			} else {
+				out.push_str("<unnamed>");
+			}
+			if *has_default {
+				out.push_str(" = <default>");
+			}
+		}
+	}
+	out.push(')');
+	out
+}
+
 const fn format_empty_str(str: &str) -> &str {
 	if str.is_empty() {
 		"\"\" (empty string)"
@@ -43,7 +65,12 @@
 	}
 }
 
+type FunctionSignature = Vec<(Option<IStr>, bool)>;
+
+/// Possible errors
+#[allow(missing_docs)]
 #[derive(Error, Debug, Clone, Trace)]
+#[non_exhaustive]
 pub enum Error {
 	#[error("intrinsic not found: {0}")]
 	IntrinsicNotFound(IStr),
@@ -76,7 +103,7 @@
 	#[error("duplicate local var: {0}")]
 	DuplicateLocalVar(IStr),
 
-	#[error("type mismatch: expected {}, got {2} {0}", .1.iter().map(|e| format!("{}", e)).collect::<Vec<_>>().join(", "))]
+	#[error("type mismatch: expected {}, got {2} {0}", .1.iter().map(|e| format!("{e}")).collect::<Vec<_>>().join(", "))]
 	TypeMismatch(&'static str, Vec<ValType>, ValType),
 	#[error("no such field: {}{}", format_empty_str(.0), format_found(.1, "field"))]
 	NoSuchField(IStr, Vec<IStr>),
@@ -87,10 +114,10 @@
 	UnknownFunctionParameter(String),
 	#[error("argument {0} is already bound")]
 	BindingParameterASecondTime(IStr),
-	#[error("too many args, function has {0}")]
-	TooManyArgsFunctionHas(usize),
-	#[error("function argument is not passed: {0}")]
-	FunctionParameterNotBoundInCall(IStr),
+	#[error("too many args, function has {0}{}", format_signature(.1))]
+	TooManyArgsFunctionHas(usize, FunctionSignature),
+	#[error("function argument is not passed: {}{}", .0.as_ref().map_or("<unnamed>", IStr::as_str), format_signature(.1))]
+	FunctionParameterNotBoundInCall(Option<IStr>, FunctionSignature),
 
 	#[error("external variable is not defined: {0}")]
 	UndefinedExternalVariable(IStr),
@@ -113,26 +140,31 @@
 	StandaloneSuper,
 
 	#[error("can't resolve {1} from {0}")]
-	ImportFileNotFound(PathBuf, String),
-	#[error("resolved file not found: {0}")]
-	ResolvedFileNotFound(PathBuf),
+	ImportFileNotFound(SourcePath, String),
+	#[error("can't resolve absolute {0}")]
+	AbsoluteImportFileNotFound(PathBuf),
+	#[error("resolved file not found: {:?}", .0)]
+	ResolvedFileNotFound(SourcePath),
+	#[error("can't import {0}: is a directory")]
+	ImportIsADirectory(SourcePath),
 	#[error("imported file is not valid utf-8: {0:?}")]
-	ImportBadFileUtf8(PathBuf),
+	ImportBadFileUtf8(SourcePath),
 	#[error("import io error: {0}")]
 	ImportIo(String),
-	#[error("tried to import {1} from {0}, but imports is not supported")]
-	ImportNotSupported(PathBuf, PathBuf),
+	#[error("tried to import {1} from {0}, but imports are not supported")]
+	ImportNotSupported(SourcePath, String),
+	#[error("tried to import {0}, but absolute imports are not supported")]
+	AbsoluteImportNotSupported(PathBuf),
 	#[error("can't import from virtual file")]
 	CantImportFromVirtualFile,
 	#[error(
 		"syntax error: expected {}, got {:?}",
 		.error.expected,
-		.source_code.chars().nth(error.location.offset)
+		.path.code().chars().nth(error.location.offset)
 		.map_or_else(|| "EOF".into(), |c| c.to_string())
 	)]
 	ImportSyntaxError {
 		path: Source,
-		source_code: IStr,
 		#[trace(skip)]
 		error: Box<jrsonnet_parser::ParseError>,
 	},
@@ -169,13 +201,6 @@
 	Format(#[from] FormatError),
 	#[error("type error: {0}")]
 	TypeError(TypeLocError),
-	#[error("sort error: {0}")]
-	Sort(#[from] SortError),
-
-	/// Thrown as error, as this is legacy feature, and error here
-	/// is acceptable for defeating object field cache
-	#[error("should not reach outside: std.thisFile")]
-	MagicThisFileUsed,
 
 	#[cfg(feature = "anyhow-error")]
 	#[error(transparent)]
@@ -195,9 +220,13 @@
 	}
 }
 
+/// Single stack trace frame
 #[derive(Clone, Debug, Trace)]
 pub struct StackTraceElement {
+	/// Source of this frame
+	/// Some frames only act as description, without attached source
 	pub location: Option<ExprLocation>,
+	/// Frame description
 	pub desc: String,
 }
 #[derive(Debug, Clone, Trace)]
@@ -227,7 +256,7 @@
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 		writeln!(f, "{}", self.0 .0)?;
 		for el in &self.0 .1 .0 {
-			writeln!(f, "\t{:?}", el)?;
+			writeln!(f, "\t{el:?}")?;
 		}
 		Ok(())
 	}
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -13,10 +13,9 @@
 	error::Error::*,
 	evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},
 	function::{CallLocation, FuncDesc, FuncVal},
-	stdlib::{std_slice, BUILTINS},
 	tb, throw,
 	typed::Typed,
-	val::{ArrValue, CachedUnbound, Thunk, ThunkValue},
+	val::{ArrValue, CachedUnbound, IndexableVal, Thunk, ThunkValue},
 	Context, GcHashMap, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, State,
 	Unbound, Val,
 };
@@ -270,9 +269,7 @@
 		}
 	}
 	let this = builder.build();
-	let _ctx = ctx
-		.extend(GcHashMap::new(), None, None, Some(this.clone()))
-		.into_future(fctx);
+	fctx.fill(ctx.extend(GcHashMap::new(), None, None, Some(this.clone())));
 	Ok(this)
 }
 
@@ -357,7 +354,7 @@
 	ctx: Context,
 	value: &LocExpr,
 	args: &ArgsDesc,
-	loc: CallLocation,
+	loc: CallLocation<'_>,
 	tailstrict: bool,
 ) -> Result<Val> {
 	let value = evaluate(s.clone(), ctx.clone(), value)?;
@@ -437,7 +434,7 @@
 		UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(s, ctx, v)?)?,
 		Var(name) => s.push(
 			CallLocation::new(loc),
-			|| format!("variable <{}> access", name),
+			|| format!("variable <{name}> access"),
 			|| ctx.binding(name.clone())?.evaluate(s.clone()),
 		)?,
 		Index(value, index) => {
@@ -447,7 +444,7 @@
 			) {
 				(Val::Obj(v), Val::Str(key)) => s.push(
 					CallLocation::new(loc),
-					|| format!("field <{}> access", key),
+					|| format!("field <{key}> access"),
 					|| match v.get(s.clone(), key.clone()) {
 						Ok(Some(v)) => Ok(v),
 						#[cfg(not(feature = "friendly-errors"))]
@@ -472,9 +469,6 @@
 								key.clone(),
 								heap.into_iter().map(|(_, v)| v).collect()
 							))
-						}
-						Err(e) if matches!(e.error(), MagicThisFileUsed) => {
-							Ok(Val::Str(loc.0.full_path().into()))
 						}
 						Err(e) => Err(e),
 					},
@@ -573,13 +567,6 @@
 		Function(params, body) => {
 			evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())
 		}
-		Intrinsic(name) => Val::Func(FuncVal::StaticBuiltin(
-			BUILTINS
-				.with(|b| b.get(name).copied())
-				.ok_or_else(|| IntrinsicNotFound(name.clone()))?,
-		)),
-		IntrinsicThisFile => return Err(MagicThisFileUsed.into()),
-		IntrinsicId => Val::Func(FuncVal::identity()),
 		AssertExpr(assert, returned) => {
 			evaluate_assert(s.clone(), ctx.clone(), assert)?;
 			evaluate(s, ctx, returned)?
@@ -613,7 +600,7 @@
 		}
 		Slice(value, desc) => {
 			fn parse_idx<T: Typed>(
-				loc: CallLocation,
+				loc: CallLocation<'_>,
 				s: State,
 				ctx: &Context,
 				expr: &Option<LocExpr>,
@@ -622,7 +609,7 @@
 				if let Some(value) = expr {
 					Ok(Some(s.push(
 						loc,
-						|| format!("slice {}", desc),
+						|| format!("slice {desc}"),
 						|| T::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),
 					)?))
 				} else {
@@ -635,29 +622,21 @@
 
 			let start = parse_idx(loc, s.clone(), &ctx, &desc.start, "start")?;
 			let end = parse_idx(loc, s.clone(), &ctx, &desc.end, "end")?;
-			let step = parse_idx(loc, s, &ctx, &desc.step, "step")?;
+			let step = parse_idx(loc, s.clone(), &ctx, &desc.step, "step")?;
 
-			std_slice(indexable.into_indexable()?, start, end, step)?
+			IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?, s)?
 		}
 		i @ (Import(path) | ImportStr(path) | ImportBin(path)) => {
 			let tmp = loc.clone().0;
-			let import_location = tmp
-				.path()
-				.map(|p| {
-					let mut p = p.to_owned();
-					p.pop();
-					p
-				})
-				.unwrap_or_default();
-			let resolved_path = s.resolve_file(&import_location, path as &str)?;
+			let resolved_path = s.resolve_from(tmp.source_path(), path as &str)?;
 			match i {
 				Import(_) => s.push(
 					CallLocation::new(loc),
 					|| format!("import {:?}", path.clone()),
-					|| s.import(resolved_path.clone()),
+					|| s.import_resolved(resolved_path),
 				)?,
-				ImportStr(_) => Val::Str(s.import_str(resolved_path)?),
-				ImportBin(_) => Val::Arr(ArrValue::Bytes(s.import_bin(resolved_path)?)),
+				ImportStr(_) => Val::Str(s.import_resolved_str(resolved_path)?),
+				ImportBin(_) => Val::Arr(ArrValue::Bytes(s.import_resolved_bin(resolved_path)?)),
 				_ => unreachable!(),
 			}
 		}
modifiedcrates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs
@@ -21,14 +21,17 @@
 pub fn evaluate_add_op(s: State, a: &Val, b: &Val) -> Result<Val> {
 	use Val::*;
 	Ok(match (a, b) {
+		(Str(a), Str(b)) if a.is_empty() => Val::Str(b.clone()),
+		(Str(a), Str(b)) if b.is_empty() => Val::Str(a.clone()),
 		(Str(v1), Str(v2)) => Str(((**v1).to_owned() + v2).into()),
 
 		// Can't use generic json serialization way, because it depends on number to string concatenation (std.jsonnet:890)
 		(Num(a), Str(b)) => Str(format!("{a}{b}").into()),
 		(Str(a), Num(b)) => Str(format!("{a}{b}").into()),
 
-		(Str(a), o) => Str(format!("{}{}", a, o.clone().to_string(s)?).into()),
-		(o, Str(a)) => Str(format!("{}{}", o.clone().to_string(s)?, a).into()),
+		(Str(a), o) | (o, Str(a)) if a.is_empty() => Val::Str(o.clone().to_string(s)?),
+		(Str(a), o) => Str(format!("{a}{}", o.clone().to_string(s)?).into()),
+		(o, Str(a)) => Str(format!("{}{a}", o.clone().to_string(s)?).into()),
 
 		(Obj(v1), Obj(v2)) => Obj(v2.extend_from(v1.clone())),
 		(Arr(a), Arr(b)) => {
modifiedcrates/jrsonnet-evaluator/src/function/arglike.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/arglike.rs
+++ b/crates/jrsonnet-evaluator/src/function/arglike.rs
@@ -96,6 +96,34 @@
 	fn named_names(&self, handler: &mut dyn FnMut(&IStr));
 }
 
+impl ArgsLike for Vec<Val> {
+	fn unnamed_len(&self) -> usize {
+		self.len()
+	}
+	fn unnamed_iter(
+		&self,
+		_s: State,
+		_ctx: Context,
+		_tailstrict: bool,
+		handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,
+	) -> Result<()> {
+		for (idx, el) in self.iter().enumerate() {
+			handler(idx, Thunk::evaluated(el.clone()))?;
+		}
+		Ok(())
+	}
+	fn named_iter(
+		&self,
+		_s: State,
+		_ctx: Context,
+		_tailstrict: bool,
+		_handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,
+	) -> Result<()> {
+		Ok(())
+	}
+	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}
+}
+
 impl ArgsLike for ArgsDesc {
 	fn unnamed_len(&self) -> usize {
 		self.unnamed.len()
@@ -273,7 +301,8 @@
 	}
 }
 impl_args_like! {
-	0usize; A @ B C D E F G H I J K L
+	// First argument is already in position, so count starts from 1
+	1usize; A @ B C D E F G H I J K L
 }
 
 impl ArgsLike for () {
modifiedcrates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/builtin.rs
+++ b/crates/jrsonnet-evaluator/src/function/builtin.rs
@@ -9,15 +9,28 @@
 
 #[derive(Clone, Trace)]
 pub struct BuiltinParam {
-	pub name: BuiltinParamName,
+	/// Parameter name for named call parsing
+	pub name: Option<BuiltinParamName>,
+	/// Is implementation allowed to return empty value
 	pub has_default: bool,
 }
 
-/// Do not implement it directly, instead use #[builtin] macro
+/// Description of function defined by native code
+///
+/// Prefer to use #[builtin] macro, instead of manual implementation of this trait
 pub trait Builtin: Trace {
+	/// Function name to be used in stack traces
 	fn name(&self) -> &str;
+	/// Parameter names for named calls
 	fn params(&self) -> &[BuiltinParam];
-	fn call(&self, s: State, ctx: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val>;
+	/// Call the builtin
+	fn call(
+		&self,
+		s: State,
+		ctx: Context,
+		loc: CallLocation<'_>,
+		args: &dyn ArgsLike,
+	) -> Result<Val>;
 }
 
 pub trait StaticBuiltin: Builtin + Send + Sync
@@ -35,8 +48,20 @@
 }
 impl NativeCallback {
 	#[deprecated = "prefer using builtins directly, use this interface only for bindings"]
-	pub fn new(params: Vec<BuiltinParam>, handler: TraceBox<dyn NativeCallbackHandler>) -> Self {
-		Self { params, handler }
+	pub fn new(
+		params: Vec<Cow<'static, str>>,
+		handler: TraceBox<dyn NativeCallbackHandler>,
+	) -> Self {
+		Self {
+			params: params
+				.into_iter()
+				.map(|n| BuiltinParam {
+					name: Some(n),
+					has_default: false,
+				})
+				.collect(),
+			handler,
+		}
 	}
 }
 
@@ -51,13 +76,20 @@
 		&self.params
 	}
 
-	fn call(&self, s: State, ctx: Context, _loc: CallLocation, args: &dyn ArgsLike) -> Result<Val> {
+	fn call(
+		&self,
+		s: State,
+		ctx: Context,
+		_loc: CallLocation<'_>,
+		args: &dyn ArgsLike,
+	) -> Result<Val> {
 		let args = parse_builtin_call(s.clone(), ctx, &self.params, args, true)?;
-		let mut out_args = Vec::with_capacity(self.params.len());
-		for p in &self.params {
-			out_args.push(args[&p.name].evaluate(s.clone())?);
-		}
-		self.handler.call(s, &out_args)
+		let args = args
+			.into_iter()
+			.map(|a| a.expect("legacy natives have no default params"))
+			.map(|a| a.evaluate(s.clone()))
+			.collect::<Result<Vec<Val>>>()?;
+		self.handler.call(s, &args)
 	}
 }
 
modifiedcrates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/mod.rs
+++ b/crates/jrsonnet-evaluator/src/function/mod.rs
@@ -18,43 +18,50 @@
 pub mod native;
 pub mod parse;
 
+/// Function callsite location.
+/// Either from other jsonnet code, specified by expression location, or from native (without location).
 #[derive(Clone, Copy)]
 pub struct CallLocation<'l>(pub Option<&'l ExprLocation>);
 impl<'l> CallLocation<'l> {
+	/// Construct new location for calls coming from specified jsonnet expression location.
 	pub const fn new(loc: &'l ExprLocation) -> Self {
 		Self(Some(loc))
 	}
 }
 impl CallLocation<'static> {
+	/// Construct new location for calls coming from native code.
 	pub const fn native() -> Self {
 		Self(None)
 	}
 }
 
-/// Function implemented in jsonnet
+/// Represents Jsonnet function defined in code.
 #[derive(Debug, PartialEq, Trace)]
 pub struct FuncDesc {
-	/// In expressions like
+	/// # Example
+	///
+	/// In expressions like this, deducted to `a`, unspecified otherwise.
 	/// ```jsonnet
 	/// local a = function() ...
 	/// local a() ...
 	/// { a: function() ... }
 	/// { a() = ... }
 	/// ```
-	///
-	/// Deducted to `a`, unspecified otherwise
 	pub name: IStr,
-	/// Context, in which this function was evaluated
+	/// Context, in which this function was evaluated.
 	///
-	/// I.e in
+	/// # Example
+	/// In
 	/// ```jsonnet
 	/// local a = 2;
 	/// function() ...
 	/// ```
-	/// context will contain `a`
+	/// context will contain `a`.
 	pub ctx: Context,
 
+	/// Function parameter definition
 	pub params: ParamsDesc,
+	/// Function body
 	pub body: LocExpr,
 }
 impl FuncDesc {
@@ -82,17 +89,17 @@
 	}
 }
 
-/// Any possible function value, including plain functions and user-provided builtins
+/// Represents a Jsonnet function value, including plain functions and user-provided builtins.
 #[allow(clippy::module_name_repetitions)]
 #[derive(Trace, Clone)]
 pub enum FuncVal {
-	/// std.id
+	/// Identity function, kept this way for comparsions.
 	Id,
-	/// Plain function implemented in jsonnet
+	/// Plain function implemented in jsonnet.
 	Normal(Cc<FuncDesc>),
-	/// Standard library function
+	/// Standard library function.
 	StaticBuiltin(#[trace(skip)] &'static dyn StaticBuiltin),
-	/// User-provided function
+	/// User-provided function.
 	Builtin(Cc<TraceBox<dyn Builtin>>),
 }
 
@@ -110,9 +117,7 @@
 }
 
 impl FuncVal {
-	pub fn into_native<D: NativeDesc>(self) -> D::Value {
-		D::into_native(self)
-	}
+	/// Amount of non-default required arguments
 	pub fn params_len(&self) -> usize {
 		match self {
 			Self::Id => 1,
@@ -121,6 +126,7 @@
 			Self::Builtin(i) => i.params().iter().filter(|p| !p.has_default).count(),
 		}
 	}
+	/// Function name, as defined in code.
 	pub fn name(&self) -> IStr {
 		match self {
 			Self::Id => "id".into(),
@@ -129,11 +135,14 @@
 			Self::Builtin(builtin) => builtin.name().into(),
 		}
 	}
+	/// Call function using arguments evaluated in specified `call_ctx` [`Context`].
+	///
+	/// If `tailstrict` is specified - then arguments will be evaluated before being passed to function body.
 	pub fn evaluate(
 		&self,
 		s: State,
 		call_ctx: Context,
-		loc: CallLocation,
+		loc: CallLocation<'_>,
 		args: &dyn ArgsLike,
 		tailstrict: bool,
 	) -> Result<Val> {
@@ -156,13 +165,22 @@
 			Self::Builtin(b) => b.call(s, call_ctx, loc, args),
 		}
 	}
+	/// Helper method, which calls [`Self::evaluate`] with sensible defaults for native code.
 	pub fn evaluate_simple(&self, s: State, args: &dyn ArgsLike) -> Result<Val> {
 		self.evaluate(s, Context::default(), CallLocation::native(), args, true)
 	}
+	/// Convert jsonnet function to plain `Fn` value.
+	pub fn into_native<D: NativeDesc>(self) -> D::Value {
+		D::into_native(self)
+	}
 
+	/// Is this function an indentity function.
+	///
+	/// Currently only works for builtin `std.id`, aka `Self::Id` value, `function(x) x` defined by jsonnet will not count as identity.
 	pub const fn is_identity(&self) -> bool {
 		matches!(self, Self::Id)
 	}
+	/// Identity function value.
 	pub const fn identity() -> Self {
 		Self::Id
 	}
modifiedcrates/jrsonnet-evaluator/src/function/native.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/native.rs
+++ b/crates/jrsonnet-evaluator/src/function/native.rs
@@ -1,5 +1,5 @@
 use super::{arglike::ArgLike, CallLocation, FuncVal};
-use crate::{error::Result, typed::Typed, State};
+use crate::{error::Result, typed::Typed, Context, State};
 
 pub trait NativeDesc {
 	type Value;
@@ -19,7 +19,8 @@
 				Box::new(move |s: State, $($gen),*| {
 					let val = val.evaluate(
 						s.clone(),
-						s.create_default_context(),
+						// This isn't intended to be used with ArgsDesc
+						Context::default(),
 						CallLocation::native(),
 						&($($gen,)*),
 						true
modifiedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/parse.rs
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -1,11 +1,10 @@
+use std::mem::replace;
+
 use jrsonnet_gcmodule::Trace;
 use jrsonnet_interner::IStr;
 use jrsonnet_parser::{LocExpr, ParamsDesc};
 
-use super::{
-	arglike::ArgsLike,
-	builtin::{BuiltinParam, BuiltinParamName},
-};
+use super::{arglike::ArgsLike, builtin::BuiltinParam};
 use crate::{
 	destructure::destruct,
 	error::{Error::*, Result},
@@ -48,7 +47,10 @@
 ) -> Result<Context> {
 	let mut passed_args = GcHashMap::with_capacity(params.len());
 	if args.unnamed_len() > params.len() {
-		throw!(TooManyArgsFunctionHas(params.len()))
+		throw!(TooManyArgsFunctionHas(
+			params.len(),
+			params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()
+		))
 	}
 
 	let mut filled_named = 0;
@@ -122,11 +124,8 @@
 				});
 				if !found {
 					throw!(FunctionParameterNotBoundInCall(
-						param
-							.0
-							.clone()
-							.name()
-							.unwrap_or_else(|| "<destruct>".into())
+						param.0.clone().name(),
+						params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()
 					));
 				}
 			}
@@ -156,28 +155,33 @@
 	params: &[BuiltinParam],
 	args: &dyn ArgsLike,
 	tailstrict: bool,
-) -> Result<GcHashMap<BuiltinParamName, Thunk<Val>>> {
-	let mut passed_args = GcHashMap::with_capacity(params.len());
+) -> Result<Vec<Option<Thunk<Val>>>> {
+	let mut passed_args: Vec<Option<Thunk<Val>>> = vec![None; params.len()];
 	if args.unnamed_len() > params.len() {
-		throw!(TooManyArgsFunctionHas(params.len()))
+		throw!(TooManyArgsFunctionHas(
+			params.len(),
+			params
+				.iter()
+				.map(|p| (p.name.as_ref().map(|v| v.as_ref().into()), p.has_default))
+				.collect()
+		))
 	}
 
 	let mut filled_args = 0;
 
 	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {
-		let name = params[id].name.clone();
-		passed_args.insert(name, arg);
+		passed_args[id] = Some(arg);
 		filled_args += 1;
 		Ok(())
 	})?;
 
 	args.named_iter(s, ctx, tailstrict, &mut |name, arg| {
 		// FIXME: O(n) for arg existence check
-		let p = params
+		let id = params
 			.iter()
-			.find(|p| p.name == name as &str)
+			.position(|p| p.name.as_ref().map_or(false, |v| v as &str == name as &str))
 			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;
-		if passed_args.insert(p.name.clone(), arg).is_some() {
+		if replace(&mut passed_args[id], Some(arg)).is_some() {
 			throw!(BindingParameterASecondTime(name.clone()));
 		}
 		filled_args += 1;
@@ -185,8 +189,8 @@
 	})?;
 
 	if filled_args < params.len() {
-		for param in params.iter().filter(|p| p.has_default) {
-			if passed_args.contains_key(&param.name) {
+		for (id, _) in params.iter().enumerate().filter(|(_, p)| p.has_default) {
+			if passed_args[id].is_some() {
 				continue;
 			}
 			filled_args += 1;
@@ -197,12 +201,22 @@
 			for param in params.iter().skip(args.unnamed_len()) {
 				let mut found = false;
 				args.named_names(&mut |name| {
-					if name as &str == &param.name as &str {
+					if param
+						.name
+						.as_ref()
+						.map_or(false, |v| v as &str == name as &str)
+					{
 						found = true;
 					}
 				});
 				if !found {
-					throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));
+					throw!(FunctionParameterNotBoundInCall(
+						param.name.as_ref().map(|v| v.as_ref().into()),
+						params
+							.iter()
+							.map(|p| (p.name.as_ref().map(|p| p.as_ref().into()), p.has_default))
+							.collect()
+					));
 				}
 			}
 			unreachable!();
@@ -215,11 +229,15 @@
 /// and with unbound values causing error to be returned
 pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {
 	#[derive(Trace)]
-	struct DependsOnUnbound(IStr);
+	struct DependsOnUnbound(IStr, ParamsDesc);
 	impl ThunkValue for DependsOnUnbound {
 		type Output = Val;
 		fn get(self: Box<Self>, _: State) -> Result<Val> {
-			Err(FunctionParameterNotBoundInCall(self.0.clone()).into())
+			Err(FunctionParameterNotBoundInCall(
+				Some(self.0.clone()),
+				self.1.iter().map(|p| (p.0.name(), p.1.is_some())).collect(),
+			)
+			.into())
 		}
 	}
 
@@ -243,7 +261,8 @@
 			destruct(
 				&param.0,
 				Thunk::new(tb!(DependsOnUnbound(
-					param.0.name().unwrap_or_else(|| "<destruct>".into())
+					param.0.name().unwrap_or_else(|| "<destruct>".into()),
+					params.clone()
 				))),
 				fctx.clone(),
 				&mut bindings,
modifiedcrates/jrsonnet-evaluator/src/gc.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/gc.rs
+++ b/crates/jrsonnet-evaluator/src/gc.rs
@@ -22,7 +22,7 @@
 }
 
 impl<T: ?Sized + Trace> Trace for TraceBox<T> {
-	fn trace(&self, tracer: &mut Tracer) {
+	fn trace(&self, tracer: &mut Tracer<'_>) {
 		self.0.trace(tracer);
 	}
 
@@ -53,25 +53,25 @@
 
 impl<T: ?Sized> Borrow<T> for TraceBox<T> {
 	fn borrow(&self) -> &T {
-		&*self.0
+		&self.0
 	}
 }
 
 impl<T: ?Sized> BorrowMut<T> for TraceBox<T> {
 	fn borrow_mut(&mut self) -> &mut T {
-		&mut *self.0
+		&mut self.0
 	}
 }
 
 impl<T: ?Sized> AsRef<T> for TraceBox<T> {
 	fn as_ref(&self) -> &T {
-		&*self.0
+		&self.0
 	}
 }
 
 impl<T: ?Sized> AsMut<T> for TraceBox<T> {
 	fn as_mut(&mut self) -> &mut T {
-		&mut *self.0
+		&mut self.0
 	}
 }
 
@@ -92,7 +92,7 @@
 where
 	V: Trace,
 {
-	fn trace(&self, tracer: &mut jrsonnet_gcmodule::Tracer) {
+	fn trace(&self, tracer: &mut Tracer<'_>) {
 		for v in &self.0 {
 			v.trace(tracer);
 		}
@@ -133,7 +133,7 @@
 	K: Trace,
 	V: Trace,
 {
-	fn trace(&self, tracer: &mut jrsonnet_gcmodule::Tracer) {
+	fn trace(&self, tracer: &mut Tracer<'_>) {
 		for (k, v) in &self.0 {
 			k.trace(tracer);
 			v.trace(tracer);
modifiedcrates/jrsonnet-evaluator/src/import.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/import.rs
+++ b/crates/jrsonnet-evaluator/src/import.rs
@@ -1,47 +1,60 @@
 use std::{
 	any::Any,
+	cell::RefCell,
+	env::current_dir,
 	fs,
-	io::Read,
+	io::{ErrorKind, Read},
 	path::{Path, PathBuf},
 };
 
 use fs::File;
+use jrsonnet_parser::{SourceDirectory, SourceFile, SourcePath};
 
 use crate::{
-	error::{Error::*, Result},
+	error::{
+		Error::{self, *},
+		Result,
+	},
 	throw,
 };
 
 /// Implements file resolution logic for `import` and `importStr`
 pub trait ImportResolver {
-	/// Resolves real file path, e.g. `(/home/user/manifests, b.libjsonnet)` can correspond
+	/// Resolves file path, e.g. `(/home/user/manifests, b.libjsonnet)` can correspond
 	/// both to `/home/user/manifests/b.libjsonnet` and to `/home/user/${vendor}/b.libjsonnet`
 	/// where `${vendor}` is a library path.
-	fn resolve_file(&self, from: &Path, path: &str) -> Result<PathBuf>;
+	///
+	/// `from` should only be returned from [`ImportResolver::resolve`], or from other defined file, any other value
+	/// may result in panic
+	fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {
+		throw!(ImportNotSupported(from.clone(), path.into()))
+	}
+	fn resolve_from_default(&self, path: &str) -> Result<SourcePath> {
+		self.resolve_from(&SourcePath::default(), path)
+	}
+	/// Resolves absolute path, doesn't supports jpath and other fancy things
+	fn resolve(&self, path: &Path) -> Result<SourcePath> {
+		throw!(AbsoluteImportNotSupported(path.to_owned()))
+	}
 
-	fn load_file_contents(&self, resolved: &Path) -> Result<Vec<u8>>;
+	/// Load resolved file
+	/// This should only be called with value returned from [`ImportResolver::resolve_file`]/[`ImportResolver::resolve`],
+	/// this cannot be resolved using associated type, as evaluator uses object instead of generic for [`ImportResolver`]
+	fn load_file_contents(&self, resolved: &SourcePath) -> Result<Vec<u8>>;
 
-	/// # Safety
-	///
-	/// For use only in bindings, should not be used elsewhere.
-	/// Implementations which are not intended to be used in bindings
-	/// should panic on call to this method.
-	unsafe fn as_any(&self) -> &dyn Any;
+	/// For downcasts
+	fn as_any(&self) -> &dyn Any;
 }
 
 /// Dummy resolver, can't resolve/load any file
 pub struct DummyImportResolver;
 impl ImportResolver for DummyImportResolver {
-	fn resolve_file(&self, from: &Path, path: &str) -> Result<PathBuf> {
-		throw!(ImportNotSupported(from.into(), path.into()))
-	}
-
-	fn load_file_contents(&self, _resolved: &Path) -> Result<Vec<u8>> {
+	fn load_file_contents(&self, _resolved: &SourcePath) -> Result<Vec<u8>> {
 		panic!("dummy resolver can't load any file")
 	}
 
-	unsafe fn as_any(&self) -> &dyn Any {
-		panic!("`as_any($self)` is not supported by dummy resolver")
+	fn as_any(&self) -> &dyn Any {
+		self
 	}
 }
 #[allow(clippy::use_self)]
@@ -56,34 +69,91 @@
 pub struct FileImportResolver {
 	/// Library directories to search for file.
 	/// Referred to as `jpath` in original jsonnet implementation.
-	pub library_paths: Vec<PathBuf>,
+	library_paths: RefCell<Vec<PathBuf>>,
+}
+impl FileImportResolver {
+	pub fn new(jpath: Vec<PathBuf>) -> Self {
+		Self {
+			library_paths: RefCell::new(jpath),
+		}
+	}
+	/// Dynamically add new jpath, used by bindings
+	pub fn add_jpath(&self, path: PathBuf) {
+		self.library_paths.borrow_mut().push(path);
+	}
 }
 impl ImportResolver for FileImportResolver {
-	fn resolve_file(&self, from: &Path, path: &str) -> Result<PathBuf> {
-		let mut direct = from.to_path_buf();
+	fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {
+		let mut direct = if let Some(f) = from.downcast_ref::<SourceFile>() {
+			let mut o = f.path().to_owned();
+			o.pop();
+			o
+		} else if let Some(d) = from.downcast_ref::<SourceDirectory>() {
+			d.path().to_owned()
+		} else if from.is_default() {
+			current_dir().map_err(|e| Error::ImportIo(e.to_string()))?
+		} else {
+			unreachable!("resolver can't return this path")
+		};
 		direct.push(path);
-		if direct.exists() {
-			Ok(direct.canonicalize().map_err(|e| ImportIo(e.to_string()))?)
+		if direct.is_file() {
+			Ok(SourcePath::new(SourceFile::new(
+				direct.canonicalize().map_err(|e| ImportIo(e.to_string()))?,
+			)))
 		} else {
-			for library_path in &self.library_paths {
+			for library_path in self.library_paths.borrow().iter() {
 				let mut cloned = library_path.clone();
 				cloned.push(path);
 				if cloned.exists() {
-					return Ok(cloned.canonicalize().map_err(|e| ImportIo(e.to_string()))?);
+					return Ok(SourcePath::new(SourceFile::new(
+						cloned.canonicalize().map_err(|e| ImportIo(e.to_string()))?,
+					)));
 				}
 			}
-			throw!(ImportFileNotFound(from.to_owned(), path.to_owned()))
+			throw!(ImportFileNotFound(from.clone(), path.to_owned()))
 		}
 	}
+	fn resolve(&self, path: &Path) -> Result<SourcePath> {
+		let meta = match fs::metadata(path) {
+			Ok(v) => v,
+			Err(e) if e.kind() == ErrorKind::NotFound => {
+				throw!(AbsoluteImportFileNotFound(path.to_owned()))
+			}
+			Err(e) => throw!(Error::ImportIo(e.to_string())),
+		};
+		if meta.is_file() {
+			Ok(SourcePath::new(SourceFile::new(
+				path.canonicalize().map_err(|e| ImportIo(e.to_string()))?,
+			)))
+		} else if meta.is_dir() {
+			Ok(SourcePath::new(SourceDirectory::new(
+				path.canonicalize().map_err(|e| ImportIo(e.to_string()))?,
+			)))
+		} else {
+			unreachable!("this can't be a symlink")
+		}
+	}
 
-	fn load_file_contents(&self, id: &Path) -> Result<Vec<u8>> {
-		let mut file = File::open(id).map_err(|_e| ResolvedFileNotFound(id.to_owned()))?;
+	fn load_file_contents(&self, id: &SourcePath) -> Result<Vec<u8>> {
+		let path = if let Some(f) = id.downcast_ref::<SourceFile>() {
+			f.path()
+		} else if id.downcast_ref::<SourceDirectory>().is_some() || id.is_default() {
+			throw!(Error::ImportIsADirectory(id.clone()))
+		} else {
+			unreachable!("other types are not supported in resolve");
+		};
+		let mut file = File::open(path).map_err(|_e| ResolvedFileNotFound(id.clone()))?;
 		let mut out = Vec::new();
 		file.read_to_end(&mut out)
 			.map_err(|e| ImportIo(e.to_string()))?;
 		Ok(out)
 	}
-	unsafe fn as_any(&self) -> &dyn Any {
-		panic!("this resolver can't be used as any")
+
+	fn as_any(&self) -> &dyn Any {
+		self
+	}
+
+	fn resolve_from_default(&self, path: &str) -> Result<SourcePath> {
+		self.resolve_from(&SourcePath::default(), path)
 	}
 }
modifiedcrates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/integrations/serde.rs
+++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs
@@ -16,7 +16,7 @@
 			Self::Null => Val::Null,
 			Self::Bool(v) => Val::Bool(v),
 			Self::Number(n) => Val::Num(n.as_f64().ok_or_else(|| {
-				RuntimeError(format!("json number can't be represented as jsonnet: {}", n).into())
+				RuntimeError(format!("json number can't be represented as jsonnet: {n}").into())
 			})?),
 			Self::String(s) => Val::Str((&s as &str).into()),
 			Self::Array(a) => {
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -1,4 +1,18 @@
-#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
+//! jsonnet interpreter implementation
+
+#![deny(unsafe_op_in_unsafe_fn)]
+#![warn(
+	clippy::all,
+	clippy::nursery,
+	clippy::pedantic,
+	// missing_docs,
+	elided_lifetimes_in_paths,
+	explicit_outlives_requirements,
+	noop_method_call,
+	single_use_lifetimes,
+	variant_size_differences,
+	rustdoc::all
+)]
 #![allow(
 	macro_expanded_macro_exports_accessed_by_absolute_paths,
 	clippy::ptr_arg,
@@ -37,17 +51,17 @@
 mod integrations;
 mod map;
 mod obj;
-mod stdlib;
+pub mod stdlib;
 pub mod trace;
 pub mod typed;
 pub mod val;
 
 use std::{
-	borrow::Cow,
+	any::Any,
 	cell::{Ref, RefCell, RefMut},
 	collections::HashMap,
 	fmt::{self, Debug},
-	path::{Path, PathBuf},
+	path::Path,
 	rc::Rc,
 };
 
@@ -55,36 +69,44 @@
 pub use dynamic::*;
 use error::{Error::*, LocError, Result, StackTraceElement};
 pub use evaluate::*;
-use function::{builtin::Builtin, CallLocation, TlaArg};
+use function::{CallLocation, TlaArg};
 use gc::{GcHashMap, TraceBox};
 use hashbrown::hash_map::RawEntryMut;
 pub use import::*;
 use jrsonnet_gcmodule::{Cc, Trace};
-use jrsonnet_interner::IBytes;
-pub use jrsonnet_interner::IStr;
+pub use jrsonnet_interner::{IBytes, IStr};
 pub use jrsonnet_parser as parser;
 use jrsonnet_parser::*;
 pub use obj::*;
-use trace::{location_to_offset, offset_to_location, CodeLocation, CompactFormat, TraceFormat};
+use trace::{CompactFormat, TraceFormat};
 pub use val::{ManifestFormat, Thunk, Val};
 
+/// Thunk without bound `super`/`this`
+/// object inheritance may be overriden multiple times, and will be fixed only on field read
 pub trait Unbound: Trace {
+	/// Type of value after object context is bound
 	type Bound;
+	/// Create value bound to specified object context
 	fn bind(&self, s: State, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Self::Bound>;
 }
 
+/// Object fields may, or may not depend on `this`/`super`, this enum allows cheaper reuse of object-independent fields for native code
+/// Standard jsonnet fields are always unbound
 #[derive(Clone, Trace)]
-pub enum LazyBinding {
-	Bindable(Cc<TraceBox<dyn Unbound<Bound = Thunk<Val>>>>),
+pub enum MaybeUnbound {
+	/// Value needs to be bound to `this`/`super`
+	Unbound(Cc<TraceBox<dyn Unbound<Bound = Thunk<Val>>>>),
+	/// Value is object-independent
 	Bound(Thunk<Val>),
 }
 
-impl Debug for LazyBinding {
+impl Debug for MaybeUnbound {
 	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		write!(f, "LazyBinding")
+		write!(f, "MaybeUnbound")
 	}
 }
-impl LazyBinding {
+impl MaybeUnbound {
+	/// Attach object context to value, if required
 	pub fn evaluate(
 		&self,
 		s: State,
@@ -92,25 +114,44 @@
 		this: Option<ObjValue>,
 	) -> Result<Thunk<Val>> {
 		match self {
-			Self::Bindable(v) => v.bind(s, sup, this),
+			Self::Unbound(v) => v.bind(s, sup, this),
 			Self::Bound(v) => Ok(v.clone()),
 		}
 	}
 }
 
+/// During import, this trait will be called to create initial context for file.
+/// It may initialize global variables, stdlib for example.
+pub trait ContextInitializer {
+	/// Initialize default file context.
+	fn initialize(&self, state: State, for_file: Source) -> Context;
+	/// Allows upcasting from abstract to concrete context initializer.
+	/// jrsonnet by itself doesn't use this method, it is allowed for it to panic.
+	fn as_any(&self) -> &dyn Any;
+}
+
+/// Context initializer which adds nothing.
+pub struct DummyContextInitializer;
+impl ContextInitializer for DummyContextInitializer {
+	fn initialize(&self, _state: State, _for_file: Source) -> Context {
+		Context::default()
+	}
+	fn as_any(&self) -> &dyn Any {
+		self
+	}
+}
+
+/// Dynamically reconfigurable evaluation settings
 pub struct EvaluationSettings {
 	/// Limits recursion by limiting the number of stack frames
 	pub max_stack: usize,
 	/// Limits amount of stack trace items preserved
 	pub max_trace: usize,
-	/// Used for s`td.extVar`
-	pub ext_vars: HashMap<IStr, TlaArg>,
-	/// Used for ext.native
-	pub ext_natives: HashMap<IStr, Cc<TraceBox<dyn Builtin>>>,
 	/// TLA vars
 	pub tla_vars: HashMap<IStr, TlaArg>,
-	/// Global variables are inserted in default context
-	pub globals: HashMap<IStr, Val>,
+	/// Context initializer, which will be used for imports and everything
+	/// [`NoopContextInitializer`] is used by default, most likely you want to have `jrsonnet-stdlib`
+	pub context_initializer: Box<dyn ContextInitializer>,
 	/// Used to resolve file locations/contents
 	pub import_resolver: Box<dyn ImportResolver>,
 	/// Used in manifestification functions
@@ -123,9 +164,7 @@
 		Self {
 			max_stack: 200,
 			max_trace: 20,
-			globals: HashMap::default(),
-			ext_vars: HashMap::default(),
-			ext_natives: HashMap::default(),
+			context_initializer: Box::new(DummyContextInitializer),
 			tla_vars: HashMap::default(),
 			import_resolver: Box::new(DummyImportResolver),
 			manifest_format: ManifestFormat::Json {
@@ -151,9 +190,7 @@
 	breakpoints: Breakpoints,
 
 	/// Contains file source codes and evaluation results for imports and pretty-printed stacktraces
-	files: GcHashMap<PathBuf, FileData>,
-	/// Contains tla arguments and others, which aren't needed to be obtained by name
-	volatile_files: GcHashMap<String, String>,
+	files: GcHashMap<SourcePath, FileData>,
 }
 struct FileData {
 	string: Option<IStr>,
@@ -229,7 +266,8 @@
 pub struct State(Rc<EvaluationStateInternals>);
 
 impl State {
-	pub fn import_str(&self, path: PathBuf) -> Result<IStr> {
+	/// Should only be called with path retrieved from [`resolve_path`], may panic otherwise
+	pub fn import_resolved_str(&self, path: SourcePath) -> Result<IStr> {
 		let mut data = self.data_mut();
 		let mut file = data.files.raw_entry_mut().from_key(&path);
 
@@ -263,7 +301,8 @@
 		}
 		Ok(file.string.as_ref().expect("just set").clone())
 	}
-	pub fn import_bin(&self, path: PathBuf) -> Result<IBytes> {
+	/// Should only be called with path retrieved from [`resolve_path`], may panic otherwise
+	pub fn import_resolved_bin(&self, path: SourcePath) -> Result<IBytes> {
 		let mut data = self.data_mut();
 		let mut file = data.files.raw_entry_mut().from_key(&path);
 
@@ -289,7 +328,8 @@
 		}
 		Ok(file.bytes.as_ref().expect("just set").clone())
 	}
-	pub fn import(&self, path: PathBuf) -> Result<Val> {
+	/// Should only be called with path retrieved from [`resolve_path`], may panic otherwise
+	pub fn import_resolved(&self, path: SourcePath) -> Result<Val> {
 		let mut data = self.data_mut();
 		let mut file = data.files.raw_entry_mut().from_key(&path);
 
@@ -323,7 +363,7 @@
 			);
 		}
 		let code = file.string.as_ref().expect("just set");
-		let file_name = Source::new(path.clone()).expect("resolver should return correct name");
+		let file_name = Source::new(path.clone(), code.clone());
 		if file.parsed.is_none() {
 			file.parsed = Some(
 				jrsonnet_parser::parse(
@@ -333,8 +373,7 @@
 					},
 				)
 				.map_err(|e| ImportSyntaxError {
-					path: file_name,
-					source_code: code.clone(),
+					path: file_name.clone(),
 					error: Box::new(e),
 				})?,
 			);
@@ -346,7 +385,11 @@
 		file.evaluating = true;
 		// Dropping file here, as it borrows data, which may be used in evaluation
 		drop(data);
-		let res = evaluate(self.clone(), self.create_default_context(), &parsed);
+		let res = evaluate(
+			self.clone(),
+			self.create_default_context(file_name),
+			&parsed,
+		);
 
 		let mut data = self.data_mut();
 		let mut file = data.files.raw_entry_mut().from_key(&path);
@@ -365,58 +408,26 @@
 		}
 	}
 
-	pub fn get_source(&self, name: Source) -> Option<String> {
-		let data = self.data();
-		match name.repr() {
-			Ok(real) => data
-				.files
-				.get(real)
-				.and_then(|f| f.string.as_ref())
-				.map(ToString::to_string),
-			Err(e) => data.volatile_files.get(e).map(ToOwned::to_owned),
-		}
-	}
-	pub fn map_source_locations(&self, file: Source, locs: &[u32]) -> Vec<CodeLocation> {
-		offset_to_location(&self.get_source(file).unwrap_or_else(|| "".into()), locs)
-	}
-	pub fn map_from_source_location(
-		&self,
-		file: Source,
-		line: usize,
-		column: usize,
-	) -> Option<usize> {
-		location_to_offset(
-			&self.get_source(file).expect("file not found"),
-			line,
-			column,
-		)
+	/// Has same semantics as `import 'path'` called from `from` file
+	pub fn import_from(&self, from: &SourcePath, path: &str) -> Result<Val> {
+		let resolved = self.resolve_from(from, path)?;
+		self.import_resolved(resolved)
 	}
-	/// Adds standard library global variable (std) to this evaluator
-	pub fn with_stdlib(&self) -> &Self {
-		let val = evaluate(
-			self.clone(),
-			self.create_default_context(),
-			&stdlib::get_parsed_stdlib(),
-		)
-		.expect("std should not fail");
-		self.settings_mut().globals.insert("std".into(), val);
-		self
+	pub fn import(&self, path: impl AsRef<Path>) -> Result<Val> {
+		let resolved = self.resolve(path)?;
+		self.import_resolved(resolved)
 	}
 
 	/// Creates context with all passed global variables
-	pub fn create_default_context(&self) -> Context {
-		let globals = &self.settings().globals;
-		let mut new_bindings = GcHashMap::with_capacity(globals.len());
-		for (name, value) in globals.iter() {
-			new_bindings.insert(name.clone(), Thunk::evaluated(value.clone()));
-		}
-		Context::new().extend(new_bindings, None, None, None)
+	pub fn create_default_context(&self, source: Source) -> Context {
+		let context_initializer = &self.settings().context_initializer;
+		context_initializer.initialize(self.clone(), source)
 	}
 
 	/// Executes code creating a new stack frame
 	pub fn push<T>(
 		&self,
-		e: CallLocation,
+		e: CallLocation<'_>,
 		frame_desc: impl FnOnce() -> String,
 		f: impl FnOnce() -> Result<T>,
 	) -> Result<T> {
@@ -545,7 +556,10 @@
 				|| {
 					func.evaluate(
 						self.clone(),
-						self.create_default_context(),
+						self.create_default_context(Source::new_virtual(
+							"<tla>".into(),
+							IStr::empty(),
+						)),
 						CallLocation::native(),
 						&self.settings().tla_vars,
 						true,
@@ -559,16 +573,13 @@
 
 /// Internals
 impl State {
-	fn data(&self) -> Ref<EvaluationData> {
-		self.0.data.borrow()
-	}
-	fn data_mut(&self) -> RefMut<EvaluationData> {
+	fn data_mut(&self) -> RefMut<'_, EvaluationData> {
 		self.0.data.borrow_mut()
 	}
-	pub fn settings(&self) -> Ref<EvaluationSettings> {
+	pub fn settings(&self) -> Ref<'_, EvaluationSettings> {
 		self.0.settings.borrow()
 	}
-	pub fn settings_mut(&self) -> RefMut<EvaluationSettings> {
+	pub fn settings_mut(&self) -> RefMut<'_, EvaluationSettings> {
 		self.0.settings.borrow_mut()
 	}
 }
@@ -576,8 +587,9 @@
 /// Raw methods evaluate passed values but don't perform TLA execution
 impl State {
 	/// Parses and evaluates the given snippet
-	pub fn evaluate_snippet(&self, name: String, code: String) -> Result<Val> {
-		let source = Source::new_virtual(Cow::Owned(name.clone()));
+	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(
 			&code,
 			&ParserSettings {
@@ -585,48 +597,15 @@
 			},
 		)
 		.map_err(|e| ImportSyntaxError {
-			path: source,
-			source_code: code.clone().into(),
+			path: source.clone(),
 			error: Box::new(e),
 		})?;
-		self.data_mut().volatile_files.insert(name, code);
-		evaluate(self.clone(), self.create_default_context(), &parsed)
+		evaluate(self.clone(), self.create_default_context(source), &parsed)
 	}
 }
 
 /// Settings utilities
 impl State {
-	pub fn add_ext_var(&self, name: IStr, value: Val) {
-		self.settings_mut()
-			.ext_vars
-			.insert(name, TlaArg::Val(value));
-	}
-	pub fn add_ext_str(&self, name: IStr, value: IStr) {
-		self.settings_mut()
-			.ext_vars
-			.insert(name, TlaArg::String(value));
-	}
-	pub fn add_ext_code(&self, name: &str, code: String) -> Result<()> {
-		let source_name = format!("<extvar:{}>", name);
-		let source = Source::new_virtual(Cow::Owned(source_name.clone()));
-		let parsed = jrsonnet_parser::parse(
-			&code,
-			&ParserSettings {
-				file_name: source.clone(),
-			},
-		)
-		.map_err(|e| ImportSyntaxError {
-			path: source,
-			source_code: code.clone().into(),
-			error: Box::new(e),
-		})?;
-		self.data_mut().volatile_files.insert(source_name, code);
-		self.settings_mut()
-			.ext_vars
-			.insert(name.into(), TlaArg::Code(parsed));
-		Ok(())
-	}
-
 	pub fn add_tla(&self, name: IStr, value: Val) {
 		self.settings_mut()
 			.tla_vars
@@ -638,8 +617,8 @@
 			.insert(name, TlaArg::String(value));
 	}
 	pub fn add_tla_code(&self, name: IStr, code: &str) -> Result<()> {
-		let source_name = format!("<top-level-arg:{}>", name);
-		let source = Source::new_virtual(Cow::Owned(source_name.clone()));
+		let source_name = format!("<top-level-arg:{name}>");
+		let source = Source::new_virtual(source_name.into(), code.into());
 		let parsed = jrsonnet_parser::parse(
 			code,
 			&ParserSettings {
@@ -648,33 +627,33 @@
 		)
 		.map_err(|e| ImportSyntaxError {
 			path: source,
-			source_code: code.into(),
 			error: Box::new(e),
 		})?;
-		self.data_mut()
-			.volatile_files
-			.insert(source_name, code.to_owned());
 		self.settings_mut()
 			.tla_vars
 			.insert(name, TlaArg::Code(parsed));
 		Ok(())
 	}
 
-	pub fn resolve_file(&self, from: &Path, path: &str) -> Result<PathBuf> {
-		self.settings()
-			.import_resolver
-			.resolve_file(from, path.as_ref())
+	// Only panics in case of [`ImportResolver`] contract violation
+	#[allow(clippy::missing_panics_doc)]
+	pub fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {
+		self.import_resolver().resolve_from(from, path.as_ref())
 	}
 
-	pub fn import_resolver(&self) -> Ref<dyn ImportResolver> {
+	// Only panics in case of [`ImportResolver`] contract violation
+	#[allow(clippy::missing_panics_doc)]
+	pub fn resolve(&self, path: impl AsRef<Path>) -> Result<SourcePath> {
+		self.import_resolver().resolve(path.as_ref())
+	}
+	pub fn import_resolver(&self) -> Ref<'_, dyn ImportResolver> {
 		Ref::map(self.settings(), |s| &*s.import_resolver)
 	}
 	pub fn set_import_resolver(&self, resolver: Box<dyn ImportResolver>) {
 		self.settings_mut().import_resolver = resolver;
 	}
-
-	pub fn add_native(&self, name: IStr, cb: Cc<TraceBox<dyn Builtin>>) {
-		self.settings_mut().ext_natives.insert(name, cb);
+	pub fn context_initializer(&self) -> Ref<'_, dyn ContextInitializer> {
+		Ref::map(self.settings(), |s| &*s.context_initializer)
 	}
 
 	pub fn manifest_format(&self) -> ManifestFormat {
@@ -684,7 +663,7 @@
 		self.settings_mut().manifest_format = format;
 	}
 
-	pub fn trace_format(&self) -> Ref<dyn TraceFormat> {
+	pub fn trace_format(&self) -> Ref<'_, dyn TraceFormat> {
 		Ref::map(self.settings(), |s| &*s.trace_format)
 	}
 	pub fn set_trace_format(&self, format: Box<dyn TraceFormat>) {
modifiedcrates/jrsonnet-evaluator/src/map.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/map.rs
+++ b/crates/jrsonnet-evaluator/src/map.rs
@@ -23,6 +23,13 @@
 		}
 	}
 
+	pub(crate) fn new(layer: GcHashMap<IStr, Thunk<Val>>) -> Self {
+		Self(Cc::new(LayeredHashMapInternals {
+			parent: None,
+			current: layer,
+		}))
+	}
+
 	pub fn extend(self, new_layer: GcHashMap<IStr, Thunk<Val>>) -> Self {
 		Self(Cc::new(LayeredHashMapInternals {
 			parent: Some(self),
modifiedcrates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -15,7 +15,7 @@
 	function::CallLocation,
 	gc::{GcHashMap, GcHashSet, TraceBox},
 	operator::evaluate_add_op,
-	throw, LazyBinding, Result, State, Thunk, Unbound, Val,
+	throw, MaybeUnbound, Result, State, Thunk, Unbound, Val,
 };
 
 #[cfg(not(feature = "exp-preserve-order"))]
@@ -100,7 +100,7 @@
 	pub add: bool,
 	pub visibility: Visibility,
 	original_index: FieldIndex,
-	pub invoke: LazyBinding,
+	pub invoke: MaybeUnbound,
 	pub location: Option<ExprLocation>,
 }
 
@@ -109,7 +109,6 @@
 }
 
 // Field => This
-type CacheKey = (IStr, WeakObjValue);
 
 #[derive(Trace)]
 enum CacheValue {
@@ -129,7 +128,7 @@
 	assertions: Cc<Vec<TraceBox<dyn ObjectAssertion>>>,
 	assertions_ran: RefCell<GcHashSet<ObjValue>>,
 	this_entries: Cc<GcHashMap<IStr, ObjMember>>,
-	value_cache: RefCell<GcHashMap<CacheKey, CacheValue>>,
+	value_cache: RefCell<GcHashMap<IStr, CacheValue>>,
 }
 
 #[derive(Clone, Trace)]
@@ -157,9 +156,9 @@
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 		if let Some(super_obj) = self.0.sup.as_ref() {
 			if f.alternate() {
-				write!(f, "{:#?}", super_obj)?;
+				write!(f, "{super_obj:#?}")?;
 			} else {
-				write!(f, "{:?}", super_obj)?;
+				write!(f, "{super_obj:?}")?;
 			}
 			write!(f, " + ")?;
 		}
@@ -209,7 +208,7 @@
 		new.insert(key, value);
 		Self::new(Some(self), Cc::new(new), Cc::new(Vec::new()))
 	}
-	pub fn extend_field(&mut self, name: IStr) -> ObjMemberBuilder<ExtendBuilder> {
+	pub fn extend_field(&mut self, name: IStr) -> ObjMemberBuilder<ExtendBuilder<'_>> {
 		ObjMemberBuilder::new(ExtendBuilder(self), name, FieldIndex::default())
 	}
 
@@ -240,6 +239,8 @@
 	}
 
 	/// Run callback for every field found in object
+	///
+	/// Returns true if ended prematurely
 	pub(crate) fn enum_fields(
 		&self,
 		depth: SuperDepth,
@@ -369,15 +370,7 @@
 
 	pub fn get(&self, s: State, key: IStr) -> Result<Option<Val>> {
 		self.run_assertions(s.clone())?;
-		self.get_raw(s, key, self.0.this.clone().unwrap_or_else(|| self.clone()))
-	}
-
-	// pub fn extend_with(self, key: )
-
-	fn get_raw(&self, s: State, key: IStr, real_this: Self) -> Result<Option<Val>> {
-		let cache_key = (key.clone(), WeakObjValue(real_this.0.downgrade()));
-
-		if let Some(v) = self.0.value_cache.borrow().get(&cache_key) {
+		if let Some(v) = self.0.value_cache.borrow().get(&key) {
 			return Ok(match v {
 				CacheValue::Cached(v) => Some(v.clone()),
 				CacheValue::NotFound => None,
@@ -388,26 +381,37 @@
 		self.0
 			.value_cache
 			.borrow_mut()
-			.insert(cache_key.clone(), CacheValue::Pending);
-		let fill_error = |e: LocError| {
-			self.0
-				.value_cache
-				.borrow_mut()
-				.insert(cache_key.clone(), CacheValue::Errored(e.clone()));
-			e
-		};
-		let value = match (self.0.this_entries.get(&key), &self.0.sup) {
-			(Some(k), None) => Ok(Some(
-				self.evaluate_this(s, k, real_this).map_err(fill_error)?,
-			)),
+			.insert(key.clone(), CacheValue::Pending);
+		let value = self
+			.get_raw(
+				s,
+				key.clone(),
+				self.0.this.clone().unwrap_or_else(|| self.clone()),
+			)
+			.map_err(|e| {
+				self.0
+					.value_cache
+					.borrow_mut()
+					.insert(key.clone(), CacheValue::Errored(e.clone()));
+				e
+			})?;
+		self.0.value_cache.borrow_mut().insert(
+			key,
+			value
+				.as_ref()
+				.map_or(CacheValue::NotFound, |v| CacheValue::Cached(v.clone())),
+		);
+		Ok(value)
+	}
+
+	fn get_raw(&self, s: State, key: IStr, real_this: Self) -> Result<Option<Val>> {
+		match (self.0.this_entries.get(&key), &self.0.sup) {
+			(Some(k), None) => Ok(Some(self.evaluate_this(s, k, real_this)?)),
 			(Some(k), Some(super_obj)) => {
-				let our = self
-					.evaluate_this(s.clone(), k, real_this.clone())
-					.map_err(fill_error)?;
+				let our = self.evaluate_this(s.clone(), k, real_this.clone())?;
 				if k.add {
 					super_obj
-						.get_raw(s.clone(), key, real_this)
-						.map_err(fill_error)?
+						.get_raw(s.clone(), key, real_this)?
 						.map_or(Ok(Some(our.clone())), |v| {
 							Ok(Some(evaluate_add_op(s.clone(), &v, &our)?))
 						})
@@ -418,15 +422,6 @@
 			(None, Some(super_obj)) => super_obj.get_raw(s, key, real_this),
 			(None, None) => Ok(None),
 		}
-		.map_err(fill_error)?;
-		self.0.value_cache.borrow_mut().insert(
-			cache_key,
-			match &value {
-				Some(v) => CacheValue::Cached(v.clone()),
-				None => CacheValue::NotFound,
-			},
-		);
-		Ok(value)
 	}
 	fn evaluate_this(&self, s: State, v: &ObjMember, real_this: Self) -> Result<Val> {
 		v.invoke
@@ -507,7 +502,7 @@
 		self.assertions.push(assertion);
 		self
 	}
-	pub fn member(&mut self, name: IStr) -> ObjMemberBuilder<ValueBuilder> {
+	pub fn member(&mut self, name: IStr) -> ObjMemberBuilder<ValueBuilder<'_>> {
 		let field_index = self.next_field_index;
 		self.next_field_index = self.next_field_index.next();
 		ObjMemberBuilder::new(ValueBuilder(self), name, field_index)
@@ -565,7 +560,7 @@
 		self.location = Some(location);
 		self
 	}
-	fn build_member(self, binding: LazyBinding) -> (Kind, IStr, ObjMember) {
+	fn build_member(self, binding: MaybeUnbound) -> (Kind, IStr, ObjMember) {
 		(
 			self.kind,
 			self.name,
@@ -581,18 +576,18 @@
 }
 
 pub struct ValueBuilder<'v>(&'v mut ObjValueBuilder);
-impl<'v> ObjMemberBuilder<ValueBuilder<'v>> {
+impl ObjMemberBuilder<ValueBuilder<'_>> {
 	pub fn value(self, s: State, value: Val) -> Result<()> {
-		self.binding(s, LazyBinding::Bound(Thunk::evaluated(value)))
+		self.binding(s, MaybeUnbound::Bound(Thunk::evaluated(value)))
 	}
 	pub fn bindable(
 		self,
 		s: State,
 		bindable: TraceBox<dyn Unbound<Bound = Thunk<Val>>>,
 	) -> Result<()> {
-		self.binding(s, LazyBinding::Bindable(Cc::new(bindable)))
+		self.binding(s, MaybeUnbound::Unbound(Cc::new(bindable)))
 	}
-	pub fn binding(self, s: State, binding: LazyBinding) -> Result<()> {
+	pub fn binding(self, s: State, binding: MaybeUnbound) -> Result<()> {
 		let (receiver, name, member) = self.build_member(binding);
 		let location = member.location.clone();
 		let old = receiver.0.map.insert(name.clone(), member);
@@ -608,14 +603,14 @@
 }
 
 pub struct ExtendBuilder<'v>(&'v mut ObjValue);
-impl<'v> ObjMemberBuilder<ExtendBuilder<'v>> {
+impl ObjMemberBuilder<ExtendBuilder<'_>> {
 	pub fn value(self, value: Val) {
-		self.binding(LazyBinding::Bound(Thunk::evaluated(value)));
+		self.binding(MaybeUnbound::Bound(Thunk::evaluated(value)));
 	}
 	pub fn bindable(self, bindable: TraceBox<dyn Unbound<Bound = Thunk<Val>>>) {
-		self.binding(LazyBinding::Bindable(Cc::new(bindable)));
+		self.binding(MaybeUnbound::Unbound(Cc::new(bindable)));
 	}
-	pub fn binding(self, binding: LazyBinding) {
+	pub fn binding(self, binding: MaybeUnbound) {
 		let (receiver, name, member) = self.build_member(binding);
 		let new = receiver.0.clone();
 		*receiver.0 = new.extend_with_raw_member(name, member);
deletedcrates/jrsonnet-evaluator/src/stdlib/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/stdlib/expr.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-use std::borrow::Cow;
-
-use jrsonnet_parser::{LocExpr, ParserSettings, Source};
-
-thread_local! {
-	/// To avoid parsing again when issued from the same thread
-	#[allow(unreachable_code)]
-	static PARSED_STDLIB: LocExpr = {
-		#[cfg(feature = "serialized-stdlib")]
-		{
-			// Should not panic, stdlib.bincode is generated in build.rs
-			return bincode::deserialize(include_bytes!(concat!(env!("OUT_DIR"), "/stdlib.bincode")))
-				.unwrap();
-		}
-
-		jrsonnet_parser::parse(
-			jrsonnet_stdlib::STDLIB_STR,
-			&ParserSettings {
-				file_name: Source::new_virtual(Cow::Borrowed("<std>")),
-			},
-		)
-		.unwrap()
-	}
-}
-
-pub fn get_parsed_stdlib() -> LocExpr {
-	PARSED_STDLIB.with(Clone::clone)
-}
modifiedcrates/jrsonnet-evaluator/src/stdlib/format.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/stdlib/format.rs
+++ b/crates/jrsonnet-evaluator/src/stdlib/format.rs
@@ -36,7 +36,7 @@
 
 type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>;
 
-pub fn try_parse_mapping_key(str: &str) -> ParseResult<&str> {
+pub fn try_parse_mapping_key(str: &str) -> ParseResult<'_, &str> {
 	if str.is_empty() {
 		return Err(TruncatedFormatCode);
 	}
@@ -45,7 +45,7 @@
 		let mut i = 1;
 		while i < bytes.len() {
 			if bytes[i] == b')' {
-				return Ok((&str[1..i as usize], &str[i as usize + 1..]));
+				return Ok((&str[1..i], &str[i + 1..]));
 			}
 			i += 1;
 		}
@@ -96,7 +96,7 @@
 	pub sign: bool,
 }
 
-pub fn try_parse_cflags(str: &str) -> ParseResult<CFlags> {
+pub fn try_parse_cflags(str: &str) -> ParseResult<'_, CFlags> {
 	if str.is_empty() {
 		return Err(TruncatedFormatCode);
 	}
@@ -125,7 +125,7 @@
 	Star,
 	Fixed(usize),
 }
-pub fn try_parse_field_width(str: &str) -> ParseResult<Width> {
+pub fn try_parse_field_width(str: &str) -> ParseResult<'_, Width> {
 	if str.is_empty() {
 		return Err(TruncatedFormatCode);
 	}
@@ -146,7 +146,7 @@
 	Ok((Width::Fixed(out), &str[digits..]))
 }
 
-pub fn try_parse_precision(str: &str) -> ParseResult<Option<Width>> {
+pub fn try_parse_precision(str: &str) -> ParseResult<'_, Option<Width>> {
 	if str.is_empty() {
 		return Err(TruncatedFormatCode);
 	}
@@ -159,7 +159,7 @@
 }
 
 // Only skips
-pub fn try_parse_length_modifier(str: &str) -> ParseResult<()> {
+pub fn try_parse_length_modifier(str: &str) -> ParseResult<'_, ()> {
 	if str.is_empty() {
 		return Err(TruncatedFormatCode);
 	}
@@ -191,7 +191,7 @@
 	caps: bool,
 }
 
-pub fn parse_conversion_type(str: &str) -> ParseResult<ConvType> {
+pub fn parse_conversion_type(str: &str) -> ParseResult<'_, ConvType> {
 	if str.is_empty() {
 		return Err(TruncatedFormatCode);
 	}
@@ -226,7 +226,7 @@
 	convtype: ConvTypeV,
 	caps: bool,
 }
-pub fn parse_code(str: &str) -> ParseResult<Code> {
+pub fn parse_code(str: &str) -> ParseResult<'_, Code<'_>> {
 	if str.is_empty() {
 		return Err(TruncatedFormatCode);
 	}
@@ -255,7 +255,7 @@
 	String(&'s str),
 	Code(Code<'s>),
 }
-pub fn parse_codes(mut str: &str) -> Result<Vec<Element>> {
+pub fn parse_codes(mut str: &str) -> Result<Vec<Element<'_>>> {
 	let mut bytes = str.as_bytes();
 	let mut out = vec![];
 	let mut offset = 0;
@@ -285,7 +285,7 @@
 #[inline]
 pub fn render_integer(
 	out: &mut String,
-	iv: i64,
+	iv: f64,
 	padding: usize,
 	precision: usize,
 	blank: bool,
@@ -294,20 +294,23 @@
 	prefix: &str,
 	caps: bool,
 ) {
+	let radix = radix as f64;
+	let iv = iv.floor();
 	// Digit char indexes in reverse order, i.e
 	// for radix = 16 and n = 12f: [15, 2, 1]
-	let digits = if iv == 0 {
+	let digits = if iv == 0.0 {
 		vec![0u8]
 	} else {
 		let mut v = iv.abs();
 		let mut nums = Vec::with_capacity(1);
-		while v > 0 {
+		while v != 0.0 {
 			nums.push((v % radix) as u8);
-			v /= radix;
+			v = (v / radix).floor();
 		}
 		nums
 	};
-	let neg = iv < 0;
+	let neg = iv < 0.0;
+	#[allow(clippy::bool_to_int_with_if)]
 	let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });
 	let zp2 = zp
 		.max(precision)
@@ -335,7 +338,7 @@
 
 pub fn render_decimal(
 	out: &mut String,
-	iv: i64,
+	iv: f64,
 	padding: usize,
 	precision: usize,
 	blank: bool,
@@ -345,7 +348,7 @@
 }
 pub fn render_octal(
 	out: &mut String,
-	iv: i64,
+	iv: f64,
 	padding: usize,
 	precision: usize,
 	alt: bool,
@@ -360,7 +363,7 @@
 		blank,
 		sign,
 		8,
-		if alt && iv != 0 { "0" } else { "" },
+		if alt && iv != 0.0 { "0" } else { "" },
 		false,
 	);
 }
@@ -368,7 +371,7 @@
 #[allow(clippy::fn_params_excessive_bools)]
 pub fn render_hexadecimal(
 	out: &mut String,
-	iv: i64,
+	iv: f64,
 	padding: usize,
 	precision: usize,
 	alt: bool,
@@ -404,9 +407,10 @@
 	ensure_pt: bool,
 	trailing: bool,
 ) {
+	#[allow(clippy::bool_to_int_with_if)]
 	let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };
 	padding = padding.saturating_sub(dot_size + precision);
-	render_decimal(out, n.floor() as i64, padding, 0, blank, sign);
+	render_decimal(out, n.floor(), padding, 0, blank, sign);
 	if precision == 0 {
 		if ensure_pt {
 			out.push('.');
@@ -420,7 +424,7 @@
 	if trailing || frac > 0.0 {
 		out.push('.');
 		let mut frac_str = String::new();
-		render_decimal(&mut frac_str, frac as i64, precision, 0, false, false);
+		render_decimal(&mut frac_str, frac, precision, 0, false, false);
 		let mut trim = frac_str.len();
 		if !trailing {
 			for b in frac_str.as_bytes().iter().rev() {
@@ -454,7 +458,7 @@
 		n / 10.0_f64.powf(exponent)
 	};
 	let mut exponent_str = String::new();
-	render_decimal(&mut exponent_str, exponent as i64, 3, 0, false, true);
+	render_decimal(&mut exponent_str, exponent, 3, 0, false, true);
 
 	// +1 for e
 	padding = padding.saturating_sub(exponent_str.len() + 1);
@@ -471,15 +475,12 @@
 	s: State,
 	out: &mut String,
 	value: &Val,
-	code: &Code,
+	code: &Code<'_>,
 	width: usize,
 	precision: Option<usize>,
 ) -> Result<()> {
 	let clfags = &code.cflags;
-	let (fpprec, iprec) = match precision {
-		Some(v) => (v, v),
-		None => (6, 0),
-	};
+	let (fpprec, iprec) = precision.map_or((6, 0), |v| (v, v));
 	let padding = if clfags.zero && !clfags.left {
 		width
 	} else {
@@ -495,7 +496,7 @@
 			let value = f64::from_untyped(value.clone(), s)?;
 			render_decimal(
 				&mut tmp_out,
-				value as i64,
+				value,
 				padding,
 				iprec,
 				clfags.blank,
@@ -506,7 +507,7 @@
 			let value = f64::from_untyped(value.clone(), s)?;
 			render_octal(
 				&mut tmp_out,
-				value as i64,
+				value,
 				padding,
 				iprec,
 				clfags.alt,
@@ -518,7 +519,7 @@
 			let value = f64::from_untyped(value.clone(), s)?;
 			render_hexadecimal(
 				&mut tmp_out,
-				value as i64,
+				value,
 				padding,
 				iprec,
 				clfags.alt,
@@ -584,8 +585,10 @@
 			}
 		}
 		ConvTypeV::Char => match value.clone() {
-			Val::Num(n) => tmp_out
-				.push(std::char::from_u32(n as u32).ok_or(InvalidUnicodeCodepointGot(n as u32))?),
+			Val::Num(n) => tmp_out.push(
+				std::char::from_u32(n as u32)
+					.ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?,
+			),
 			Val::Str(s) => {
 				if s.chars().count() != 1 {
 					throw!(RuntimeError(
@@ -774,10 +777,7 @@
 			format_arr(s.clone(), "%+-4o", &[Val::Num(8.0)]).unwrap(),
 			"+10 "
 		);
-		assert_eq!(
-			format_arr(s.clone(), "%+-04o", &[Val::Num(8.0)]).unwrap(),
-			"+10 "
-		);
+		assert_eq!(format_arr(s, "%+-04o", &[Val::Num(8.0)]).unwrap(), "+10 ");
 	}
 
 	#[test]
modifiedcrates/jrsonnet-evaluator/src/stdlib/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/stdlib/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/stdlib/manifest.rs
@@ -49,7 +49,7 @@
 		}
 		Val::Null => buf.push_str("null"),
 		Val::Str(s) => escape_string_json_buf(s, buf),
-		Val::Num(n) => write!(buf, "{}", n).unwrap(),
+		Val::Num(n) => write!(buf, "{n}").unwrap(),
 		Val::Arr(items) => {
 			buf.push('[');
 			if !items.is_empty() {
@@ -116,7 +116,7 @@
 						|| {
 							let value = obj.get(s.clone(), field.clone())?.unwrap();
 							manifest_json_ex_buf(s.clone(), &value, buf, cur_padding, options)?;
-							Ok(Val::Null)
+							Ok(())
 						},
 					)?;
 				}
modifiedcrates/jrsonnet-evaluator/src/stdlib/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/stdlib/mod.rs
+++ b/crates/jrsonnet-evaluator/src/stdlib/mod.rs
@@ -1,742 +1,24 @@
 // All builtins should return results
 #![allow(clippy::unnecessary_wraps)]
 
-use std::collections::HashMap;
-
 use format::{format_arr, format_obj};
-use jrsonnet_gcmodule::Cc;
-use jrsonnet_interner::{IBytes, IStr};
-use serde::Deserialize;
-use serde_yaml_with_quirks::DeserializingQuirks;
-
-use crate::{
-	error::{Error::*, Result},
-	function::{builtin::StaticBuiltin, ArgLike, CallLocation, FuncVal},
-	operator::evaluate_mod_op,
-	stdlib::manifest::{manifest_yaml_ex, ManifestYamlOptions},
-	throw,
-	typed::{Any, BoundedUsize, Either2, Either4, PositiveF64, Typed, VecVal, M1},
-	val::{equals, primitive_equals, ArrValue, IndexableVal, Slice},
-	Either, ObjValue, State, Val,
-};
-
-pub mod expr;
-pub use expr::*;
+use jrsonnet_interner::IStr;
 
-use self::manifest::{escape_string_json, manifest_json_ex, ManifestJsonOptions, ManifestType};
+use crate::{error::Result, function::CallLocation, State, Val};
 
 pub mod format;
 pub mod manifest;
-pub mod sort;
 
 pub fn std_format(s: State, str: IStr, vals: Val) -> Result<String> {
 	s.push(
 		CallLocation::native(),
-		|| format!("std.format of {}", str),
+		|| format!("std.format of {str}"),
 		|| {
 			Ok(match vals {
 				Val::Arr(vals) => format_arr(s.clone(), &str, &vals.evaluated(s.clone())?)?,
 				Val::Obj(obj) => format_obj(s.clone(), &str, &obj)?,
 				o => format_arr(s.clone(), &str, &[o])?,
 			})
-		},
-	)
-}
-
-pub fn std_slice(
-	indexable: IndexableVal,
-	index: Option<BoundedUsize<0, { i32::MAX as usize }>>,
-	end: Option<BoundedUsize<0, { i32::MAX as usize }>>,
-	step: Option<BoundedUsize<1, { i32::MAX as usize }>>,
-) -> Result<Val> {
-	match &indexable {
-		IndexableVal::Str(s) => {
-			let index = index.as_deref().copied().unwrap_or(0);
-			let end = end.as_deref().copied().unwrap_or(usize::MAX);
-			let step = step.as_deref().copied().unwrap_or(1);
-
-			if index >= end {
-				return Ok(Val::Str("".into()));
-			}
-
-			Ok(Val::Str(
-				(s.chars()
-					.skip(index)
-					.take(end - index)
-					.step_by(step)
-					.collect::<String>())
-				.into(),
-			))
-		}
-		IndexableVal::Arr(arr) => {
-			let index = index.as_deref().copied().unwrap_or(0);
-			let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());
-			let step = step.as_deref().copied().unwrap_or(1);
-
-			if index >= end {
-				return Ok(Val::Arr(ArrValue::new_eager()));
-			}
-
-			Ok(Val::Arr(ArrValue::Slice(Box::new(Slice {
-				inner: arr.clone(),
-				from: index as u32,
-				to: end as u32,
-				step: step as u32,
-			}))))
-		}
-	}
-}
-
-type BuiltinsType = HashMap<IStr, &'static dyn StaticBuiltin>;
-
-thread_local! {
-	pub static BUILTINS: BuiltinsType = {
-		[
-			("length".into(), builtin_length::INST),
-			("type".into(), builtin_type::INST),
-			("makeArray".into(), builtin_make_array::INST),
-			("codepoint".into(), builtin_codepoint::INST),
-			("objectFieldsEx".into(), builtin_object_fields_ex::INST),
-			("objectHasEx".into(), builtin_object_has_ex::INST),
-			("slice".into(), builtin_slice::INST),
-			("substr".into(), builtin_substr::INST),
-			("primitiveEquals".into(), builtin_primitive_equals::INST),
-			("equals".into(), builtin_equals::INST),
-			("modulo".into(), builtin_modulo::INST),
-			("mod".into(), builtin_mod::INST),
-			("floor".into(), builtin_floor::INST),
-			("ceil".into(), builtin_ceil::INST),
-			("log".into(), builtin_log::INST),
-			("pow".into(), builtin_pow::INST),
-			("sqrt".into(), builtin_sqrt::INST),
-			("sin".into(), builtin_sin::INST),
-			("cos".into(), builtin_cos::INST),
-			("tan".into(), builtin_tan::INST),
-			("asin".into(), builtin_asin::INST),
-			("acos".into(), builtin_acos::INST),
-			("atan".into(), builtin_atan::INST),
-			("exp".into(), builtin_exp::INST),
-			("mantissa".into(), builtin_mantissa::INST),
-			("exponent".into(), builtin_exponent::INST),
-			("extVar".into(), builtin_ext_var::INST),
-			("native".into(), builtin_native::INST),
-			("filter".into(), builtin_filter::INST),
-			("map".into(), builtin_map::INST),
-			("flatMap".into(), builtin_flatmap::INST),
-			("foldl".into(), builtin_foldl::INST),
-			("foldr".into(), builtin_foldr::INST),
-			("sort".into(), builtin_sort::INST),
-			("format".into(), builtin_format::INST),
-			("range".into(), builtin_range::INST),
-			("char".into(), builtin_char::INST),
-			("encodeUTF8".into(), builtin_encode_utf8::INST),
-			("decodeUTF8".into(), builtin_decode_utf8::INST),
-			("md5".into(), builtin_md5::INST),
-			("base64".into(), builtin_base64::INST),
-			("base64DecodeBytes".into(), builtin_base64_decode_bytes::INST),
-			("base64Decode".into(), builtin_base64_decode::INST),
-			("trace".into(), builtin_trace::INST),
-			("join".into(), builtin_join::INST),
-			("escapeStringJson".into(), builtin_escape_string_json::INST),
-			("manifestJsonEx".into(), builtin_manifest_json_ex::INST),
-			("manifestYamlDoc".into(), builtin_manifest_yaml_doc::INST),
-			("reverse".into(), builtin_reverse::INST),
-			("strReplace".into(), builtin_str_replace::INST),
-			("splitLimit".into(), builtin_splitlimit::INST),
-			("parseJson".into(), builtin_parse_json::INST),
-			("parseYaml".into(), builtin_parse_yaml::INST),
-			("asciiUpper".into(), builtin_ascii_upper::INST),
-			("asciiLower".into(), builtin_ascii_lower::INST),
-			("member".into(), builtin_member::INST),
-			("count".into(), builtin_count::INST),
-			("any".into(), builtin_any::INST),
-			("all".into(), builtin_all::INST),
-		].iter().cloned().collect()
-	};
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> Result<usize> {
-	use Either4::*;
-	Ok(match x {
-		A(x) => x.chars().count(),
-		B(x) => x.len(),
-		C(x) => x.len(),
-		D(f) => f.params_len(),
-	})
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_type(x: Any) -> Result<IStr> {
-	Ok(x.0.value_type().name().into())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_make_array(s: State, sz: usize, func: FuncVal) -> Result<VecVal> {
-	let mut out = Vec::with_capacity(sz);
-	for i in 0..sz {
-		out.push(func.evaluate_simple(s.clone(), &(i as f64,))?);
-	}
-	Ok(VecVal(Cc::new(out)))
-}
-
-#[jrsonnet_macros::builtin]
-const fn builtin_codepoint(str: char) -> Result<u32> {
-	Ok(str as u32)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_object_fields_ex(
-	obj: ObjValue,
-	inc_hidden: bool,
-	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
-) -> Result<VecVal> {
-	#[cfg(feature = "exp-preserve-order")]
-	let preserve_order = preserve_order.unwrap_or(false);
-	let out = obj.fields_ex(
-		inc_hidden,
-		#[cfg(feature = "exp-preserve-order")]
-		preserve_order,
-	);
-	Ok(VecVal(Cc::new(
-		out.into_iter().map(Val::Str).collect::<Vec<_>>(),
-	)))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_object_has_ex(obj: ObjValue, f: IStr, inc_hidden: bool) -> Result<bool> {
-	Ok(obj.has_field_ex(f, inc_hidden))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_parse_json(st: State, s: IStr) -> Result<Any> {
-	use serde_json::Value;
-	let value: Value = serde_json::from_str(&s)
-		.map_err(|e| RuntimeError(format!("failed to parse json: {}", e).into()))?;
-	Ok(Any(Value::into_untyped(value, st)?))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_parse_yaml(st: State, s: IStr) -> Result<Any> {
-	use serde_json::Value;
-	let value = serde_yaml_with_quirks::Deserializer::from_str_with_quirks(
-		&s,
-		DeserializingQuirks { old_octals: true },
-	);
-	let mut out = vec![];
-	for item in value {
-		let value = Value::deserialize(item)
-			.map_err(|e| RuntimeError(format!("failed to parse yaml: {}", e).into()))?;
-		let val = Value::into_untyped(value, st.clone())?;
-		out.push(val);
-	}
-	Ok(Any(if out.is_empty() {
-		Val::Null
-	} else if out.len() == 1 {
-		out.into_iter().next().unwrap()
-	} else {
-		Val::Arr(out.into())
-	}))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_slice(
-	indexable: IndexableVal,
-	index: Option<BoundedUsize<0, { i32::MAX as usize }>>,
-	end: Option<BoundedUsize<0, { i32::MAX as usize }>>,
-	step: Option<BoundedUsize<1, { i32::MAX as usize }>>,
-) -> Result<Any> {
-	std_slice(indexable, index, end, step).map(Any)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_substr(str: IStr, from: usize, len: usize) -> Result<String> {
-	Ok(str.chars().skip(from as usize).take(len as usize).collect())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_primitive_equals(a: Any, b: Any) -> Result<bool> {
-	primitive_equals(&a.0, &b.0)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_equals(s: State, a: Any, b: Any) -> Result<bool> {
-	equals(s, &a.0, &b.0)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_modulo(a: f64, b: f64) -> Result<f64> {
-	Ok(a % b)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_mod(s: State, a: Either![f64, IStr], b: Any) -> Result<Any> {
-	use Either2::*;
-	Ok(Any(evaluate_mod_op(
-		s,
-		&match a {
-			A(v) => Val::Num(v),
-			B(s) => Val::Str(s),
-		},
-		&b.0,
-	)?))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_floor(x: f64) -> Result<f64> {
-	Ok(x.floor())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_ceil(x: f64) -> Result<f64> {
-	Ok(x.ceil())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_log(n: f64) -> Result<f64> {
-	Ok(n.ln())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_pow(x: f64, n: f64) -> Result<f64> {
-	Ok(x.powf(n))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_sqrt(x: PositiveF64) -> Result<f64> {
-	Ok(x.0.sqrt())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_sin(x: f64) -> Result<f64> {
-	Ok(x.sin())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_cos(x: f64) -> Result<f64> {
-	Ok(x.cos())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_tan(x: f64) -> Result<f64> {
-	Ok(x.tan())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_asin(x: f64) -> Result<f64> {
-	Ok(x.asin())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_acos(x: f64) -> Result<f64> {
-	Ok(x.acos())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_atan(x: f64) -> Result<f64> {
-	Ok(x.atan())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_exp(x: f64) -> Result<f64> {
-	Ok(x.exp())
-}
-
-fn frexp(s: f64) -> (f64, i16) {
-	if 0.0 == s {
-		(s, 0)
-	} else {
-		let lg = s.abs().log2();
-		let x = (lg - lg.floor() - 1.0).exp2();
-		let exp = lg.floor() + 1.0;
-		(s.signum() * x, exp as i16)
-	}
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_mantissa(x: f64) -> Result<f64> {
-	Ok(frexp(x).0)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_exponent(x: f64) -> Result<i16> {
-	Ok(frexp(x).1)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_ext_var(s: State, x: IStr) -> Result<Any> {
-	let ctx = s.create_default_context();
-	Ok(Any(s
-		.clone()
-		.settings()
-		.ext_vars
-		.get(&x)
-		.cloned()
-		.ok_or(UndefinedExternalVariable(x))?
-		.evaluate_arg(s.clone(), ctx, true)?
-		.evaluate(s)?))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_native(s: State, name: IStr) -> Result<Any> {
-	Ok(Any(s
-		.settings()
-		.ext_natives
-		.get(&name)
-		.cloned()
-		.map_or(Val::Null, |v| {
-			Val::Func(FuncVal::Builtin(v.clone()))
-		})))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_filter(s: State, func: FuncVal, arr: ArrValue) -> Result<ArrValue> {
-	arr.filter(s.clone(), |val| {
-		bool::from_untyped(
-			func.evaluate_simple(s.clone(), &(Any(val.clone()),))?,
-			s.clone(),
-		)
-	})
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_map(s: State, func: FuncVal, arr: ArrValue) -> Result<ArrValue> {
-	arr.map(s.clone(), |val| {
-		func.evaluate_simple(s.clone(), &(Any(val),))
-	})
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_flatmap(s: State, func: FuncVal, arr: IndexableVal) -> Result<IndexableVal> {
-	match arr {
-		IndexableVal::Str(str) => {
-			let mut out = String::new();
-			for c in str.chars() {
-				match func.evaluate_simple(s.clone(), &(c.to_string(),))? {
-					Val::Str(o) => out.push_str(&o),
-					Val::Null => continue,
-					_ => throw!(RuntimeError(
-						"in std.join all items should be strings".into()
-					)),
-				};
-			}
-			Ok(IndexableVal::Str(out.into()))
-		}
-		IndexableVal::Arr(a) => {
-			let mut out = Vec::new();
-			for el in a.iter(s.clone()) {
-				let el = el?;
-				match func.evaluate_simple(s.clone(), &(Any(el),))? {
-					Val::Arr(o) => {
-						for oe in o.iter(s.clone()) {
-							out.push(oe?);
-						}
-					}
-					Val::Null => continue,
-					_ => throw!(RuntimeError(
-						"in std.join all items should be arrays".into()
-					)),
-				};
-			}
-			Ok(IndexableVal::Arr(out.into()))
-		}
-	}
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_foldl(s: State, func: FuncVal, arr: ArrValue, init: Any) -> Result<Any> {
-	let mut acc = init.0;
-	for i in arr.iter(s.clone()) {
-		acc = func.evaluate_simple(s.clone(), &(Any(acc), Any(i?)))?;
-	}
-	Ok(Any(acc))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_foldr(s: State, func: FuncVal, arr: ArrValue, init: Any) -> Result<Any> {
-	let mut acc = init.0;
-	for i in arr.iter(s.clone()).rev() {
-		acc = func.evaluate_simple(s.clone(), &(Any(i?), Any(acc)))?;
-	}
-	Ok(Any(acc))
-}
-
-#[jrsonnet_macros::builtin]
-#[allow(non_snake_case)]
-fn builtin_sort(s: State, arr: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
-	if arr.len() <= 1 {
-		return Ok(arr);
-	}
-	Ok(ArrValue::Eager(sort::sort(
-		s.clone(),
-		arr.evaluated(s)?,
-		keyF.unwrap_or_else(FuncVal::identity),
-	)?))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_format(s: State, str: IStr, vals: Any) -> Result<String> {
-	std_format(s, str, vals.0)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_range(from: i32, to: i32) -> Result<ArrValue> {
-	if to < from {
-		return Ok(ArrValue::new_eager());
-	}
-	Ok(ArrValue::new_range(from, to))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_char(n: u32) -> Result<char> {
-	Ok(std::char::from_u32(n as u32).ok_or(InvalidUnicodeCodepointGot(n as u32))?)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_encode_utf8(str: IStr) -> Result<IBytes> {
-	Ok(str.cast_bytes())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_decode_utf8(arr: IBytes) -> Result<IStr> {
-	Ok(arr
-		.cast_str()
-		.ok_or_else(|| RuntimeError("bad utf8".into()))?)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_md5(str: IStr) -> Result<String> {
-	Ok(format!("{:x}", md5::compute(&str.as_bytes())))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_trace(s: State, loc: CallLocation, str: IStr, rest: Any) -> Result<Any> {
-	eprint!("TRACE:");
-	if let Some(loc) = loc.0 {
-		let locs = s.map_source_locations(loc.0.clone(), &[loc.1]);
-		eprint!(" {}:{}", loc.0.short_display(), locs[0].line);
-	}
-	eprintln!(" {}", str);
-	Ok(rest) as Result<Any>
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_base64(input: Either![IBytes, IStr]) -> Result<String> {
-	use Either2::*;
-	Ok(match input {
-		A(a) => base64::encode(a.as_slice()),
-		B(l) => base64::encode(l.bytes().collect::<Vec<_>>()),
-	})
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_base64_decode_bytes(input: IStr) -> Result<IBytes> {
-	Ok(base64::decode(&input.as_bytes())
-		.map_err(|_| RuntimeError("bad base64".into()))?
-		.as_slice()
-		.into())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_base64_decode(input: IStr) -> Result<String> {
-	let bytes = base64::decode(&input.as_bytes()).map_err(|_| RuntimeError("bad base64".into()))?;
-	Ok(String::from_utf8(bytes).map_err(|_| RuntimeError("bad utf8".into()))?)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_join(s: State, sep: IndexableVal, arr: ArrValue) -> Result<IndexableVal> {
-	Ok(match sep {
-		IndexableVal::Arr(joiner_items) => {
-			let mut out = Vec::new();
-
-			let mut first = true;
-			for item in arr.iter(s.clone()) {
-				let item = item?.clone();
-				if let Val::Arr(items) = item {
-					if !first {
-						out.reserve(joiner_items.len());
-						// TODO: extend
-						for item in joiner_items.iter(s.clone()) {
-							out.push(item?);
-						}
-					}
-					first = false;
-					out.reserve(items.len());
-					for item in items.iter(s.clone()) {
-						out.push(item?);
-					}
-				} else if matches!(item, Val::Null) {
-					continue;
-				} else {
-					throw!(RuntimeError(
-						"in std.join all items should be arrays".into()
-					));
-				}
-			}
-
-			IndexableVal::Arr(out.into())
-		}
-		IndexableVal::Str(sep) => {
-			let mut out = String::new();
-
-			let mut first = true;
-			for item in arr.iter(s) {
-				let item = item?.clone();
-				if let Val::Str(item) = item {
-					if !first {
-						out += &sep;
-					}
-					first = false;
-					out += &item;
-				} else if matches!(item, Val::Null) {
-					continue;
-				} else {
-					throw!(RuntimeError(
-						"in std.join all items should be strings".into()
-					));
-				}
-			}
-
-			IndexableVal::Str(out.into())
-		}
-	})
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_escape_string_json(str_: IStr) -> Result<String> {
-	Ok(escape_string_json(&str_))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_manifest_json_ex(
-	s: State,
-	value: Any,
-	indent: IStr,
-	newline: Option<IStr>,
-	key_val_sep: Option<IStr>,
-	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
-) -> Result<String> {
-	let newline = newline.as_deref().unwrap_or("\n");
-	let key_val_sep = key_val_sep.as_deref().unwrap_or(": ");
-	manifest_json_ex(
-		s,
-		&value.0,
-		&ManifestJsonOptions {
-			padding: &indent,
-			mtype: ManifestType::Std,
-			newline,
-			key_val_sep,
-			#[cfg(feature = "exp-preserve-order")]
-			preserve_order: preserve_order.unwrap_or(false),
 		},
 	)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_manifest_yaml_doc(
-	s: State,
-	value: Any,
-	indent_array_in_object: Option<bool>,
-	quote_keys: Option<bool>,
-	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
-) -> Result<String> {
-	manifest_yaml_ex(
-		s,
-		&value.0,
-		&ManifestYamlOptions {
-			padding: "  ",
-			arr_element_padding: if indent_array_in_object.unwrap_or(false) {
-				"  "
-			} else {
-				""
-			},
-			quote_keys: quote_keys.unwrap_or(true),
-			#[cfg(feature = "exp-preserve-order")]
-			preserve_order: preserve_order.unwrap_or(false),
-		},
-	)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_reverse(value: ArrValue) -> Result<ArrValue> {
-	Ok(value.reversed())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_str_replace(str: String, from: IStr, to: IStr) -> Result<String> {
-	Ok(str.replace(&from as &str, &to as &str))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_splitlimit(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> Result<VecVal> {
-	use Either2::*;
-	Ok(VecVal(Cc::new(match maxsplits {
-		A(n) => str
-			.splitn(n + 1, &c as &str)
-			.map(|s| Val::Str(s.into()))
-			.collect(),
-		B(_) => str.split(&c as &str).map(|s| Val::Str(s.into())).collect(),
-	})))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_ascii_upper(str: IStr) -> Result<String> {
-	Ok(str.to_ascii_uppercase())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_ascii_lower(str: IStr) -> Result<String> {
-	Ok(str.to_ascii_lowercase())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_member(s: State, arr: IndexableVal, x: Any) -> Result<bool> {
-	match arr {
-		IndexableVal::Str(str) => {
-			let x: IStr = IStr::from_untyped(x.0, s)?;
-			Ok(!x.is_empty() && str.contains(&*x))
-		}
-		IndexableVal::Arr(a) => {
-			for item in a.iter(s.clone()) {
-				let item = item?;
-				if equals(s.clone(), &item, &x.0)? {
-					return Ok(true);
-				}
-			}
-			Ok(false)
-		}
-	}
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_count(s: State, arr: Vec<Any>, v: Any) -> Result<usize> {
-	let mut count = 0;
-	for item in &arr {
-		if equals(s.clone(), &item.0, &v.0)? {
-			count += 1;
-		}
-	}
-	Ok(count)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_any(s: State, arr: ArrValue) -> Result<bool> {
-	for v in arr.iter(s.clone()) {
-		let v = bool::from_untyped(v?, s.clone())?;
-		if v {
-			return Ok(true);
-		}
-	}
-	Ok(false)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_all(s: State, arr: ArrValue) -> Result<bool> {
-	for v in arr.iter(s.clone()) {
-		let v = bool::from_untyped(v?, s.clone())?;
-		if !v {
-			return Ok(false);
-		}
-	}
-	Ok(true)
 }
deletedcrates/jrsonnet-evaluator/src/stdlib/sort.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/stdlib/sort.rs
+++ /dev/null
@@ -1,110 +0,0 @@
-use jrsonnet_gcmodule::{Cc, Trace};
-
-use crate::{
-	error::{Error, LocError, Result},
-	function::FuncVal,
-	throw,
-	typed::Any,
-	State, Val,
-};
-
-#[derive(Debug, Clone, thiserror::Error, Trace)]
-pub enum SortError {
-	#[error("sort key should be string or number")]
-	SortKeyShouldBeStringOrNumber,
-	#[error("sort elements should have equal types")]
-	SortElementsShouldHaveEqualType,
-}
-
-impl From<SortError> for LocError {
-	fn from(s: SortError) -> Self {
-		Self::new(Error::Sort(s))
-	}
-}
-
-#[derive(Copy, Clone)]
-enum SortKeyType {
-	Number,
-	String,
-	Unknown,
-}
-
-#[derive(PartialEq)]
-struct NonNaNf64(f64);
-impl PartialOrd for NonNaNf64 {
-	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-		self.0.partial_cmp(&other.0)
-	}
-}
-impl Eq for NonNaNf64 {}
-impl Ord for NonNaNf64 {
-	fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-		self.partial_cmp(other).expect("non nan")
-	}
-}
-
-fn get_sort_type<T>(
-	values: &mut Vec<T>,
-	key_getter: impl Fn(&mut T) -> &mut Val,
-) -> Result<SortKeyType> {
-	let mut sort_type = SortKeyType::Unknown;
-	for i in values.iter_mut() {
-		let i = key_getter(i);
-		match (i, sort_type) {
-			(Val::Str(_), SortKeyType::Unknown) => sort_type = SortKeyType::String,
-			(Val::Num(_), SortKeyType::Unknown) => sort_type = SortKeyType::Number,
-			(Val::Str(_), SortKeyType::String) | (Val::Num(_), SortKeyType::Number) => {}
-			(Val::Str(_) | Val::Num(_), _) => {
-				throw!(SortError::SortElementsShouldHaveEqualType)
-			}
-			_ => throw!(SortError::SortKeyShouldBeStringOrNumber),
-		}
-	}
-	Ok(sort_type)
-}
-
-/// * `key_getter` - None, if identity sort required
-pub fn sort(s: State, values: Cc<Vec<Val>>, key_getter: FuncVal) -> Result<Cc<Vec<Val>>> {
-	if values.len() <= 1 {
-		return Ok(values);
-	}
-	if key_getter.is_identity() {
-		// Fast path, identity key getter
-		let mut values = (*values).clone();
-		let sort_type = get_sort_type(&mut values, |k| k)?;
-		match sort_type {
-			SortKeyType::Number => values.sort_unstable_by_key(|v| match v {
-				Val::Num(n) => NonNaNf64(*n),
-				_ => unreachable!(),
-			}),
-			SortKeyType::String => values.sort_unstable_by_key(|v| match v {
-				Val::Str(s) => s.clone(),
-				_ => unreachable!(),
-			}),
-			SortKeyType::Unknown => unreachable!(),
-		};
-		Ok(Cc::new(values))
-	} else {
-		// Slow path, user provided key getter
-		let mut vk = Vec::with_capacity(values.len());
-		for value in values.iter() {
-			vk.push((
-				value.clone(),
-				key_getter.evaluate_simple(s.clone(), &(Any(value.clone()),))?,
-			));
-		}
-		let sort_type = get_sort_type(&mut vk, |v| &mut v.1)?;
-		match sort_type {
-			SortKeyType::Number => vk.sort_by_key(|v| match v.1 {
-				Val::Num(n) => NonNaNf64(n),
-				_ => unreachable!(),
-			}),
-			SortKeyType::String => vk.sort_by_key(|v| match &v.1 {
-				Val::Str(s) => s.clone(),
-				_ => unreachable!(),
-			}),
-			SortKeyType::Unknown => unreachable!(),
-		};
-		Ok(Cc::new(vk.into_iter().map(|v| v.0).collect()))
-	}
-}
deletedcrates/jrsonnet-evaluator/src/trace/location.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/trace/location.rs
+++ /dev/null
@@ -1,124 +0,0 @@
-#[allow(clippy::module_name_repetitions)]
-#[derive(Clone, PartialEq, Eq, Debug)]
-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(file: &str, offsets: &[u32]) -> Vec<CodeLocation> {
-	if offsets.is_empty() {
-		return vec![];
-	}
-	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 = vec![
-		CodeLocation {
-			offset: 0,
-			column: 0,
-			line: 0,
-			line_start_offset: 0,
-			line_end_offset: 0
-		};
-		offsets.len()
-	];
-	let mut with_no_known_line_ending = vec![];
-	let mut this_line_offset = 0;
-	for (pos, ch) in file
-		.chars()
-		.enumerate()
-		.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]
-			),
-			vec![
-				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
-				}
-			]
-		)
-	}
-}
modifiedcrates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/trace/mod.rs
+++ b/crates/jrsonnet-evaluator/src/trace/mod.rs
@@ -1,13 +1,11 @@
-mod location;
-
 use std::path::{Path, PathBuf};
 
-use jrsonnet_parser::Source;
-pub use location::*;
+use jrsonnet_parser::{CodeLocation, Source};
 
 use crate::{error::Error, LocError, State};
 
 /// The way paths should be displayed
+#[derive(Clone)]
 pub enum PathResolver {
 	/// Only filename
 	FileName,
@@ -18,6 +16,10 @@
 }
 
 impl PathResolver {
+	/// Will return `Self::Relative(cwd)`, or `Self::Absolute` on cwd failure
+	pub fn new_cwd_fallback() -> Self {
+		std::env::current_dir().map_or(Self::Absolute, Self::Relative)
+	}
 	pub fn resolve(&self, from: &Path) -> String {
 		match self {
 			Self::FileName => from
@@ -84,31 +86,27 @@
 	fn write_trace(
 		&self,
 		out: &mut dyn std::fmt::Write,
-		s: &State,
+		_s: &State,
 		error: &LocError,
 	) -> Result<(), std::fmt::Error> {
 		write!(out, "{}", error.error())?;
-		if let Error::ImportSyntaxError {
-			path,
-			source_code,
-			error,
-		} = error.error()
-		{
+		if let Error::ImportSyntaxError { path, error } = error.error() {
 			use std::fmt::Write;
 
 			writeln!(out)?;
-			let mut n = match path.repr() {
-				Ok(r) => self.resolver.resolve(r),
-				Err(v) => v.to_string(),
-			};
+			let mut n = path.source_path().path().map_or_else(
+				|| path.source_path().to_string(),
+				|r| self.resolver.resolve(r),
+			);
 			let mut offset = error.location.offset;
-			let is_eof = if offset >= source_code.len() {
-				offset = source_code.len().saturating_sub(1);
+			let is_eof = if offset >= path.code().len() {
+				offset = path.code().len().saturating_sub(1);
 				true
 			} else {
 				false
 			};
-			let mut location = offset_to_location(source_code, &[offset as u32])
+			let mut location = path
+				.map_source_locations(&[offset as u32])
 				.into_iter()
 				.next()
 				.unwrap();
@@ -118,7 +116,7 @@
 
 			write!(n, ":").unwrap();
 			print_code_location(&mut n, &location, &location).unwrap();
-			write!(out, "{:<p$}{}", "", n, p = self.padding,)?;
+			write!(out, "{:<p$}{n}", "", p = self.padding)?;
 		}
 		let file_names = error
 			.trace()
@@ -129,13 +127,12 @@
 				use std::fmt::Write;
 				#[allow(clippy::option_if_let_else)]
 				if let Some(location) = location {
-					let mut resolved_path = match location.0.repr() {
-						Ok(r) => self.resolver.resolve(r),
-						Err(v) => v.to_string(),
+					let mut resolved_path = match location.0.source_path().path() {
+						Some(r) => self.resolver.resolve(r),
+						None => location.0.source_path().to_string(),
 					};
 					// TODO: Process all trace elements first
-					let location =
-						s.map_source_locations(location.0.clone(), &[location.1, location.2]);
+					let location = location.0.map_source_locations(&[location.1, location.2]);
 					write!(resolved_path, ":").unwrap();
 					print_code_location(&mut resolved_path, &location[0], &location[1]).unwrap();
 					write!(resolved_path, ":").unwrap();
@@ -176,7 +173,7 @@
 	fn write_trace(
 		&self,
 		out: &mut dyn std::fmt::Write,
-		s: &State,
+		_s: &State,
 		error: &LocError,
 	) -> Result<(), std::fmt::Error> {
 		write!(out, "{}", error.error())?;
@@ -184,11 +181,11 @@
 			writeln!(out)?;
 			let desc = &item.desc;
 			if let Some(source) = &item.location {
-				let start_end = s.map_source_locations(source.0.clone(), &[source.1, source.2]);
-				let resolved_path = match source.0.repr() {
-					Ok(r) => r.display().to_string(),
-					Err(v) => v.to_string(),
-				};
+				let start_end = source.0.map_source_locations(&[source.1, source.2]);
+				let resolved_path = source.0.source_path().path().map_or_else(
+					|| source.0.source_path().to_string(),
+					|r| r.display().to_string(),
+				);
 
 				write!(
 					out,
@@ -196,7 +193,7 @@
 					desc, resolved_path, start_end[0].line, start_end[0].column,
 				)?;
 			} else {
-				write!(out, "    during {}", desc)?;
+				write!(out, "    during {desc}")?;
 			}
 		}
 		Ok(())
@@ -213,19 +210,15 @@
 	fn write_trace(
 		&self,
 		out: &mut dyn std::fmt::Write,
-		s: &State,
+		_s: &State,
 		error: &LocError,
 	) -> Result<(), std::fmt::Error> {
 		write!(out, "{}", error.error())?;
-		if let Error::ImportSyntaxError {
-			path,
-			source_code,
-			error,
-		} = error.error()
-		{
+		if let Error::ImportSyntaxError { path, error } = error.error() {
 			writeln!(out)?;
 			let offset = error.location.offset;
-			let location = offset_to_location(source_code, &[offset as u32])
+			let location = path
+				.map_source_locations(&[offset as u32])
 				.into_iter()
 				.next()
 				.unwrap();
@@ -234,7 +227,7 @@
 
 			self.print_snippet(
 				out,
-				source_code,
+				path.code(),
 				path,
 				&location,
 				&end_location,
@@ -246,17 +239,17 @@
 			writeln!(out)?;
 			let desc = &item.desc;
 			if let Some(source) = &item.location {
-				let start_end = s.map_source_locations(source.0.clone(), &[source.1, source.2]);
+				let start_end = source.0.map_source_locations(&[source.1, source.2]);
 				self.print_snippet(
 					out,
-					&s.get_source(source.0.clone()).unwrap(),
+					source.0.code(),
 					&source.0,
 					&start_end[0],
 					&start_end[1],
 					desc,
 				)?;
 			} else {
-				write!(out, "{}", desc)?;
+				write!(out, "{desc}")?;
 			}
 		}
 		Ok(())
@@ -284,10 +277,10 @@
 			.take(end.line_end_offset - end.line_start_offset)
 			.collect();
 
-		let origin = match origin.repr() {
-			Ok(r) => self.resolver.resolve(r),
-			Err(v) => v.to_string(),
-		};
+		let origin = origin.source_path().path().map_or_else(
+			|| origin.source_path().to_string(),
+			|r| self.resolver.resolve(r),
+		);
 		let snippet = Snippet {
 			opt: FormatOptions {
 				color: true,
@@ -312,7 +305,7 @@
 		};
 
 		let dl = DisplayList::from(snippet);
-		write!(out, "{}", dl)?;
+		write!(out, "{dl}")?;
 
 		Ok(())
 	}
modifiedcrates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -393,6 +393,7 @@
 	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty, $f:ty) => {Either6<$a, $b, $c, $d, $e, $f>};
 	($a:ty, $b:ty, $c:ty, $d:ty, $e:ty, $f:ty, $g:ty) => {Either7<$a, $b, $c, $d, $e, $f, $g>};
 }
+pub use Either;
 
 pub type MyType = Either![u32, f64, String];
 
modifiedcrates/jrsonnet-evaluator/src/typed/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/mod.rs
+++ b/crates/jrsonnet-evaluator/src/typed/mod.rs
@@ -21,8 +21,8 @@
 	UnionFailed(ComplexValType, TypeLocErrorList),
 	#[error(
 		"number out of bounds: {0} not in {}..{}",
-		.1.map(|v|v.to_string()).unwrap_or_else(|| "".to_owned()),
-		.2.map(|v|v.to_string()).unwrap_or_else(|| "".to_owned()),
+		.1.map(|v|v.to_string()).unwrap_or_default(),
+		.2.map(|v|v.to_string()).unwrap_or_default(),
 	)]
 	BoundsFailed(f64, Option<f64>, Option<f64>),
 }
@@ -65,7 +65,7 @@
 				writeln!(f)?;
 			}
 			out.clear();
-			write!(out, "{}", err)?;
+			write!(out, "{err}")?;
 
 			for (i, line) in out.lines().enumerate() {
 				if line.trim().is_empty() {
@@ -77,7 +77,7 @@
 					writeln!(f)?;
 					write!(f, "    ")?;
 				}
-				write!(f, "{}", line)?;
+				write!(f, "{line}")?;
 			}
 		}
 		Ok(())
@@ -125,8 +125,8 @@
 impl Display for ValuePathItem {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 		match self {
-			Self::Field(name) => write!(f, ".{:?}", name)?,
-			Self::Index(idx) => write!(f, "[{}]", idx)?,
+			Self::Field(name) => write!(f, ".{name:?}")?,
+			Self::Index(idx) => write!(f, "[{idx}]")?,
 		}
 		Ok(())
 	}
@@ -138,7 +138,7 @@
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 		write!(f, "self")?;
 		for elem in self.0.iter().rev() {
-			write!(f, "{}", elem)?;
+			write!(f, "{elem}")?;
 		}
 		Ok(())
 	}
@@ -171,7 +171,7 @@
 					for (i, item) in a.iter(s.clone()).enumerate() {
 						push_type_description(
 							s.clone(),
-							|| format!("array index {}", i),
+							|| format!("array index {i}"),
 							|| ValuePathItem::Index(i as u64),
 							|| elem_type.check(s.clone(), &item.clone()?),
 						)?;
@@ -185,7 +185,7 @@
 					for (i, item) in a.iter(s.clone()).enumerate() {
 						push_type_description(
 							s.clone(),
-							|| format!("array index {}", i),
+							|| format!("array index {i}"),
 							|| ValuePathItem::Index(i as u64),
 							|| elem_type.check(s.clone(), &item.clone()?),
 						)?;
@@ -200,7 +200,7 @@
 						if let Some(got_v) = obj.get(s.clone(), (*k).into())? {
 							push_type_description(
 								s.clone(),
-								|| format!("property {}", k),
+								|| format!("property {k}"),
 								|| ValuePathItem::Field((*k).into()),
 								|| v.check(s.clone(), &got_v),
 							)?;
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -11,7 +11,9 @@
 	stdlib::manifest::{
 		manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType, ManifestYamlOptions,
 	},
-	throw, ObjValue, Result, State, Unbound, WeakObjValue,
+	throw,
+	typed::BoundedUsize,
+	ObjValue, Result, State, Unbound, WeakObjValue,
 };
 
 pub trait ThunkValue: Trace {
@@ -184,16 +186,26 @@
 	}
 }
 
+/// Represents a Jsonnet array value.
 #[derive(Debug, Clone, Trace)]
 // may contrain other ArrValue
 #[trace(tracking(force))]
 pub enum ArrValue {
+	/// Layout optimized byte array.
 	Bytes(#[trace(skip)] IBytes),
+	/// Every element is lazy evaluated.
 	Lazy(Cc<Vec<Thunk<Val>>>),
+	/// Every field is already evaluated.
 	Eager(Cc<Vec<Val>>),
+	/// Concatenation of two arrays of any kind.
 	Extended(Box<(Self, Self)>),
+	/// Represents a integer array in form `[start, start + 1, ... end - 1, end]`.
+	/// This kind of arrays is generated by `std.range(start, end)` call, and used for loops.
 	Range(i32, i32),
+	/// Sliced array view.
 	Slice(Box<Slice>),
+	/// Reversed array view.
+	/// Returned by `std.reverse(other)` call
 	Reversed(Box<Self>),
 }
 
@@ -204,9 +216,13 @@
 	pub fn new_eager() -> Self {
 		Self::Eager(Cc::new(Vec::new()))
 	}
+	pub fn empty() -> Self {
+		Self::new_range(0, 0)
+	}
 
 	/// # Panics
 	/// If a > b
+	#[inline]
 	pub fn new_range(a: i32, b: i32) -> Self {
 		assert!(a <= b);
 		Self::Range(a, b)
@@ -231,6 +247,7 @@
 		}))
 	}
 
+	/// Array length.
 	pub fn len(&self) -> usize {
 		match self {
 			Self::Bytes(i) => i.len(),
@@ -243,10 +260,14 @@
 		}
 	}
 
+	/// Is array contains no elements?
 	pub fn is_empty(&self) -> bool {
 		self.len() == 0
 	}
 
+	/// Get array element by index, evaluating it, if it is lazy.
+	///
+	/// Returns `None` on out-of-bounds condition.
 	pub fn get(&self, s: State, index: usize) -> Result<Option<Val>> {
 		match self {
 			Self::Bytes(i) => i
@@ -286,11 +307,14 @@
 				if index >= v.to() {
 					return Ok(None);
 				}
-				v.inner.get(s, index as usize)
+				v.inner.get(s, index)
 			}
 		}
 	}
 
+	/// Get array element by index, without evaluation.
+	///
+	/// Returns `None` on out-of-bounds condition.
 	pub fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
 		match self {
 			Self::Bytes(i) => i
@@ -326,11 +350,12 @@
 				if index >= s.to() {
 					return None;
 				}
-				s.inner.get_lazy(index as usize)
+				s.inner.get_lazy(index)
 			}
 		}
 	}
 
+	/// Evaluate all array elements, returning new array.
 	pub fn evaluated(&self, s: State) -> Result<Cc<Vec<Val>>> {
 		Ok(match self {
 			Self::Bytes(i) => {
@@ -383,6 +408,7 @@
 		})
 	}
 
+	/// Iterate over elements, evaluating them.
 	pub fn iter(&self, s: State) -> impl DoubleEndedIterator<Item = Result<Val>> + '_ {
 		(0..self.len()).map(move |idx| match self {
 			Self::Bytes(b) => Ok(Val::Num(f64::from(b[idx]))),
@@ -394,6 +420,7 @@
 		})
 	}
 
+	/// Iterate over elements, returning lazy values.
 	pub fn iter_lazy(&self) -> impl DoubleEndedIterator<Item = Thunk<Val>> + '_ {
 		(0..self.len()).map(move |idx| match self {
 			Self::Bytes(b) => Thunk::evaluated(Val::Num(f64::from(b[idx]))),
@@ -405,11 +432,13 @@
 		})
 	}
 
+	/// Return a reversed view on current array.
 	#[must_use]
 	pub fn reversed(self) -> Self {
 		Self::Reversed(Box::new(self))
 	}
 
+	/// Return a new array, produced by passing every element of current array to specified callback function.
 	pub fn map(self, s: State, mapper: impl Fn(Val) -> Result<Val>) -> Result<Self> {
 		let mut out = Vec::with_capacity(self.len());
 
@@ -420,6 +449,7 @@
 		Ok(Self::Eager(Cc::new(out)))
 	}
 
+	/// Return a new array, produced from current array by removing every value, for which specified callback function returns false.
 	pub fn filter(self, s: State, filter: impl Fn(&Val) -> Result<bool>) -> Result<Self> {
 		let mut out = Vec::with_capacity(self.len());
 
@@ -454,25 +484,100 @@
 	}
 }
 
+/// Represents a Jsonnet value, which can be spliced or indexed (string or array).
 #[allow(clippy::module_name_repetitions)]
 pub enum IndexableVal {
+	/// String.
 	Str(IStr),
+	/// Array.
 	Arr(ArrValue),
 }
+impl IndexableVal {
+	/// Slice the value.
+	///
+	/// # Implementation
+	///
+	/// For strings, will create a copy of specified interval.
+	///
+	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.
+	pub fn slice(
+		self,
+		index: Option<BoundedUsize<0, { i32::MAX as usize }>>,
+		end: Option<BoundedUsize<0, { i32::MAX as usize }>>,
+		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,
+	) -> Result<Self> {
+		match &self {
+			IndexableVal::Str(s) => {
+				let index = index.as_deref().copied().unwrap_or(0);
+				let end = end.as_deref().copied().unwrap_or(usize::MAX);
+				let step = step.as_deref().copied().unwrap_or(1);
 
+				if index >= end {
+					return Ok(Self::Str("".into()));
+				}
+
+				Ok(Self::Str(
+					(s.chars()
+						.skip(index)
+						.take(end - index)
+						.step_by(step)
+						.collect::<String>())
+					.into(),
+				))
+			}
+			IndexableVal::Arr(arr) => {
+				let index = index.as_deref().copied().unwrap_or(0);
+				let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());
+				let step = step.as_deref().copied().unwrap_or(1);
+
+				if index >= end {
+					return Ok(Self::Arr(ArrValue::new_eager()));
+				}
+
+				Ok(Self::Arr(ArrValue::Slice(Box::new(Slice {
+					inner: arr.clone(),
+					from: index as u32,
+					to: end as u32,
+					step: step as u32,
+				}))))
+			}
+		}
+	}
+}
+
+/// Represents any valid Jsonnet value.
 #[derive(Debug, Clone, Trace)]
 pub enum Val {
+	/// Represents a Jsonnet boolean.
 	Bool(bool),
+	/// Represents a Jsonnet null value.
 	Null,
+	/// Represents a Jsonnet string.
 	Str(IStr),
+	/// Represents a Jsonnet number.
+	/// Should be finite, and not NaN
+	/// This restriction isn't enforced by enum, as enum field can't be marked as private
 	Num(f64),
+	/// Represents a Jsonnet array.
 	Arr(ArrValue),
+	/// Represents a Jsonnet object.
 	Obj(ObjValue),
+	/// Represents a Jsonnet function.
 	Func(FuncVal),
 }
 
-#[cfg(target_pointer_width = "64")]
-static_assertions::assert_eq_size!(Val, [u8; 32]);
+impl From<IndexableVal> for Val {
+	fn from(v: IndexableVal) -> Self {
+		match v {
+			IndexableVal::Str(s) => Self::Str(s),
+			IndexableVal::Arr(a) => Self::Arr(a),
+		}
+	}
+}
+
+// Broken between stable and nightly, as there is new layout size optimization
+// #[cfg(target_pointer_width = "64")]
+// static_assertions::assert_eq_size!(Val, [u8; 24]);
 
 impl Val {
 	pub const fn as_bool(&self) -> Option<bool> {
deletedcrates/jrsonnet-evaluator/tests/as_native.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/as_native.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-use jrsonnet_evaluator::{error::Result, State};
-
-mod common;
-
-#[test]
-fn as_native() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-
-	let val = s.evaluate_snippet("snip".to_owned(), r#"function(a, b) a + b"#.into())?;
-	let func = val.as_func().expect("this is function");
-
-	let native = func.into_native::<((u32, u32), u32)>();
-
-	ensure_eq!(native(s.clone(), 1, 2)?, 3);
-	ensure_eq!(native(s, 3, 4)?, 7);
-
-	Ok(())
-}
deletedcrates/jrsonnet-evaluator/tests/builtin.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/builtin.rs
+++ /dev/null
@@ -1,100 +0,0 @@
-mod common;
-
-use jrsonnet_evaluator::{
-	error::Result,
-	function::{builtin, builtin::Builtin, CallLocation, FuncVal},
-	tb,
-	typed::Typed,
-	State, Val,
-};
-use jrsonnet_gcmodule::Cc;
-
-#[builtin]
-fn a() -> Result<u32> {
-	Ok(1)
-}
-
-#[test]
-fn basic_function() -> Result<()> {
-	let s = State::default();
-	let a: a = a {};
-	let v = u32::from_untyped(
-		a.call(
-			s.clone(),
-			s.create_default_context(),
-			CallLocation::native(),
-			&(),
-		)?,
-		s,
-	)?;
-
-	ensure_eq!(v, 1);
-	Ok(())
-}
-
-#[builtin]
-fn native_add(a: u32, b: u32) -> Result<u32> {
-	Ok(a + b)
-}
-
-#[test]
-fn call_from_code() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-	s.settings_mut().globals.insert(
-		"nativeAdd".into(),
-		Val::Func(FuncVal::StaticBuiltin(native_add::INST)),
-	);
-
-	let v = s.evaluate_snippet(
-		"snip".to_owned(),
-		"
-            assert nativeAdd(1, 2) == 3;
-            assert nativeAdd(100, 200) == 300;
-            null
-        "
-		.into(),
-	)?;
-	ensure_val_eq!(s, v, Val::Null);
-	Ok(())
-}
-
-#[builtin(fields(
-    a: u32
-))]
-fn curried_add(this: &curried_add, b: u32) -> Result<u32> {
-	Ok(this.a + b)
-}
-
-#[builtin]
-fn curry_add(a: u32) -> Result<FuncVal> {
-	Ok(FuncVal::Builtin(Cc::new(tb!(curried_add { a }))))
-}
-
-#[test]
-fn nonstatic_builtin() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-	s.settings_mut().globals.insert(
-		"curryAdd".into(),
-		Val::Func(FuncVal::StaticBuiltin(curry_add::INST)),
-	);
-
-	let v = s.evaluate_snippet(
-		"snip".to_owned(),
-		"
-            local a = curryAdd(1);
-            local b = curryAdd(4);
-
-            assert a(2) == 3;
-            assert a(200) == 201;
-
-            assert b(2) == 6;
-            assert b(200) == 204;
-            null
-        "
-		.into(),
-	)?;
-	ensure_val_eq!(s, v, Val::Null);
-	Ok(())
-}
deletedcrates/jrsonnet-evaluator/tests/common.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/common.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-use jrsonnet_evaluator::{
-	error::Result,
-	function::{builtin, FuncVal},
-	throw_runtime, ObjValueBuilder, State, Thunk, Val,
-};
-
-#[macro_export]
-macro_rules! ensure_eq {
-	($a:expr, $b:expr $(,)?) => {{
-		let a = &$a;
-		let b = &$b;
-		if a != b {
-			::jrsonnet_evaluator::throw_runtime!("assertion failed: a != b\na={:#?}\nb={:#?}", a, b)
-		}
-	}};
-}
-
-#[macro_export]
-macro_rules! ensure {
-	($v:expr $(,)?) => {
-		if !$v {
-			::jrsonnet_evaluator::throw_runtime!("assertion failed: {}", stringify!($v))
-		}
-	};
-}
-
-#[macro_export]
-macro_rules! ensure_val_eq {
-	($s:expr, $a:expr, $b:expr) => {{
-		if !::jrsonnet_evaluator::val::equals($s.clone(), &$a.clone(), &$b.clone())? {
-			::jrsonnet_evaluator::throw_runtime!(
-				"assertion failed: a != b\na={:#?}\nb={:#?}",
-				$a.to_json(
-					$s.clone(),
-					2,
-					#[cfg(feature = "exp-preserve-order")]
-					false
-				)?,
-				$b.to_json(
-					$s.clone(),
-					2,
-					#[cfg(feature = "exp-preserve-order")]
-					false
-				)?,
-			)
-		}
-	}};
-}
-
-#[builtin]
-fn assert_throw(s: State, lazy: Thunk<Val>, message: String) -> Result<bool> {
-	match lazy.evaluate(s) {
-		Ok(_) => {
-			throw_runtime!("expected argument to throw on evaluation, but it returned instead")
-		}
-		Err(e) => {
-			let error = format!("{}", e.error());
-			ensure_eq!(message, error);
-		}
-	}
-	Ok(true)
-}
-
-#[allow(dead_code)]
-pub fn with_test(s: &State) {
-	let mut bobj = ObjValueBuilder::new();
-	bobj.member("assertThrow".into())
-		.hide()
-		.value(
-			s.clone(),
-			Val::Func(FuncVal::StaticBuiltin(assert_throw::INST)),
-		)
-		.expect("no error");
-
-	s.settings_mut()
-		.globals
-		.insert("test".into(), Val::Obj(bobj.build()));
-}
deletedcrates/jrsonnet-evaluator/tests/golden.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden.rs
+++ /dev/null
@@ -1,69 +0,0 @@
-use std::{
-	fs, io,
-	path::{Path, PathBuf},
-};
-
-use jrsonnet_evaluator::{
-	trace::{CompactFormat, PathResolver},
-	FileImportResolver, State,
-};
-
-mod common;
-
-fn run(root: &Path, file: &Path) -> String {
-	let s = State::default();
-	s.set_trace_format(Box::new(CompactFormat {
-		resolver: PathResolver::Relative(root.to_owned()),
-		padding: 3,
-	}));
-	s.with_stdlib();
-	common::with_test(&s);
-	s.set_import_resolver(Box::new(FileImportResolver::default()));
-
-	let v = match s.import(file.to_owned()) {
-		Ok(v) => v,
-		Err(e) => return s.stringify_err(&e),
-	};
-	match v.to_json(
-		s.clone(),
-		3,
-		#[cfg(feature = "exp-preserve-order")]
-		false,
-	) {
-		Ok(v) => v.to_string(),
-		Err(e) => s.stringify_err(&e),
-	}
-}
-
-#[test]
-fn test() -> io::Result<()> {
-	let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
-	root.push("tests/golden");
-
-	for entry in fs::read_dir(&root)? {
-		let entry = entry?;
-		if !entry.path().extension().map_or(false, |e| e == "jsonnet") {
-			continue;
-		}
-
-		let result = run(&root, &entry.path());
-
-		let mut golden_path = entry.path();
-		golden_path.set_extension("jsonnet.golden");
-
-		if !golden_path.exists() {
-			fs::write(golden_path, &result)?;
-		} else {
-			let golden = fs::read_to_string(golden_path)?;
-
-			assert_eq!(
-				result,
-				golden,
-				"golden didn't match for {}",
-				entry.path().display()
-			)
-		}
-	}
-
-	Ok(())
-}
deletedcrates/jrsonnet-evaluator/tests/golden/array_comp.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/array_comp.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-[[a, b] for a in [1, 2, 3] for b in [4, 5, 6]]
deletedcrates/jrsonnet-evaluator/tests/golden/array_comp.jsonnet.goldendiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/array_comp.jsonnet.golden
+++ /dev/null
@@ -1,38 +0,0 @@
-[
-   [
-      1,
-      4
-   ],
-   [
-      1,
-      5
-   ],
-   [
-      1,
-      6
-   ],
-   [
-      2,
-      4
-   ],
-   [
-      2,
-      5
-   ],
-   [
-      2,
-      6
-   ],
-   [
-      3,
-      4
-   ],
-   [
-      3,
-      5
-   ],
-   [
-      3,
-      6
-   ]
-]
\ No newline at end of file
deletedcrates/jrsonnet-evaluator/tests/golden/builtin_json.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/builtin_json.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-std.manifestJsonEx({ a: 3, b: 4, c: 6 }, '')
deletedcrates/jrsonnet-evaluator/tests/golden/builtin_json.jsonnet.goldendiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/builtin_json.jsonnet.golden
+++ /dev/null
@@ -1 +0,0 @@
-"{\n\"a\": 3,\n\"b\": 4,\n\"c\": 6\n}"
\ No newline at end of file
deletedcrates/jrsonnet-evaluator/tests/golden/builtin_json_minified.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/builtin_json_minified.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-std.manifestJsonMinified({ a: 3, b: 4, c: 6 })
deletedcrates/jrsonnet-evaluator/tests/golden/builtin_json_minified.jsonnet.goldendiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/builtin_json_minified.jsonnet.golden
+++ /dev/null
@@ -1 +0,0 @@
-"{\"a\":3,\"b\":4,\"c\":6}"
\ No newline at end of file
deletedcrates/jrsonnet-evaluator/tests/golden/builtin_parseJson.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/builtin_parseJson.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-std.parseJson('{"a": -1,"b": 1,"c": 3.141,"d": []}')
deletedcrates/jrsonnet-evaluator/tests/golden/builtin_parseJson.jsonnet.goldendiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/builtin_parseJson.jsonnet.golden
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-   "a": -1,
-   "b": 1,
-   "c": 3.141,
-   "d": [ ]
-}
\ No newline at end of file
deletedcrates/jrsonnet-evaluator/tests/golden/issue23.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/issue23.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-import 'issue23.jsonnet'
deletedcrates/jrsonnet-evaluator/tests/golden/issue23.jsonnet.goldendiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/issue23.jsonnet.golden
+++ /dev/null
@@ -1,2 +0,0 @@
-infinite recursion detected
-   issue23.jsonnet:1:1-26: import "issue23.jsonnet"
\ No newline at end of file
deletedcrates/jrsonnet-evaluator/tests/golden/issue40.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/issue40.jsonnet
+++ /dev/null
@@ -1,9 +0,0 @@
-local conf = {
-  n: '',
-};
-
-local result = conf {
-  assert std.isNumber(self.n) : 'is number',
-};
-
-std.manifestJsonEx(result, '')
deletedcrates/jrsonnet-evaluator/tests/golden/issue40.jsonnet.goldendiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/issue40.jsonnet.golden
+++ /dev/null
@@ -1,3 +0,0 @@
-assert failed: is number
-   issue40.jsonnet:6:10-31: assertion failure
-   issue40.jsonnet:9:1-32:  function <builtin_manifest_json_ex> call
\ No newline at end of file
deletedcrates/jrsonnet-evaluator/tests/golden/missing_binding.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/missing_binding.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-sta
deletedcrates/jrsonnet-evaluator/tests/golden/missing_binding.jsonnet.goldendiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/missing_binding.jsonnet.golden
+++ /dev/null
@@ -1,3 +0,0 @@
-variable is not defined: sta
-There is variable with similar name present: std
-   missing_binding.jsonnet:1:1-5: variable <sta> access
\ No newline at end of file
deletedcrates/jrsonnet-evaluator/tests/golden/object_comp.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/object_comp.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-{ local t = 'a', ['h' + i + '_' + z]: if 'h' + (i - 1) + '_' + z in self then t + 1 else 0 + t for i in [1, 2, 3] for z in [2, 3, 4] if z != i }
deletedcrates/jrsonnet-evaluator/tests/golden/object_comp.jsonnet.goldendiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/object_comp.jsonnet.golden
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-   "h1_2": "0a",
-   "h1_3": "0a",
-   "h1_4": "0a",
-   "h2_3": "a1",
-   "h2_4": "a1",
-   "h3_2": "0a",
-   "h3_4": "a1"
-}
\ No newline at end of file
deletedcrates/jrsonnet-evaluator/tests/golden/test_assertThrow.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/test_assertThrow.jsonnet
+++ /dev/null
@@ -1,2 +0,0 @@
-// Test that test.assertThrow will return error, if body is not errored
-test.assertThrow(1, '1')
deletedcrates/jrsonnet-evaluator/tests/golden/test_assertThrow.jsonnet.goldendiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/golden/test_assertThrow.jsonnet.golden
+++ /dev/null
@@ -1,2 +0,0 @@
-runtime error: expected argument to throw on evaluation, but it returned instead
-   test_assertThrow.jsonnet:2:1-26: function <assert_throw> call
\ No newline at end of file
deletedcrates/jrsonnet-evaluator/tests/sanity.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/sanity.rs
+++ /dev/null
@@ -1,41 +0,0 @@
-use jrsonnet_evaluator::{error::Result, throw_runtime, State, Val};
-
-mod common;
-
-#[test]
-fn assert_positive() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-
-	let v = s.evaluate_snippet("snip".to_owned(), "assert 1 == 1: 'fail'; null".into())?;
-	ensure_val_eq!(s, v, Val::Null);
-	let v = s.evaluate_snippet("snip".to_owned(), "std.assertEqual(1, 1)".into())?;
-	ensure_val_eq!(s, v, Val::Bool(true));
-
-	Ok(())
-}
-
-#[test]
-fn assert_negative() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-
-	{
-		let e = match s.evaluate_snippet("snip".to_owned(), "assert 1 == 2: 'fail'; null".into()) {
-			Ok(_) => throw_runtime!("assertion should fail"),
-			Err(e) => e,
-		};
-		let e = s.stringify_err(&e);
-		ensure!(e.starts_with("assert failed: fail\n"));
-	}
-	{
-		let e = match s.evaluate_snippet("snip".to_owned(), "std.assertEqual(1, 2)".into()) {
-			Ok(_) => throw_runtime!("assertion should fail"),
-			Err(e) => e,
-		};
-		let e = s.stringify_err(&e);
-		ensure!(e.starts_with("runtime error: Assertion failed. 1 != 2"))
-	}
-
-	Ok(())
-}
deletedcrates/jrsonnet-evaluator/tests/suite.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite.rs
+++ /dev/null
@@ -1,46 +0,0 @@
-use std::{
-	fs, io,
-	path::{Path, PathBuf},
-};
-
-use jrsonnet_evaluator::{
-	trace::{CompactFormat, PathResolver},
-	FileImportResolver, State, Val,
-};
-
-mod common;
-
-fn run(root: &Path, file: &Path) {
-	let s = State::default();
-	s.set_trace_format(Box::new(CompactFormat {
-		resolver: PathResolver::Relative(root.to_owned()),
-		padding: 3,
-	}));
-	s.with_stdlib();
-	common::with_test(&s);
-	s.set_import_resolver(Box::new(FileImportResolver::default()));
-
-	match s.import(file.to_owned()) {
-		Ok(Val::Bool(true)) => {}
-		Ok(Val::Bool(false)) => panic!("test {} returned false", file.display()),
-		Ok(_) => panic!("test {} returned wrong type as result", file.display()),
-		Err(e) => panic!("test {} failed:\n{}", file.display(), s.stringify_err(&e)),
-	};
-}
-
-#[test]
-fn test() -> io::Result<()> {
-	let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
-	root.push("tests/suite");
-
-	for entry in fs::read_dir(&root)? {
-		let entry = entry?;
-		if !entry.path().extension().map_or(false, |e| e == "jsonnet") {
-			continue;
-		}
-
-		run(&root, &entry.path());
-	}
-
-	Ok(())
-}
deletedcrates/jrsonnet-evaluator/tests/suite/builtin_ascii.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/builtin_ascii.jsonnet
+++ /dev/null
@@ -1,3 +0,0 @@
-std.assertEqual(std.asciiUpper('aBc😀'), 'ABC😀') &&
-std.assertEqual(std.asciiLower('aBc😀'), 'abc😀') &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/builtin_base64.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/builtin_base64.jsonnet
+++ /dev/null
@@ -1,2 +0,0 @@
-std.assertEqual(std.base64('test'), 'dGVzdA==') &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/builtin_chars.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/builtin_chars.jsonnet
+++ /dev/null
@@ -1,3 +0,0 @@
-local c = '😎';
-std.assertEqual({ c: std.codepoint(c), l: std.length(c) }, { c: 128526, l: 1 }) &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/builtin_constant.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/builtin_constant.jsonnet
+++ /dev/null
@@ -1,3 +0,0 @@
-local std2 = std; local std = std2 { primitiveEquals(a, b):: false };
-// In jsonnet, this expression was failing because of being desugared to std.primitiveEquals(1, 1)
-std.assertEqual(1 == 1, true)
deletedcrates/jrsonnet-evaluator/tests/suite/builtin_count.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/builtin_count.jsonnet
+++ /dev/null
@@ -1,4 +0,0 @@
-std.assertEqual(std.count([], ''), 0) &&
-std.assertEqual(std.count(['a', 'b', 'a'], 'd'), 0) &&
-std.assertEqual(std.count(['a', 'b', 'a'], 'a'), 2) &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/builtin_join.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/builtin_join.jsonnet
+++ /dev/null
@@ -1,4 +0,0 @@
-std.assertEqual(std.join([0, 0], [[1, 2], [3, 4], [5, 6]]), [1, 2, 0, 0, 3, 4, 0, 0, 5, 6]) &&
-std.assertEqual(std.join(',', ['1', '2', '3', '4']), '1,2,3,4') &&
-std.assertEqual(std.join(',', ['1', null, '2', null, '3']), '1,2,3') &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/builtin_member.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/builtin_member.jsonnet
+++ /dev/null
@@ -1,7 +0,0 @@
-!std.member('', '') &&
-std.member('abc', 'a') &&
-!std.member('abc', 'd') &&
-!std.member([], '') &&
-std.member(['a', 'b', 'c'], 'a') &&
-!std.member(['a', 'b', 'c'], 'd') &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/function_args.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/function_args.jsonnet
+++ /dev/null
@@ -1,3 +0,0 @@
-std.assertEqual(local a = function(b, c=2) b + c; a(2), 4) &&
-std.assertEqual(local a = function(b, c='Dear') b + c + d, d = 'World'; a('Hello'), 'HelloDearWorld') &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/function_context.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/function_context.jsonnet
+++ /dev/null
@@ -1,10 +0,0 @@
-local k = {
-  t(name=self.h): [self.h, name],
-  h: 3,
-};
-local f = {
-  t: k.t(),
-  h: 4,
-};
-std.assertEqual(f.t[0], f.t[1]) &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/function_lazy_args.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/function_lazy_args.jsonnet
+++ /dev/null
@@ -1,5 +0,0 @@
-local fun(a) = 2;
-std.assertEqual(fun(error '3'), 2) &&
-// But in tailstrict mode arguments are evaluated eagerly
-test.assertThrow(fun(error '3') tailstrict, 'runtime error: 3') &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/local.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/local.jsonnet
+++ /dev/null
@@ -1,4 +0,0 @@
-std.assertEqual(local a = 2; local b = 3; a + b, 5) &&
-std.assertEqual(local a = 1, b = a + 1; a + b, 3) &&
-std.assertEqual(local a = 1; local a = 2; a, 2) &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/math.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/math.jsonnet
+++ /dev/null
@@ -1,3 +0,0 @@
-std.assertEqual(2 + 2 * 2, 6) &&
-std.assertEqual(3 + (2 + 2 * 2), 9) &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/object_assertion.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/object_assertion.jsonnet
+++ /dev/null
@@ -1,3 +0,0 @@
-std.assertEqual({ assert 'a' in self : 'missing a' } + { a: 2 }, { a: 2 }) &&
-test.assertThrow({ assert 'a' in self : 'missing a', b: 1 }.b, 'assert failed: missing a') &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/object_comp_self.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/object_comp_self.jsonnet
+++ /dev/null
@@ -1,8 +0,0 @@
-std.assertEqual(std.objectFields({
-  a: {
-    [name]: name
-    for name in std.objectFields(self)
-  },
-  b: 2,
-  c: 3,
-}.a), ['a', 'b', 'c'])
deletedcrates/jrsonnet-evaluator/tests/suite/object_context.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/object_context.jsonnet
+++ /dev/null
@@ -1,13 +0,0 @@
-// `self` assigned to `me` was lost when being
-// referenced from field
-std.assertEqual({
-  local me = self,
-  a: 3,
-  b: me.a,
-}.b, 3) &&
-std.assertEqual({
-  local me = self,
-  a: 3,
-  b(): me.a,
-}.b(), 3) &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/object_fields.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/object_fields.jsonnet
+++ /dev/null
@@ -1,4 +0,0 @@
-local a = 'a', b = null;
-std.assertEqual({ [a]: 2 }, { a: 2 }) &&
-std.assertEqual({ [b]: 2 }, {}) &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/object_inheritance.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/object_inheritance.jsonnet
+++ /dev/null
@@ -1,17 +0,0 @@
-std.assertEqual({ a: self.b } + { b: 3 }, { a: 3, b: 3 }) &&
-std.assertEqual(
-  {
-    name: 'Alice',
-    welcome: 'Hello ' + self.name + '!',
-  },
-  { name: 'Alice', welcome: 'Hello Alice!' },
-) &&
-std.assertEqual(
-  {
-    name: 'Alice',
-    welcome: 'Hello ' + self.name + '!',
-  } + {
-    name: 'Bob',
-  }, { name: 'Bob', welcome: 'Hello Bob!' }
-) &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/object_locals.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/object_locals.jsonnet
+++ /dev/null
@@ -1,4 +0,0 @@
-std.assertEqual({ local a = 3, b: a }, { b: 3 }) &&
-std.assertEqual({ local a = 3, local c = a, b: c }, { b: 3 }) &&
-std.assertEqual({ local a = function(b) { [b]: 4 }, test: a('test') }, { test: { test: 4 } }) &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/object_super_standalone.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/object_super_standalone.jsonnet
+++ /dev/null
@@ -1,11 +0,0 @@
-local obj = {
-  a: 1,
-  b: 2,
-  c: 3,
-};
-local test = obj + {
-  fields: std.objectFields(super),
-  d: 5,
-};
-std.assertEqual(test.fields, ['a', 'b', 'c']) &&
-true
deletedcrates/jrsonnet-evaluator/tests/suite/sjsonnet_issue_127.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/suite/sjsonnet_issue_127.jsonnet
+++ /dev/null
@@ -1,6 +0,0 @@
-local myFunc = function(a)
-  if (a) then "a" else "b";
-
-local b = "aaa";
-
-std.assertEqual(myFunc(b == [] || b == ['e']), "b")
deletedcrates/jrsonnet-evaluator/tests/suite/string_concat.jsonnetdiffbeforeafterboth

no changes

deletedcrates/jrsonnet-evaluator/tests/typed_obj.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/typed_obj.rs
+++ /dev/null
@@ -1,194 +0,0 @@
-mod common;
-
-use std::fmt::Debug;
-
-use jrsonnet_evaluator::{error::Result, typed::Typed, State};
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct A {
-	a: u32,
-	b: u16,
-}
-
-fn test_roundtrip<T: Typed + PartialEq + Debug + Clone>(value: T, s: State) -> Result<()> {
-	let untyped = T::into_untyped(value.clone(), s.clone())?;
-	let value2 = T::from_untyped(untyped.clone(), s.clone())?;
-	ensure_eq!(value, value2);
-	let untyped2 = T::into_untyped(value2, s.clone())?;
-	ensure_val_eq!(s, untyped, untyped2);
-
-	Ok(())
-}
-
-#[test]
-fn simple_object() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-	let a = A::from_untyped(
-		s.evaluate_snippet("snip".to_owned(), "{a: 1, b: 2}".into())?,
-		s.clone(),
-	)?;
-	ensure_eq!(a, A { a: 1, b: 2 });
-	test_roundtrip(a, s)?;
-	Ok(())
-}
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct B {
-	a: u32,
-	#[typed(rename = "c")]
-	b: u16,
-}
-
-#[test]
-fn renamed_field() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-	let b = B::from_untyped(
-		s.evaluate_snippet("snip".to_owned(), "{a: 1, c: 2}".into())?,
-		s.clone(),
-	)?;
-	ensure_eq!(b, B { a: 1, b: 2 });
-	ensure_eq!(
-		&B::into_untyped(b.clone(), s.clone())?.to_string(s.clone())? as &str,
-		r#"{"a": 1, "c": 2}"#,
-	);
-	test_roundtrip(b, s)?;
-	Ok(())
-}
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct ObjectKind {
-	#[typed(rename = "apiVersion")]
-	api_version: String,
-	#[typed(rename = "kind")]
-	kind: String,
-}
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct Object {
-	#[typed(flatten)]
-	kind: ObjectKind,
-	b: u16,
-}
-
-#[test]
-fn flattened_object() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-	let obj = Object::from_untyped(
-		s.evaluate_snippet(
-			"snip".to_owned(),
-			"{apiVersion: 'ver', kind: 'kind', b: 2}".into(),
-		)?,
-		s.clone(),
-	)?;
-	ensure_eq!(
-		obj,
-		Object {
-			kind: ObjectKind {
-				api_version: "ver".into(),
-				kind: "kind".into(),
-			},
-			b: 2
-		}
-	);
-	ensure_eq!(
-		&Object::into_untyped(obj.clone(), s.clone())?.to_string(s.clone())? as &str,
-		r#"{"apiVersion": "ver", "b": 2, "kind": "kind"}"#,
-	);
-	test_roundtrip(obj, s)?;
-	Ok(())
-}
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct C {
-	a: Option<u32>,
-	b: u16,
-}
-
-#[test]
-fn optional_field_some() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-	let c = C::from_untyped(
-		s.evaluate_snippet("snip".to_owned(), "{a: 1, b: 2}".into())?,
-		s.clone(),
-	)?;
-	ensure_eq!(c, C { a: Some(1), b: 2 });
-	ensure_eq!(
-		&C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
-		r#"{"a": 1, "b": 2}"#,
-	);
-	test_roundtrip(c, s)?;
-	Ok(())
-}
-
-#[test]
-fn optional_field_none() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-	let c = C::from_untyped(
-		s.evaluate_snippet("snip".to_owned(), "{b: 2}".into())?,
-		s.clone(),
-	)?;
-	ensure_eq!(c, C { a: None, b: 2 });
-	ensure_eq!(
-		&C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
-		r#"{"b": 2}"#,
-	);
-	test_roundtrip(c, s)?;
-	Ok(())
-}
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct D {
-	#[typed(flatten(ok))]
-	e: Option<E>,
-	b: u16,
-}
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct E {
-	v: u32,
-}
-
-#[test]
-fn flatten_optional_some() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-	let d = D::from_untyped(
-		s.evaluate_snippet("snip".to_owned(), "{b: 2, v:1}".into())?,
-		s.clone(),
-	)?;
-	ensure_eq!(
-		d,
-		D {
-			e: Some(E { v: 1 }),
-			b: 2
-		}
-	);
-	ensure_eq!(
-		&D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
-		r#"{"b": 2, "v": 1}"#,
-	);
-	test_roundtrip(d, s)?;
-	Ok(())
-}
-
-#[test]
-fn flatten_optional_none() -> Result<()> {
-	let s = State::default();
-	s.with_stdlib();
-	let d = D::from_untyped(
-		s.evaluate_snippet("snip".to_owned(), "{b: 2, v: '1'}".into())?,
-		s.clone(),
-	)?;
-	ensure_eq!(d, D { e: None, b: 2 });
-	ensure_eq!(
-		&D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
-		r#"{"b": 2}"#,
-	);
-	test_roundtrip(d, s)?;
-	Ok(())
-}
modifiedcrates/jrsonnet-interner/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-interner/Cargo.toml
+++ b/crates/jrsonnet-interner/Cargo.toml
@@ -7,12 +7,19 @@
 edition = "2021"
 
 [features]
-default = ["serde"]
+default = []
+# Implement value serialization using structdump
+structdump = ["dep:structdump"]
+# Implement value serialization using serde
+#
+# Warning: serialized values won't be deduplicated
 serde = ["dep:serde"]
 
 [dependencies]
 jrsonnet-gcmodule = { version = "0.3.4" }
 
 serde = { version = "1.0", optional = true }
+structdump = { version = "0.2.0", optional = true }
+
 rustc-hash = "1.1"
 hashbrown = { version = "0.12.1", features = ["inline-more"] }
modifiedcrates/jrsonnet-interner/src/inner.rsdiffbeforeafterboth
--- a/crates/jrsonnet-interner/src/inner.rs
+++ b/crates/jrsonnet-interner/src/inner.rs
@@ -84,6 +84,8 @@
 		unsafe { Self::new_raw(str.as_bytes(), true) }
 	}
 
+	// `slice::from_raw_parts` is not yet stabilized
+	#[allow(clippy::missing_const_for_fn)]
 	pub fn as_slice(&self) -> &[u8] {
 		let header = Self::header(self);
 		// SAFETY: data is not null, and it is correctly initialized
modifiedcrates/jrsonnet-interner/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-interner/src/lib.rs
+++ b/crates/jrsonnet-interner/src/lib.rs
@@ -33,6 +33,10 @@
 
 impl IStr {
 	#[must_use]
+	pub fn empty() -> Self {
+		"".into()
+	}
+	#[must_use]
 	pub fn as_str(&self) -> &str {
 		self as &str
 	}
@@ -201,6 +205,7 @@
 	}
 }
 
+#[cfg(feature = "serde")]
 impl serde::Serialize for IStr {
 	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
 	where
@@ -210,6 +215,7 @@
 	}
 }
 
+#[cfg(feature = "serde")]
 impl<'de> serde::Deserialize<'de> for IStr {
 	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
 	where
@@ -220,6 +226,24 @@
 	}
 }
 
+#[cfg(feature = "structdump")]
+impl structdump::Codegen for IStr {
+	fn gen_code(
+		&self,
+		res: &mut structdump::CodegenResult,
+		_unique: bool,
+	) -> structdump::TokenStream {
+		let s: &str = self;
+		res.add_code(
+			structdump::quote! {
+				structdump_import::IStr::from(#s)
+			},
+			Some(structdump::quote![structdump_import::IStr]),
+			false,
+		)
+	}
+}
+
 thread_local! {
 	static POOL: RefCell<HashMap<Inner, (), BuildHasherDefault<FxHasher>>> = RefCell::new(HashMap::with_capacity_and_hasher(200, BuildHasherDefault::default()));
 }
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -122,13 +122,13 @@
 	Normal {
 		ty: Box<Type>,
 		is_option: bool,
-		name: String,
+		name: Option<String>,
 		cfg_attrs: Vec<Attribute>,
 		// ident: Ident,
 	},
 	Lazy {
 		is_option: bool,
-		name: String,
+		name: Option<String>,
 	},
 	State,
 	Location,
@@ -142,8 +142,8 @@
 			FnArg::Typed(a) => a,
 		};
 		let ident = match &arg.pat as &Pat {
-			Pat::Ident(i) => i.ident.clone(),
-			_ => return Err(Error::new(arg.pat.span(), "arg should be plain identifier")),
+			Pat::Ident(i) => Some(i.ident.clone()),
+			_ => None,
 		};
 		let ty = &arg.ty;
 		if type_is_path(ty, "State").is_some() {
@@ -153,7 +153,7 @@
 		} else if type_is_path(ty, "Thunk").is_some() {
 			return Ok(Self::Lazy {
 				is_option: false,
-				name: ident.to_string(),
+				name: ident.map(|v| v.to_string()),
 			});
 		}
 
@@ -166,7 +166,7 @@
 			if type_is_path(ty, "Thunk").is_some() {
 				return Ok(Self::Lazy {
 					is_option: true,
-					name: ident.to_string(),
+					name: ident.map(|v| v.to_string()),
 				});
 			}
 
@@ -185,7 +185,7 @@
 		Ok(Self::Normal {
 			ty,
 			is_option,
-			name: ident.to_string(),
+			name: ident.map(|v| v.to_string()),
 			cfg_attrs,
 		})
 	}
@@ -248,69 +248,95 @@
 			name,
 			cfg_attrs,
 			..
-		} => Some(quote! {
-			#(#cfg_attrs)*
-			BuiltinParam {
-				name: std::borrow::Cow::Borrowed(#name),
-				has_default: #is_option,
-			},
-		}),
-		ArgInfo::Lazy { is_option, name } => Some(quote! {
-			BuiltinParam {
-				name: std::borrow::Cow::Borrowed(#name),
-				has_default: #is_option,
-			},
-		}),
+		} => {
+			let name = name
+				.as_ref()
+				.map(|n| quote! {Some(std::borrow::Cow::Borrowed(#n))})
+				.unwrap_or_else(|| quote! {None});
+			Some(quote! {
+				#(#cfg_attrs)*
+				BuiltinParam {
+					name: #name,
+					has_default: #is_option,
+				},
+			})
+		}
+		ArgInfo::Lazy { is_option, name } => {
+			let name = name
+				.as_ref()
+				.map(|n| quote! {Some(std::borrow::Cow::Borrowed(#n))})
+				.unwrap_or_else(|| quote! {None});
+			Some(quote! {
+				BuiltinParam {
+					name: #name,
+					has_default: #is_option,
+				},
+			})
+		}
 		ArgInfo::State => None,
 		ArgInfo::Location => None,
 		ArgInfo::This => None,
 	});
 
-	let pass = args.iter().map(|a| match a {
-		ArgInfo::Normal {
-			ty,
-			is_option,
-			name,
-			cfg_attrs,
-		} => {
-			let eval = quote! {s.push_description(
-				|| format!("argument <{}> evaluation", #name),
-				|| <#ty>::from_untyped(value.evaluate(s.clone())?, s.clone()),
-			)?};
-			let value = if *is_option {
-				quote! {if let Some(value) = parsed.get(#name) {
-					Some(#eval)
+	let mut id = 0usize;
+	let pass = args
+		.iter()
+		.map(|a| match a {
+			ArgInfo::Normal { .. } | ArgInfo::Lazy { .. } => {
+				let cid = id;
+				id += 1;
+				(quote! {#cid}, a)
+			}
+			ArgInfo::State | ArgInfo::Location | ArgInfo::This => {
+				(quote! {compile_error!("should not use id")}, a)
+			}
+		})
+		.map(|(id, a)| match a {
+			ArgInfo::Normal {
+				ty,
+				is_option,
+				name,
+				cfg_attrs,
+			} => {
+				let name = name.as_ref().map(|v| v.as_str()).unwrap_or("<unnamed>");
+				let eval = quote! {s.push_description(
+					|| format!("argument <{}> evaluation", #name),
+					|| <#ty>::from_untyped(value.evaluate(s.clone())?, s.clone()),
+				)?};
+				let value = if *is_option {
+					quote! {if let Some(value) = &parsed[#id] {
+						Some(#eval)
+					} else {
+						None
+					},}
 				} else {
-					None
-				},}
-			} else {
-				quote! {{
-					let value = parsed.get(#name).expect("args shape is checked");
-					#eval
-				},}
-			};
-			quote! {
-				#(#cfg_attrs)*
-				#value
+					quote! {{
+						let value = parsed[#id].as_ref().expect("args shape is checked");
+						#eval
+					},}
+				};
+				quote! {
+					#(#cfg_attrs)*
+					#value
+				}
 			}
-		}
-		ArgInfo::Lazy { is_option, name } => {
-			if *is_option {
-				quote! {if let Some(value) = parsed.get(#name) {
-					Some(value.clone())
+			ArgInfo::Lazy { is_option, .. } => {
+				if *is_option {
+					quote! {if let Some(value) = &parsed[#id] {
+						Some(value.clone())
+					} else {
+						None
+					}}
 				} else {
-					None
-				}}
-			} else {
-				quote! {
-					parsed.get(#name).expect("args shape is correct").clone(),
+					quote! {
+						parsed[#id].as_ref().expect("args shape is correct").clone(),
+					}
 				}
 			}
-		}
-		ArgInfo::State => quote! {s.clone(),},
-		ArgInfo::Location => quote! {location,},
-		ArgInfo::This => quote! {self,},
-	});
+			ArgInfo::State => quote! {s.clone(),},
+			ArgInfo::Location => quote! {location,},
+			ArgInfo::This => quote! {self,},
+		});
 
 	let fields = attr.fields.iter().map(|field| {
 		let name = &field.name;
modifiedcrates/jrsonnet-parser/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-parser/Cargo.toml
+++ b/crates/jrsonnet-parser/Cargo.toml
@@ -7,7 +7,23 @@
 edition = "2021"
 
 [features]
+default = []
 exp-destruct = []
+# Implement serialization of AST using structdump
+#
+# Structdump generates code, which exactly replicated passed AST
+# Contrary to serde, has no code bloat problem, and is recommended
+#
+# The only limitation is serialized form is only useable if built from build script
+structdump = ["dep:structdump", "jrsonnet-interner/structdump"]
+# Implement serialization of AST using serde
+#
+# Warning: as serde doesn't deduplicate strings, `Source` struct will bloat
+# output binary with repeating source code. To resolve this issue, you should either
+# override serialization of this struct using custom `Serializer`/`Deserializer`,
+# not rely on Source, and fill its `source_code` with empty value, or use `structdump`
+# instead
+serde = ["dep:serde"]
 
 [dependencies]
 jrsonnet-interner = { path = "../jrsonnet-interner", version = "0.4.2" }
@@ -18,6 +34,4 @@
 peg = "0.8.0"
 
 serde = { version = "1.0", features = ["derive", "rc"], optional = true }
-
-[dev-dependencies]
-jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.4.2" }
+structdump = { version = "0.2.0", features = ["derive"], optional = true }
modifiedcrates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -8,10 +8,13 @@
 use jrsonnet_interner::IStr;
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Serialize};
+#[cfg(feature = "structdump")]
+use structdump::Codegen;
 
 use crate::source::Source;
 
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[derive(Debug, PartialEq, Trace)]
 pub enum FieldName {
 	/// {fixed: 2}
@@ -20,6 +23,7 @@
 	Dyn(LocExpr),
 }
 
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)]
 pub enum Visibility {
@@ -37,10 +41,12 @@
 	}
 }
 
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Clone, Debug, PartialEq, Trace)]
 pub struct AssertStmt(pub LocExpr, pub Option<LocExpr>);
 
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, PartialEq, Trace)]
 pub struct FieldMember {
@@ -51,6 +57,7 @@
 	pub value: LocExpr,
 }
 
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, PartialEq, Trace)]
 pub enum Member {
@@ -59,6 +66,7 @@
 	AssertStmt(AssertStmt),
 }
 
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)]
 pub enum UnaryOpType {
@@ -84,6 +92,7 @@
 	}
 }
 
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)]
 pub enum BinaryOpType {
@@ -150,11 +159,13 @@
 }
 
 /// name, default value
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, PartialEq, Trace)]
 pub struct Param(pub Destruct, pub Option<LocExpr>);
 
 /// Defined function parameters
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, Clone, PartialEq, Trace)]
 pub struct ParamsDesc(pub Rc<Vec<Param>>);
@@ -166,6 +177,7 @@
 	}
 }
 
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, PartialEq, Trace)]
 pub struct ArgsDesc {
@@ -187,6 +199,7 @@
 	Drop,
 }
 
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, Clone, PartialEq, Trace)]
 pub enum Destruct {
@@ -216,6 +229,7 @@
 	}
 }
 
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, Clone, PartialEq, Trace)]
 pub enum BindSpec {
@@ -230,14 +244,17 @@
 	},
 }
 
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, PartialEq, Trace)]
 pub struct IfSpecData(pub LocExpr);
 
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, PartialEq, Trace)]
 pub struct ForSpecData(pub IStr, pub LocExpr);
 
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, PartialEq, Trace)]
 pub enum CompSpec {
@@ -245,6 +262,7 @@
 	ForSpec(ForSpecData),
 }
 
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, PartialEq, Trace)]
 pub struct ObjComp {
@@ -256,6 +274,7 @@
 	pub compspecs: Vec<CompSpec>,
 }
 
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, PartialEq, Trace)]
 pub enum ObjBody {
@@ -263,6 +282,7 @@
 	ObjComp(ObjComp),
 }
 
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, PartialEq, Eq, Clone, Copy, Trace)]
 pub enum LiteralType {
@@ -274,6 +294,7 @@
 	False,
 }
 
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, PartialEq, Trace)]
 pub struct SliceDesc {
@@ -283,6 +304,7 @@
 }
 
 /// Syntax base
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, PartialEq, Trace)]
 pub enum Expr {
@@ -341,12 +363,6 @@
 	Index(LocExpr, LocExpr),
 	/// function(x) x
 	Function(ParamsDesc, LocExpr),
-	/// std.thisFile
-	IntrinsicThisFile,
-	/// std.id,
-	IntrinsicId,
-	/// std.primitiveEquals
-	Intrinsic(IStr),
 	/// if true == false then 1 else 2
 	IfElse {
 		cond: IfSpecData,
@@ -357,6 +373,7 @@
 }
 
 /// file, begin offset, end offset
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Clone, PartialEq, Eq, Trace)]
 #[trace(skip)]
@@ -379,6 +396,7 @@
 
 /// Holds AST expression and its location in source file
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[derive(Clone, PartialEq, Trace)]
 pub struct LocExpr(pub Rc<Expr>, pub ExprLocation);
 
modifiedcrates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -7,9 +7,11 @@
 pub use expr::*;
 pub use jrsonnet_interner::IStr;
 pub use peg;
+mod location;
 mod source;
 mod unescape;
-pub use source::Source;
+pub use location::CodeLocation;
+pub use source::{Source, SourceDirectory, SourceFile, SourcePath, SourcePathT, SourceVirtual};
 
 pub struct ParserSettings {
 	pub file_name: Source,
@@ -193,7 +195,7 @@
 			/ 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})* "[" _ key:expr(s) _ "]" _ plus:"+"? _ ":" _ value:expr(s) post_locals:(comma() b:obj_local(s) {b})* _ forspec:forspec(s) others:(_ rest:compspec(s) {rest})? {
+			= pre_locals:(b: obj_local(s) comma() {b})* "[" _ key:expr(s) _ "]" _ plus:"+"? _ ":" _ value:expr(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());
 				expr::ObjBody::ObjComp(expr::ObjComp{
@@ -251,10 +253,6 @@
 
 		pub rule expr_basic(s: &ParserSettings) -> Expr
 			= literal(s)
-
-			/ quiet!{"$intrinsicThisFile" {Expr::IntrinsicThisFile}}
-			/ quiet!{"$intrinsicId" {Expr::IntrinsicId}}
-			/ quiet!{"$intrinsic(" name:id() ")" {Expr::Intrinsic(name)}}
 
 			/ string_expr(s) / number_expr(s)
 			/ array_expr(s)
@@ -362,8 +360,7 @@
 
 #[cfg(test)]
 pub mod tests {
-	use std::borrow::Cow;
-
+	use jrsonnet_interner::IStr;
 	use BinaryOpType::*;
 
 	use super::{expr::*, parse};
@@ -374,7 +371,7 @@
 			parse(
 				$s,
 				&ParserSettings {
-					file_name: Source::new_virtual(Cow::Borrowed("<test>")),
+					file_name: Source::new_virtual("<test>".into(), IStr::empty()),
 				},
 			)
 			.unwrap()
@@ -385,7 +382,11 @@
 		($expr:expr, $from:expr, $to:expr$(,)?) => {
 			LocExpr(
 				std::rc::Rc::new($expr),
-				ExprLocation(Source::new_virtual(Cow::Borrowed("<test>")), $from, $to),
+				ExprLocation(
+					Source::new_virtual("<test>".into(), IStr::empty()),
+					$from,
+					$to,
+				),
 			)
 		};
 	}
@@ -715,15 +716,10 @@
 	}
 
 	#[test]
-	fn can_parse_stdlib() {
-		parse!(jrsonnet_stdlib::STDLIB_STR);
-	}
-
-	#[test]
 	fn add_location_info_to_all_sub_expressions() {
 		use Expr::*;
 
-		let file_name = Source::new_virtual(Cow::Borrowed("<test>"));
+		let file_name = Source::new_virtual("<test>".into(), IStr::empty());
 		let expr = parse(
 			"{} { local x = 1, x: x } + {}",
 			&ParserSettings { file_name },
addedcrates/jrsonnet-parser/src/location.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-parser/src/location.rs
@@ -0,0 +1,124 @@
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone, PartialEq, Eq, Debug)]
+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(file: &str, offsets: &[u32]) -> Vec<CodeLocation> {
+	if offsets.is_empty() {
+		return vec![];
+	}
+	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 = vec![
+		CodeLocation {
+			offset: 0,
+			column: 0,
+			line: 0,
+			line_start_offset: 0,
+			line_end_offset: 0
+		};
+		offsets.len()
+	];
+	let mut with_no_known_line_ending = vec![];
+	let mut this_line_offset = 0;
+	for (pos, ch) in file
+		.chars()
+		.enumerate()
+		.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]
+			),
+			vec![
+				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
+				}
+			]
+		)
+	}
+}
modifiedcrates/jrsonnet-parser/src/source.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/source.rs
+++ b/crates/jrsonnet-parser/src/source.rs
@@ -1,26 +1,267 @@
 use std::{
-	borrow::Cow,
-	fmt,
-	path::{Component, Path, PathBuf},
+	any::Any,
+	fmt::{self, Debug, Display},
+	hash::{Hash, Hasher},
+	path::{Path, PathBuf},
 	rc::Rc,
 };
 
 use jrsonnet_gcmodule::{Trace, Tracer};
+use jrsonnet_interner::IStr;
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Serialize};
+#[cfg(feature = "structdump")]
+use structdump::Codegen;
+
+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 other = if let Some(v) = other.as_any().downcast_ref::<Self>() {
+				v
+			} 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: Trace + 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
+///
+/// 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, Debug, Clone)]
+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 Trace for SourcePath {
+	fn trace(&self, tracer: &mut Tracer) {
+		(*self.0).trace(tracer)
+	}
+
+	fn is_type_tracked() -> bool
+	where
+		Self: Sized,
+	{
+		true
+	}
+}
+impl Display 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))
+	}
+}
 
-#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
-#[derive(PartialEq, Eq, Debug, Hash)]
-enum Inner {
-	Real(PathBuf),
-	Virtual(Cow<'static, str>),
+#[cfg(feature = "structdump")]
+impl Codegen for SourcePath {
+	fn gen_code(
+		&self,
+		res: &mut structdump::CodegenResult,
+		unique: bool,
+	) -> structdump::TokenStream {
+		let source_virtual = self
+			.0
+			.as_any()
+			.downcast_ref::<SourceVirtual>()
+			.expect("can only codegen for virtual source paths!")
+			.0
+			.clone();
+		let val = res.add_value(source_virtual, false);
+		res.add_code(
+			structdump::quote! {
+				structdump_import::SourcePath::new(structdump_import::SourceVirtual(#val))
+			},
+			Some(structdump::quote!(SourcePath)),
+			unique,
+		)
+	}
 }
 
+#[derive(Trace, 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);
+}
+
+/// 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(Trace, 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(Trace, 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
+#[cfg_attr(feature = "structdump", derive(Codegen))]
+#[derive(Trace, Hash, PartialEq, Eq, Debug, Clone)]
+pub struct SourceVirtual(pub IStr);
+impl Display for SourceVirtual {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "{}", self.0)
+	}
+}
+impl SourcePathT for SourceVirtual {
+	fn is_default(&self) -> bool {
+		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
+#[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Clone, PartialEq, Eq, Debug)]
-pub struct Source(Rc<Inner>);
+pub struct Source(pub Rc<(SourcePath, IStr)>);
 static_assertions::assert_eq_size!(Source, *const ());
 
 impl Trace for Source {
@@ -32,62 +273,26 @@
 }
 
 impl Source {
-	/// Fails when path contains inner /../ or /./ references, or not absolute
-	pub fn new(path: PathBuf) -> Option<Self> {
-		if !path.is_absolute()
-			|| path
-				.components()
-				.any(|c| matches!(c, Component::CurDir | Component::ParentDir))
-		{
-			return None;
-		}
-		Some(Self(Rc::new(Inner::Real(path))))
+	pub fn new(path: SourcePath, code: IStr) -> Self {
+		Self(Rc::new((path, code)))
 	}
 
-	pub fn new_virtual(n: Cow<'static, str>) -> Self {
-		Self(Rc::new(Inner::Virtual(n)))
+	pub fn new_virtual(name: IStr, code: IStr) -> Self {
+		Self::new(SourcePath::new(SourceVirtual(name)), code)
 	}
 
-	pub fn short_display(&self) -> ShortDisplay {
-		ShortDisplay(self.clone())
-	}
-	pub fn full_path(&self) -> String {
-		match self.inner() {
-			Inner::Real(r) => r.display().to_string(),
-			Inner::Virtual(v) => v.to_string(),
-		}
+	pub fn code(&self) -> &str {
+		&self.0 .1
 	}
 
-	/// Returns None if file is virtual
-	pub fn path(&self) -> Option<&Path> {
-		match self.inner() {
-			Inner::Real(r) => Some(r),
-			Inner::Virtual(_) => None,
-		}
-	}
-	pub fn repr(&self) -> Result<&Path, &str> {
-		match self.inner() {
-			Inner::Real(r) => Ok(r),
-			Inner::Virtual(v) => Err(v.as_ref()),
-		}
+	pub fn source_path(&self) -> &SourcePath {
+		&self.0 .0
 	}
 
-	fn inner(&self) -> &Inner {
-		&self.0 as &Inner
+	pub fn map_source_locations(&self, locs: &[u32]) -> Vec<CodeLocation> {
+		offset_to_location(&self.0 .1, locs)
 	}
-}
-pub struct ShortDisplay(Source);
-impl fmt::Display for ShortDisplay {
-	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-		match &self.0 .0 as &Inner {
-			Inner::Real(r) => {
-				write!(
-					f,
-					"{}",
-					r.file_name().expect("path is valid").to_string_lossy()
-				)
-			}
-			Inner::Virtual(n) => write!(f, "{}", n),
-		}
+	pub fn map_from_source_location(&self, line: usize, column: usize) -> Option<usize> {
+		location_to_offset(&self.0 .1, line, column)
 	}
 }
modifiedcrates/jrsonnet-stdlib/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/Cargo.toml
+++ b/crates/jrsonnet-stdlib/Cargo.toml
@@ -7,5 +7,44 @@
 edition = "2021"
 
 [features]
+default = ["codegenerated-stdlib"]
+# Speed-up initialization by generating code for parsed stdlib, instead
+# of invoking parser for it
+codegenerated-stdlib = ["jrsonnet-parser/structdump"]
+# Enables legacy `std.thisFile` support, at the cost of worse caching
+legacy-this-file = []
+# Add order preservation flag to some functions
+exp-preserve-order = ["jrsonnet-evaluator/exp-preserve-order"]
+# Preserve order for files parsed via `std.parseJson`
+# Shame it isn't possible to enable per parse call, instead of globally
+exp-serde-preserve-order = [
+    "serde_json/preserve_order",
+    "jrsonnet-evaluator/exp-serde-preserve-order",
+]
 
 [dependencies]
+jrsonnet-evaluator = { path = "../jrsonnet-evaluator", features = [
+    # std.parseJson parses file via serde, then converts Value to evaluator Val
+    "serde_json",
+], version = "0.4.2" }
+jrsonnet-macros = { path = "../jrsonnet-macros", version = "0.4.2" }
+jrsonnet-parser = { path = "../jrsonnet-parser", version = "0.4.2" }
+jrsonnet-gcmodule = "0.3.4"
+
+# Used for stdlib AST serialization
+bincode = { version = "1.3", optional = true }
+# Used both for stdlib AST serialization and std.parseJson/std.parseYaml
+serde = "1.0"
+
+# std.md5
+md5 = "0.7.0"
+# std.base64
+base64 = "0.13.0"
+# std.parseJson
+serde_json = "1.0"
+# std.parseYaml, custom library fork is used for C++/golang compatibility
+serde_yaml_with_quirks = "0.8.24"
+
+[build-dependencies]
+jrsonnet-parser = { path = "../jrsonnet-parser", version = "0.4.2" }
+structdump = { version = "0.2.0", features = ["derive"] }
deletedcrates/jrsonnet-stdlib/README.mddiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# jrsonnet-stdlib
-
-Jsonnet standard library packaged as crate
addedcrates/jrsonnet-stdlib/build.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-stdlib/build.rs
@@ -0,0 +1,33 @@
+use std::{env, fs::File, io::Write, path::Path};
+
+use jrsonnet_parser::{parse, ParserSettings, Source};
+use structdump::CodegenResult;
+
+fn main() {
+	let parsed = parse(
+		include_str!("./src/std.jsonnet"),
+		&ParserSettings {
+			file_name: Source::new_virtual(
+				"<std>".into(),
+				include_str!("./src/std.jsonnet").into(),
+			),
+		},
+	)
+	.expect("parse");
+
+	let mut out = CodegenResult::default();
+
+	let v = out.codegen(&parsed, true);
+
+	{
+		let out_dir = env::var("OUT_DIR").unwrap();
+		let dest_path = Path::new(&out_dir).join("stdlib.rs");
+		let mut f = File::create(&dest_path).unwrap();
+		f.write_all(
+			("#[allow(clippy::redundant_clone)]".to_owned() + &v.to_string())
+				.replace(';', ";\n")
+				.as_bytes(),
+		)
+		.unwrap();
+	}
+}
addedcrates/jrsonnet-stdlib/src/arrays.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/arrays.rs
@@ -0,0 +1,216 @@
+use jrsonnet_evaluator::{
+	error::Result,
+	function::{builtin, FuncVal},
+	throw_runtime,
+	typed::{Any, BoundedUsize, Typed, VecVal},
+	val::{equals, ArrValue, IndexableVal},
+	IStr, State, Val,
+};
+use jrsonnet_gcmodule::Cc;
+
+#[builtin]
+pub fn builtin_make_array(s: State, sz: usize, func: FuncVal) -> Result<VecVal> {
+	let mut out = Vec::with_capacity(sz);
+	for i in 0..sz {
+		out.push(func.evaluate_simple(s.clone(), &(i as f64,))?);
+	}
+	Ok(VecVal(Cc::new(out)))
+}
+
+#[builtin]
+pub fn builtin_slice(
+	indexable: IndexableVal,
+	index: Option<BoundedUsize<0, { i32::MAX as usize }>>,
+	end: Option<BoundedUsize<0, { i32::MAX as usize }>>,
+	step: Option<BoundedUsize<1, { i32::MAX as usize }>>,
+) -> Result<Any> {
+	indexable.slice(index, end, step).map(Val::from).map(Any)
+}
+
+#[builtin]
+pub fn builtin_map(s: State, func: FuncVal, arr: ArrValue) -> Result<ArrValue> {
+	arr.map(s.clone(), |val| {
+		func.evaluate_simple(s.clone(), &(Any(val),))
+	})
+}
+
+#[builtin]
+pub fn builtin_flatmap(s: State, func: FuncVal, arr: IndexableVal) -> Result<IndexableVal> {
+	match arr {
+		IndexableVal::Str(str) => {
+			let mut out = String::new();
+			for c in str.chars() {
+				match func.evaluate_simple(s.clone(), &(c.to_string(),))? {
+					Val::Str(o) => out.push_str(&o),
+					Val::Null => continue,
+					_ => throw_runtime!("in std.join all items should be strings"),
+				};
+			}
+			Ok(IndexableVal::Str(out.into()))
+		}
+		IndexableVal::Arr(a) => {
+			let mut out = Vec::new();
+			for el in a.iter(s.clone()) {
+				let el = el?;
+				match func.evaluate_simple(s.clone(), &(Any(el),))? {
+					Val::Arr(o) => {
+						for oe in o.iter(s.clone()) {
+							out.push(oe?);
+						}
+					}
+					Val::Null => continue,
+					_ => throw_runtime!("in std.join all items should be arrays"),
+				};
+			}
+			Ok(IndexableVal::Arr(out.into()))
+		}
+	}
+}
+
+#[builtin]
+pub fn builtin_filter(s: State, func: FuncVal, arr: ArrValue) -> Result<ArrValue> {
+	arr.filter(s.clone(), |val| {
+		bool::from_untyped(
+			func.evaluate_simple(s.clone(), &(Any(val.clone()),))?,
+			s.clone(),
+		)
+	})
+}
+
+#[builtin]
+pub fn builtin_foldl(s: State, func: FuncVal, arr: ArrValue, init: Any) -> Result<Any> {
+	let mut acc = init.0;
+	for i in arr.iter(s.clone()) {
+		acc = func.evaluate_simple(s.clone(), &(Any(acc), Any(i?)))?;
+	}
+	Ok(Any(acc))
+}
+
+#[builtin]
+pub fn builtin_foldr(s: State, func: FuncVal, arr: ArrValue, init: Any) -> Result<Any> {
+	let mut acc = init.0;
+	for i in arr.iter(s.clone()).rev() {
+		acc = func.evaluate_simple(s.clone(), &(Any(i?), Any(acc)))?;
+	}
+	Ok(Any(acc))
+}
+
+#[builtin]
+pub fn builtin_range(from: i32, to: i32) -> Result<ArrValue> {
+	if to < from {
+		return Ok(ArrValue::new_eager());
+	}
+	Ok(ArrValue::new_range(from, to))
+}
+
+#[builtin]
+pub fn builtin_join(s: State, sep: IndexableVal, arr: ArrValue) -> Result<IndexableVal> {
+	Ok(match sep {
+		IndexableVal::Arr(joiner_items) => {
+			let mut out = Vec::new();
+
+			let mut first = true;
+			for item in arr.iter(s.clone()) {
+				let item = item?.clone();
+				if let Val::Arr(items) = item {
+					if !first {
+						out.reserve(joiner_items.len());
+						// TODO: extend
+						for item in joiner_items.iter(s.clone()) {
+							out.push(item?);
+						}
+					}
+					first = false;
+					out.reserve(items.len());
+					for item in items.iter(s.clone()) {
+						out.push(item?);
+					}
+				} else if matches!(item, Val::Null) {
+					continue;
+				} else {
+					throw_runtime!("in std.join all items should be arrays");
+				}
+			}
+
+			IndexableVal::Arr(out.into())
+		}
+		IndexableVal::Str(sep) => {
+			let mut out = String::new();
+
+			let mut first = true;
+			for item in arr.iter(s) {
+				let item = item?.clone();
+				if let Val::Str(item) = item {
+					if !first {
+						out += &sep;
+					}
+					first = false;
+					out += &item;
+				} else if matches!(item, Val::Null) {
+					continue;
+				} else {
+					throw_runtime!("in std.join all items should be strings");
+				}
+			}
+
+			IndexableVal::Str(out.into())
+		}
+	})
+}
+
+#[builtin]
+pub fn builtin_reverse(value: ArrValue) -> Result<ArrValue> {
+	Ok(value.reversed())
+}
+
+#[builtin]
+pub fn builtin_any(s: State, arr: ArrValue) -> Result<bool> {
+	for v in arr.iter(s.clone()) {
+		let v = bool::from_untyped(v?, s.clone())?;
+		if v {
+			return Ok(true);
+		}
+	}
+	Ok(false)
+}
+
+#[builtin]
+pub fn builtin_all(s: State, arr: ArrValue) -> Result<bool> {
+	for v in arr.iter(s.clone()) {
+		let v = bool::from_untyped(v?, s.clone())?;
+		if !v {
+			return Ok(false);
+		}
+	}
+	Ok(true)
+}
+
+#[builtin]
+pub fn builtin_member(s: State, arr: IndexableVal, x: Any) -> Result<bool> {
+	match arr {
+		IndexableVal::Str(str) => {
+			let x: IStr = IStr::from_untyped(x.0, s)?;
+			Ok(!x.is_empty() && str.contains(&*x))
+		}
+		IndexableVal::Arr(a) => {
+			for item in a.iter(s.clone()) {
+				let item = item?;
+				if equals(s.clone(), &item, &x.0)? {
+					return Ok(true);
+				}
+			}
+			Ok(false)
+		}
+	}
+}
+
+#[builtin]
+pub fn builtin_count(s: State, arr: Vec<Any>, v: Any) -> Result<usize> {
+	let mut count = 0;
+	for item in &arr {
+		if equals(s.clone(), &item.0, &v.0)? {
+			count += 1;
+		}
+	}
+	Ok(count)
+}
addedcrates/jrsonnet-stdlib/src/encoding.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/encoding.rs
@@ -0,0 +1,41 @@
+use jrsonnet_evaluator::{
+	error::{Error::RuntimeError, Result},
+	function::builtin,
+	typed::{Either, Either2},
+	IBytes, IStr,
+};
+
+#[builtin]
+pub fn builtin_encode_utf8(str: IStr) -> Result<IBytes> {
+	Ok(str.cast_bytes())
+}
+
+#[builtin]
+pub fn builtin_decode_utf8(arr: IBytes) -> Result<IStr> {
+	Ok(arr
+		.cast_str()
+		.ok_or_else(|| RuntimeError("bad utf8".into()))?)
+}
+
+#[builtin]
+pub fn builtin_base64(input: Either![IBytes, IStr]) -> Result<String> {
+	use Either2::*;
+	Ok(match input {
+		A(a) => base64::encode(a.as_slice()),
+		B(l) => base64::encode(l.bytes().collect::<Vec<_>>()),
+	})
+}
+
+#[builtin]
+pub fn builtin_base64_decode_bytes(input: IStr) -> Result<IBytes> {
+	Ok(base64::decode(input.as_bytes())
+		.map_err(|_| RuntimeError("bad base64".into()))?
+		.as_slice()
+		.into())
+}
+
+#[builtin]
+pub fn builtin_base64_decode(input: IStr) -> Result<String> {
+	let bytes = base64::decode(input.as_bytes()).map_err(|_| RuntimeError("bad base64".into()))?;
+	Ok(String::from_utf8(bytes).map_err(|_| RuntimeError("bad utf8".into()))?)
+}
addedcrates/jrsonnet-stdlib/src/expr.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/expr.rs
@@ -0,0 +1,101 @@
+use jrsonnet_parser::LocExpr;
+
+mod structdump_import {
+	pub(super) use std::{option::Option, rc::Rc, vec};
+
+	pub(super) use jrsonnet_parser::*;
+}
+
+pub fn stdlib_expr() -> LocExpr {
+	#[cfg(feature = "serialized-stdlib")]
+	{
+		use bincode::{BincodeRead, DefaultOptions, Options};
+		use serde::{Deserialize, Deserializer};
+
+		struct LocDeserializer<R, O: Options> {
+			source: Source,
+			wrapped: bincode::Deserializer<R, O>,
+		}
+		macro_rules! delegate {
+			($(fn $name:ident($($arg:ident: $ty:ty),*))+) => {$(
+				fn $name<V>(mut self $(, $arg: $ty)*, visitor: V) -> Result<V::Value, Self::Error>
+				where V: serde::de::Visitor<'de>,
+				{
+					self.wrapped.$name($($arg,)* visitor)
+				}
+			)+};
+		}
+		impl<'de, R, O> Deserializer<'de> for LocDeserializer<R, O>
+		where
+			R: BincodeRead<'de>,
+			O: Options,
+		{
+			type Error = <&'de mut bincode::Deserializer<R, O> as Deserializer<'de>>::Error;
+
+			delegate! {
+				fn deserialize_any()
+				fn deserialize_bool()
+				fn deserialize_u16()
+				fn deserialize_u32()
+				fn deserialize_u64()
+				fn deserialize_i16()
+				fn deserialize_i32()
+				fn deserialize_i64()
+				fn deserialize_f32()
+				fn deserialize_f64()
+				fn deserialize_u128()
+				fn deserialize_i128()
+				fn deserialize_u8()
+				fn deserialize_i8()
+				fn deserialize_unit()
+				fn deserialize_char()
+				fn deserialize_str()
+				fn deserialize_string()
+				fn deserialize_bytes()
+				fn deserialize_byte_buf()
+				fn deserialize_enum(name: &'static str, variants: &'static [&'static str])
+				fn deserialize_tuple(len: usize)
+				fn deserialize_option()
+				fn deserialize_seq()
+				fn deserialize_map()
+				fn deserialize_struct(name: &'static str, fields: &'static [&'static str])
+				fn deserialize_identifier()
+				fn deserialize_newtype_struct(name: &'static str)
+				fn deserialize_unit_struct(name: &'static str)
+				fn deserialize_tuple_struct(name: &'static str, len: usize)
+				fn deserialize_ignored_any()
+			}
+
+			fn is_human_readable(&self) -> bool {
+				false
+			}
+		}
+
+		// In build.rs, Source object is populated with empty values, deserializer wrapper loads correct values on deserialize
+		let mut deserializer = bincode::Deserializer::from_slice(
+			include_bytes!(concat!(env!("OUT_DIR"), "/stdlib.bincode")),
+			DefaultOptions::new()
+				.with_fixint_encoding()
+				.allow_trailing_bytes(),
+		);
+
+		// Should not panic, stdlib.bincode is generated in build.rs
+		LocExpr::deserialize(&mut deserializer).unwrap()
+	}
+
+	#[cfg(feature = "codegenerated-stdlib")]
+	{
+		include!(concat!(env!("OUT_DIR"), "/stdlib.rs"))
+	}
+
+	#[cfg(not(feature = "codegenerated-stdlib"))]
+	{
+		jrsonnet_parser::parse(
+			STDLIB_STR,
+			&ParserSettings {
+				file_name: Source::new_virtual(Cow::Borrowed("<std>"), STDLIB_STR.into()),
+			},
+		)
+		.unwrap()
+	}
+}
addedcrates/jrsonnet-stdlib/src/hash.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/hash.rs
@@ -0,0 +1,6 @@
+use jrsonnet_evaluator::{error::Result, function::builtin, IStr};
+
+#[builtin]
+pub fn builtin_md5(str: IStr) -> Result<String> {
+	Ok(format!("{:x}", md5::compute(str.as_bytes())))
+}
modifiedcrates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -1 +1,562 @@
-pub const STDLIB_STR: &str = include_str!("./std.jsonnet");
+use std::{
+	cell::{Ref, RefCell, RefMut},
+	collections::HashMap,
+	rc::Rc,
+};
+
+use jrsonnet_evaluator::{
+	error::{Error::*, Result},
+	function::{builtin::Builtin, ArgLike, CallLocation, FuncVal, TlaArg},
+	gc::{GcHashMap, TraceBox},
+	tb, throw_runtime,
+	trace::PathResolver,
+	typed::{Any, Either, Either2, Either4, VecVal, M1},
+	val::{equals, ArrValue},
+	Context, ContextBuilder, IStr, ObjValue, ObjValueBuilder, State, Thunk, Val,
+};
+use jrsonnet_gcmodule::Cc;
+use jrsonnet_macros::builtin;
+use jrsonnet_parser::Source;
+
+mod expr;
+mod types;
+pub use types::*;
+mod arrays;
+pub use arrays::*;
+mod math;
+pub use math::*;
+mod operator;
+pub use operator::*;
+mod sort;
+pub use sort::*;
+mod hash;
+pub use hash::*;
+mod encoding;
+pub use encoding::*;
+mod objects;
+pub use objects::*;
+mod manifest;
+pub use manifest::*;
+mod parse;
+pub use parse::*;
+
+pub fn stdlib_uncached(s: State, settings: Rc<RefCell<Settings>>) -> ObjValue {
+	let mut builder = ObjValueBuilder::new();
+
+	let expr = expr::stdlib_expr();
+	let eval = jrsonnet_evaluator::evaluate(s.clone(), Context::default(), &expr)
+		.expect("stdlib.jsonnet should have no errors")
+		.as_obj()
+		.expect("stdlib.jsonnet should evaluate to object");
+
+	builder.with_super(eval);
+
+	for (name, builtin) in [
+		("length", builtin_length::INST),
+		// Types
+		("type", builtin_type::INST),
+		("isString", builtin_is_string::INST),
+		("isNumber", builtin_is_number::INST),
+		("isBoolean", builtin_is_boolean::INST),
+		("isObject", builtin_is_object::INST),
+		("isArray", builtin_is_array::INST),
+		("isFunction", builtin_is_function::INST),
+		// Arrays
+		("makeArray", builtin_make_array::INST),
+		("slice", builtin_slice::INST),
+		("map", builtin_map::INST),
+		("flatMap", builtin_flatmap::INST),
+		("filter", builtin_filter::INST),
+		("foldl", builtin_foldl::INST),
+		("foldr", builtin_foldr::INST),
+		("range", builtin_range::INST),
+		("join", builtin_join::INST),
+		("reverse", builtin_reverse::INST),
+		("any", builtin_any::INST),
+		("all", builtin_all::INST),
+		("member", builtin_member::INST),
+		("count", builtin_count::INST),
+		// Math
+		("modulo", builtin_modulo::INST),
+		("floor", builtin_floor::INST),
+		("ceil", builtin_ceil::INST),
+		("log", builtin_log::INST),
+		("pow", builtin_pow::INST),
+		("sqrt", builtin_sqrt::INST),
+		("sin", builtin_sin::INST),
+		("cos", builtin_cos::INST),
+		("tan", builtin_tan::INST),
+		("asin", builtin_asin::INST),
+		("acos", builtin_acos::INST),
+		("atan", builtin_atan::INST),
+		("exp", builtin_exp::INST),
+		("mantissa", builtin_mantissa::INST),
+		("exponent", builtin_exponent::INST),
+		// Operator
+		("mod", builtin_mod::INST),
+		("primitiveEquals", builtin_primitive_equals::INST),
+		("equals", builtin_equals::INST),
+		("format", builtin_format::INST),
+		// Sort
+		("sort", builtin_sort::INST),
+		// Hash
+		("md5", builtin_md5::INST),
+		// Encoding
+		("encodeUTF8", builtin_encode_utf8::INST),
+		("decodeUTF8", builtin_decode_utf8::INST),
+		("base64", builtin_base64::INST),
+		("base64Decode", builtin_base64_decode::INST),
+		("base64DecodeBytes", builtin_base64_decode_bytes::INST),
+		// Objects
+		("objectFieldsEx", builtin_object_fields_ex::INST),
+		("objectHasEx", builtin_object_has_ex::INST),
+		// Manifest
+		("escapeStringJson", builtin_escape_string_json::INST),
+		("manifestJsonEx", builtin_manifest_json_ex::INST),
+		("manifestYamlDoc", builtin_manifest_yaml_doc::INST),
+		// Parsing
+		("parseJson", builtin_parse_json::INST),
+		("parseYaml", builtin_parse_yaml::INST),
+		// Misc
+		("codepoint", builtin_codepoint::INST),
+		("substr", builtin_substr::INST),
+		("char", builtin_char::INST),
+		("strReplace", builtin_str_replace::INST),
+		("splitLimit", builtin_splitlimit::INST),
+		("asciiUpper", builtin_ascii_upper::INST),
+		("asciiLower", builtin_ascii_lower::INST),
+		("findSubstr", builtin_find_substr::INST),
+		("startsWith", builtin_starts_with::INST),
+		("endsWith", builtin_ends_with::INST),
+	]
+	.iter()
+	.cloned()
+	{
+		builder
+			.member(name.into())
+			.hide()
+			.value(s.clone(), Val::Func(FuncVal::StaticBuiltin(builtin)))
+			.expect("no conflict");
+	}
+
+	builder
+		.member("extVar".into())
+		.hide()
+		.value(
+			s.clone(),
+			Val::Func(FuncVal::Builtin(Cc::new(tb!(builtin_ext_var {
+				settings: settings.clone()
+			})))),
+		)
+		.expect("no conflict");
+	builder
+		.member("native".into())
+		.hide()
+		.value(
+			s.clone(),
+			Val::Func(FuncVal::Builtin(Cc::new(tb!(builtin_native {
+				settings: settings.clone()
+			})))),
+		)
+		.expect("no conflict");
+	builder
+		.member("trace".into())
+		.hide()
+		.value(
+			s.clone(),
+			Val::Func(FuncVal::Builtin(Cc::new(tb!(builtin_trace { settings })))),
+		)
+		.expect("no conflict");
+
+	builder
+		.member("id".into())
+		.hide()
+		.value(s, Val::Func(FuncVal::Id))
+		.expect("no conflict");
+
+	builder.build()
+}
+
+pub trait TracePrinter {
+	fn print_trace(&self, s: State, loc: CallLocation, value: IStr);
+}
+
+pub struct StdTracePrinter {
+	resolver: PathResolver,
+}
+impl StdTracePrinter {
+	pub fn new(resolver: PathResolver) -> Self {
+		Self { resolver }
+	}
+}
+impl TracePrinter for StdTracePrinter {
+	fn print_trace(&self, _s: State, loc: CallLocation, value: IStr) {
+		eprint!("TRACE:");
+		if let Some(loc) = loc.0 {
+			let locs = loc.0.map_source_locations(&[loc.1]);
+			eprint!(
+				" {}:{}",
+				match loc.0.source_path().path() {
+					Some(p) => self.resolver.resolve(p),
+					None => loc.0.source_path().to_string(),
+				},
+				locs[0].line
+			);
+		}
+		eprintln!(" {}", value);
+	}
+}
+
+pub struct Settings {
+	/// Used for `std.extVar`
+	pub ext_vars: HashMap<IStr, TlaArg>,
+	/// Used for `std.native`
+	pub ext_natives: HashMap<IStr, Cc<TraceBox<dyn Builtin>>>,
+	/// Helper to add globals without implementing custom ContextInitializer
+	pub globals: GcHashMap<IStr, Thunk<Val>>,
+	/// Used for `std.trace`
+	pub trace_printer: Box<dyn TracePrinter>,
+	/// Used for `std.thisFile`
+	pub path_resolver: PathResolver,
+}
+
+pub fn extvar_source(name: &str, code: impl Into<IStr>) -> Source {
+	let source_name = format!("<extvar:{}>", name);
+	Source::new_virtual(source_name.into(), code.into())
+}
+
+pub struct ContextInitializer {
+	// When we don't need to support legacy-this-file, we can reuse same context for all files
+	#[cfg(not(feature = "legacy-this-file"))]
+	context: Context,
+	// Otherwise, we can only keep first stdlib layer, and then stack thisFile on top of it
+	#[cfg(feature = "legacy-this-file")]
+	stdlib_obj: ObjValue,
+	settings: Rc<RefCell<Settings>>,
+}
+impl ContextInitializer {
+	pub fn new(s: State, resolver: PathResolver) -> Self {
+		let settings = Settings {
+			ext_vars: Default::default(),
+			ext_natives: Default::default(),
+			globals: Default::default(),
+			trace_printer: Box::new(StdTracePrinter::new(resolver.clone())),
+			path_resolver: resolver,
+		};
+		let settings = Rc::new(RefCell::new(settings));
+		Self {
+			#[cfg(not(feature = "legacy-this-file"))]
+			context: {
+				let mut context = ContextBuilder::with_capacity(1);
+				context.bind(
+					"std".into(),
+					Thunk::evaluated(Val::Obj(stdlib_uncached(s, settings.clone()))),
+				);
+				context.build()
+			},
+			#[cfg(feature = "legacy-this-file")]
+			stdlib_obj: stdlib_uncached(s, settings.clone()),
+			settings,
+		}
+	}
+	pub fn settings(&self) -> Ref<Settings> {
+		self.settings.borrow()
+	}
+	pub fn settings_mut(&self) -> RefMut<Settings> {
+		self.settings.borrow_mut()
+	}
+	pub fn add_ext_var(&self, name: IStr, value: Val) {
+		self.settings_mut()
+			.ext_vars
+			.insert(name, TlaArg::Val(value));
+	}
+	pub fn add_ext_str(&self, name: IStr, value: IStr) {
+		self.settings_mut()
+			.ext_vars
+			.insert(name, TlaArg::String(value));
+	}
+	pub fn add_ext_code(&self, name: &str, code: impl Into<IStr>) -> Result<()> {
+		let code = code.into();
+		let source = extvar_source(name, code.clone());
+		let parsed = jrsonnet_parser::parse(
+			&code,
+			&jrsonnet_parser::ParserSettings {
+				file_name: source.clone(),
+			},
+		)
+		.map_err(|e| ImportSyntaxError {
+			path: source,
+			error: Box::new(e),
+		})?;
+		// self.data_mut().volatile_files.insert(source_name, code);
+		self.settings_mut()
+			.ext_vars
+			.insert(name.into(), TlaArg::Code(parsed));
+		Ok(())
+	}
+	pub fn add_native(&self, name: IStr, cb: Cc<TraceBox<dyn Builtin>>) {
+		self.settings_mut().ext_natives.insert(name, cb);
+	}
+}
+impl jrsonnet_evaluator::ContextInitializer for ContextInitializer {
+	#[cfg(not(feature = "legacy-this-file"))]
+	fn initialize(&self, _s: State, _source: Source) -> jrsonnet_evaluator::Context {
+		let out = self.context.clone();
+		let globals = &self.settings().globals;
+		if globals.is_empty() {
+			return out;
+		}
+
+		let mut out = ContextBuilder::extend(out);
+		for (k, v) in globals.iter() {
+			out.bind(k.clone(), v.clone());
+		}
+		out.build()
+	}
+	#[cfg(feature = "legacy-this-file")]
+	fn initialize(&self, s: State, source: Source) -> jrsonnet_evaluator::Context {
+		let mut builder = ObjValueBuilder::new();
+		builder.with_super(self.stdlib_obj.clone());
+		builder
+			.member("thisFile".into())
+			.hide()
+			.value(
+				s,
+				Val::Str(match source.source_path().path() {
+					Some(p) => self.settings().path_resolver.resolve(p).into(),
+					None => source.source_path().to_string().into(),
+				}),
+			)
+			.expect("this object builder is empty");
+		let stdlib_with_this_file = builder.build();
+
+		let mut context = ContextBuilder::with_capacity(1);
+		context.bind(
+			"std".into(),
+			Thunk::evaluated(Val::Obj(stdlib_with_this_file)),
+		);
+		for (k, v) in self.settings().globals.iter() {
+			context.bind(k.clone(), v.clone());
+		}
+		context.build()
+	}
+	fn as_any(&self) -> &dyn std::any::Any {
+		self
+	}
+}
+
+#[builtin]
+fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> Result<usize> {
+	use Either4::*;
+	Ok(match x {
+		A(x) => x.chars().count(),
+		B(x) => x.len(),
+		C(x) => x.len(),
+		D(f) => f.params_len(),
+	})
+}
+
+#[builtin]
+const fn builtin_codepoint(str: char) -> Result<u32> {
+	Ok(str as u32)
+}
+
+#[builtin]
+fn builtin_substr(str: IStr, from: usize, len: usize) -> Result<String> {
+	Ok(str.chars().skip(from).take(len).collect())
+}
+
+#[builtin(fields(
+	settings: Rc<RefCell<Settings>>,
+))]
+fn builtin_ext_var(this: &builtin_ext_var, s: State, x: IStr) -> Result<Any> {
+	let ctx = s.create_default_context(extvar_source(&x, ""));
+	Ok(Any(this
+		.settings
+		.borrow()
+		.ext_vars
+		.get(&x)
+		.cloned()
+		.ok_or_else(|| UndefinedExternalVariable(x))?
+		.evaluate_arg(s.clone(), ctx, true)?
+		.evaluate(s)?))
+}
+
+#[builtin(fields(
+	settings: Rc<RefCell<Settings>>,
+))]
+fn builtin_native(this: &builtin_native, name: IStr) -> Result<Any> {
+	Ok(Any(this
+		.settings
+		.borrow()
+		.ext_natives
+		.get(&name)
+		.cloned()
+		.map_or(Val::Null, |v| {
+			Val::Func(FuncVal::Builtin(v.clone()))
+		})))
+}
+
+#[builtin]
+fn builtin_char(n: u32) -> Result<char> {
+	Ok(std::char::from_u32(n).ok_or_else(|| InvalidUnicodeCodepointGot(n))?)
+}
+
+#[builtin(fields(
+	settings: Rc<RefCell<Settings>>,
+))]
+fn builtin_trace(
+	this: &builtin_trace,
+	s: State,
+	loc: CallLocation,
+	str: IStr,
+	rest: Thunk<Val>,
+) -> Result<Any> {
+	this.settings
+		.borrow()
+		.trace_printer
+		.print_trace(s.clone(), loc, str);
+	Ok(Any(rest.evaluate(s)?))
+}
+
+#[builtin]
+fn builtin_str_replace(str: String, from: IStr, to: IStr) -> Result<String> {
+	Ok(str.replace(&from as &str, &to as &str))
+}
+
+#[builtin]
+fn builtin_splitlimit(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> Result<VecVal> {
+	use Either2::*;
+	Ok(VecVal(Cc::new(match maxsplits {
+		A(n) => str
+			.splitn(n + 1, &c as &str)
+			.map(|s| Val::Str(s.into()))
+			.collect(),
+		B(_) => str.split(&c as &str).map(|s| Val::Str(s.into())).collect(),
+	})))
+}
+
+#[builtin]
+fn builtin_ascii_upper(str: IStr) -> Result<String> {
+	Ok(str.to_ascii_uppercase())
+}
+
+#[builtin]
+fn builtin_ascii_lower(str: IStr) -> Result<String> {
+	Ok(str.to_ascii_lowercase())
+}
+
+#[builtin]
+fn builtin_find_substr(pat: IStr, str: IStr) -> Result<ArrValue> {
+	if pat.is_empty() || str.is_empty() || pat.len() > str.len() {
+		return Ok(ArrValue::empty());
+	}
+
+	let str = str.as_str();
+	let pat = pat.as_bytes();
+	let strb = str.as_bytes();
+
+	let max_pos = str.len() - pat.len();
+
+	let mut out: Vec<Val> = Vec::new();
+	for (ch_idx, (i, _)) in str
+		.char_indices()
+		.take_while(|(i, _)| i <= &max_pos)
+		.enumerate()
+	{
+		if &strb[i..i + pat.len()] == pat {
+			out.push(Val::Num(ch_idx as f64))
+		}
+	}
+	Ok(out.into())
+}
+
+#[allow(clippy::comparison_chain)]
+#[builtin]
+fn builtin_starts_with(
+	s: State,
+	a: Either![IStr, ArrValue],
+	b: Either![IStr, ArrValue],
+) -> Result<bool> {
+	Ok(match (a, b) {
+		(Either2::A(a), Either2::A(b)) => a.starts_with(b.as_str()),
+		(Either2::B(a), Either2::B(b)) => {
+			if b.len() > a.len() {
+				return Ok(false);
+			} else if b.len() == a.len() {
+				return equals(s, &Val::Arr(a), &Val::Arr(b));
+			} else {
+				for (a, b) in a
+					.slice(None, Some(b.len()), None)
+					.iter(s.clone())
+					.zip(b.iter(s.clone()))
+				{
+					let a = a?;
+					let b = b?;
+					if !equals(s.clone(), &a, &b)? {
+						return Ok(false);
+					}
+				}
+				true
+			}
+		}
+		_ => throw_runtime!("both arguments should be of the same type"),
+	})
+}
+
+#[allow(clippy::comparison_chain)]
+#[builtin]
+fn builtin_ends_with(
+	s: State,
+	a: Either![IStr, ArrValue],
+	b: Either![IStr, ArrValue],
+) -> Result<bool> {
+	Ok(match (a, b) {
+		(Either2::A(a), Either2::A(b)) => a.ends_with(b.as_str()),
+		(Either2::B(a), Either2::B(b)) => {
+			if b.len() > a.len() {
+				return Ok(false);
+			} else if b.len() == a.len() {
+				return equals(s, &Val::Arr(a), &Val::Arr(b));
+			} else {
+				let a_len = a.len();
+				for (a, b) in a
+					.slice(Some(a_len - b.len()), None, None)
+					.iter(s.clone())
+					.zip(b.iter(s.clone()))
+				{
+					let a = a?;
+					let b = b?;
+					if !equals(s.clone(), &a, &b)? {
+						return Ok(false);
+					}
+				}
+				true
+			}
+		}
+		_ => throw_runtime!("both arguments should be of the same type"),
+	})
+}
+
+pub trait StateExt {
+	/// This method was previously implemented in jrsonnet-evaluator itself
+	fn with_stdlib(&self);
+	fn add_global(&self, name: IStr, value: Thunk<Val>);
+}
+
+impl StateExt for State {
+	fn with_stdlib(&self) {
+		let initializer = ContextInitializer::new(self.clone(), PathResolver::new_cwd_fallback());
+		self.settings_mut().context_initializer = Box::new(initializer)
+	}
+	fn add_global(&self, name: IStr, value: Thunk<Val>) {
+		self.settings()
+			.context_initializer
+			.as_any()
+			.downcast_ref::<ContextInitializer>()
+			.expect("not standard context initializer")
+			.settings_mut()
+			.globals
+			.insert(name, value);
+	}
+}
addedcrates/jrsonnet-stdlib/src/manifest.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/manifest.rs
@@ -0,0 +1,65 @@
+use jrsonnet_evaluator::{
+	error::Result,
+	function::builtin,
+	stdlib::manifest::{
+		escape_string_json, manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType,
+		ManifestYamlOptions,
+	},
+	typed::Any,
+	IStr, State,
+};
+
+#[builtin]
+pub fn builtin_escape_string_json(str_: IStr) -> Result<String> {
+	Ok(escape_string_json(&str_))
+}
+
+#[builtin]
+pub fn builtin_manifest_json_ex(
+	s: State,
+	value: Any,
+	indent: IStr,
+	newline: Option<IStr>,
+	key_val_sep: Option<IStr>,
+	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> Result<String> {
+	let newline = newline.as_deref().unwrap_or("\n");
+	let key_val_sep = key_val_sep.as_deref().unwrap_or(": ");
+	manifest_json_ex(
+		s,
+		&value.0,
+		&ManifestJsonOptions {
+			padding: &indent,
+			mtype: ManifestType::Std,
+			newline,
+			key_val_sep,
+			#[cfg(feature = "exp-preserve-order")]
+			preserve_order: preserve_order.unwrap_or(false),
+		},
+	)
+}
+
+#[builtin]
+pub fn builtin_manifest_yaml_doc(
+	s: State,
+	value: Any,
+	indent_array_in_object: Option<bool>,
+	quote_keys: Option<bool>,
+	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> Result<String> {
+	manifest_yaml_ex(
+		s,
+		&value.0,
+		&ManifestYamlOptions {
+			padding: "  ",
+			arr_element_padding: if indent_array_in_object.unwrap_or(false) {
+				"  "
+			} else {
+				""
+			},
+			quote_keys: quote_keys.unwrap_or(true),
+			#[cfg(feature = "exp-preserve-order")]
+			preserve_order: preserve_order.unwrap_or(false),
+		},
+	)
+}
addedcrates/jrsonnet-stdlib/src/math.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/math.rs
@@ -0,0 +1,87 @@
+use jrsonnet_evaluator::{error::Result, function::builtin, typed::PositiveF64};
+
+#[builtin]
+pub fn builtin_modulo(a: f64, b: f64) -> Result<f64> {
+	Ok(a % b)
+}
+
+#[builtin]
+pub fn builtin_floor(x: f64) -> Result<f64> {
+	Ok(x.floor())
+}
+
+#[builtin]
+pub fn builtin_ceil(x: f64) -> Result<f64> {
+	Ok(x.ceil())
+}
+
+#[builtin]
+pub fn builtin_log(n: f64) -> Result<f64> {
+	Ok(n.ln())
+}
+
+#[builtin]
+pub fn builtin_pow(x: f64, n: f64) -> Result<f64> {
+	Ok(x.powf(n))
+}
+
+#[builtin]
+pub fn builtin_sqrt(x: PositiveF64) -> Result<f64> {
+	Ok(x.0.sqrt())
+}
+
+#[builtin]
+pub fn builtin_sin(x: f64) -> Result<f64> {
+	Ok(x.sin())
+}
+
+#[builtin]
+pub fn builtin_cos(x: f64) -> Result<f64> {
+	Ok(x.cos())
+}
+
+#[builtin]
+pub fn builtin_tan(x: f64) -> Result<f64> {
+	Ok(x.tan())
+}
+
+#[builtin]
+pub fn builtin_asin(x: f64) -> Result<f64> {
+	Ok(x.asin())
+}
+
+#[builtin]
+pub fn builtin_acos(x: f64) -> Result<f64> {
+	Ok(x.acos())
+}
+
+#[builtin]
+pub fn builtin_atan(x: f64) -> Result<f64> {
+	Ok(x.atan())
+}
+
+#[builtin]
+pub fn builtin_exp(x: f64) -> Result<f64> {
+	Ok(x.exp())
+}
+
+fn frexp(s: f64) -> (f64, i16) {
+	if s == 0.0 {
+		(s, 0)
+	} else {
+		let lg = s.abs().log2();
+		let x = (lg - lg.floor() - 1.0).exp2();
+		let exp = lg.floor() + 1.0;
+		(s.signum() * x, exp as i16)
+	}
+}
+
+#[builtin]
+pub fn builtin_mantissa(x: f64) -> Result<f64> {
+	Ok(frexp(x).0)
+}
+
+#[builtin]
+pub fn builtin_exponent(x: f64) -> Result<i16> {
+	Ok(frexp(x).1)
+}
addedcrates/jrsonnet-stdlib/src/objects.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/objects.rs
@@ -0,0 +1,27 @@
+use jrsonnet_evaluator::{
+	error::Result, function::builtin, typed::VecVal, val::Val, IStr, ObjValue,
+};
+use jrsonnet_gcmodule::Cc;
+
+#[builtin]
+pub fn builtin_object_fields_ex(
+	obj: ObjValue,
+	inc_hidden: bool,
+	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> Result<VecVal> {
+	#[cfg(feature = "exp-preserve-order")]
+	let preserve_order = preserve_order.unwrap_or(false);
+	let out = obj.fields_ex(
+		inc_hidden,
+		#[cfg(feature = "exp-preserve-order")]
+		preserve_order,
+	);
+	Ok(VecVal(Cc::new(
+		out.into_iter().map(Val::Str).collect::<Vec<_>>(),
+	)))
+}
+
+#[builtin]
+pub fn builtin_object_has_ex(obj: ObjValue, f: IStr, inc_hidden: bool) -> Result<bool> {
+	Ok(obj.has_field_ex(f, inc_hidden))
+}
addedcrates/jrsonnet-stdlib/src/operator.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/operator.rs
@@ -0,0 +1,40 @@
+//! Some jsonnet operations are desugared to stdlib functions...
+//! However, in our case we instead implement them in native, and implement native functions on top of core for backwards compatibility
+
+use jrsonnet_evaluator::{
+	error::Result,
+	function::builtin,
+	operator::evaluate_mod_op,
+	stdlib::std_format,
+	typed::{Any, Either, Either2},
+	val::{equals, primitive_equals},
+	IStr, State, Val,
+};
+
+#[builtin]
+pub fn builtin_mod(s: State, a: Either![f64, IStr], b: Any) -> Result<Any> {
+	use Either2::*;
+	Ok(Any(evaluate_mod_op(
+		s,
+		&match a {
+			A(v) => Val::Num(v),
+			B(s) => Val::Str(s),
+		},
+		&b.0,
+	)?))
+}
+
+#[builtin]
+pub fn builtin_primitive_equals(a: Any, b: Any) -> Result<bool> {
+	primitive_equals(&a.0, &b.0)
+}
+
+#[builtin]
+pub fn builtin_equals(s: State, a: Any, b: Any) -> Result<bool> {
+	equals(s, &a.0, &b.0)
+}
+
+#[builtin]
+pub fn builtin_format(s: State, str: IStr, vals: Any) -> Result<String> {
+	std_format(s, str, vals.0)
+}
addedcrates/jrsonnet-stdlib/src/parse.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/parse.rs
@@ -0,0 +1,39 @@
+use jrsonnet_evaluator::{
+	error::{Error::RuntimeError, Result},
+	function::builtin,
+	typed::{Any, Typed},
+	IStr, State, Val,
+};
+use serde::Deserialize;
+
+#[builtin]
+pub fn builtin_parse_json(st: State, s: IStr) -> Result<Any> {
+	use serde_json::Value;
+	let value: Value = serde_json::from_str(&s)
+		.map_err(|e| RuntimeError(format!("failed to parse json: {}", e).into()))?;
+	Ok(Any(Value::into_untyped(value, st)?))
+}
+
+#[builtin]
+pub fn builtin_parse_yaml(st: State, s: IStr) -> Result<Any> {
+	use serde_json::Value;
+	use serde_yaml_with_quirks::DeserializingQuirks;
+	let value = serde_yaml_with_quirks::Deserializer::from_str_with_quirks(
+		&s,
+		DeserializingQuirks { old_octals: true },
+	);
+	let mut out = vec![];
+	for item in value {
+		let value = Value::deserialize(item)
+			.map_err(|e| RuntimeError(format!("failed to parse yaml: {}", e).into()))?;
+		let val = Value::into_untyped(value, st.clone())?;
+		out.push(val);
+	}
+	Ok(Any(if out.is_empty() {
+		Val::Null
+	} else if out.len() == 1 {
+		out.into_iter().next().unwrap()
+	} else {
+		Val::Arr(out.into())
+	}))
+}
addedcrates/jrsonnet-stdlib/src/sort.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/sort.rs
@@ -0,0 +1,109 @@
+use jrsonnet_evaluator::{
+	error::Result,
+	function::{builtin, FuncVal},
+	throw_runtime,
+	typed::Any,
+	val::ArrValue,
+	State, Val,
+};
+use jrsonnet_gcmodule::Cc;
+
+#[derive(Copy, Clone)]
+enum SortKeyType {
+	Number,
+	String,
+	Unknown,
+}
+
+#[derive(PartialEq)]
+struct NonNaNf64(f64);
+impl PartialOrd for NonNaNf64 {
+	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+		self.0.partial_cmp(&other.0)
+	}
+}
+impl Eq for NonNaNf64 {}
+impl Ord for NonNaNf64 {
+	fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+		self.partial_cmp(other).expect("non nan")
+	}
+}
+
+fn get_sort_type<T>(
+	values: &mut [T],
+	key_getter: impl Fn(&mut T) -> &mut Val,
+) -> Result<SortKeyType> {
+	let mut sort_type = SortKeyType::Unknown;
+	for i in values.iter_mut() {
+		let i = key_getter(i);
+		match (i, sort_type) {
+			(Val::Str(_), SortKeyType::Unknown) => sort_type = SortKeyType::String,
+			(Val::Num(_), SortKeyType::Unknown) => sort_type = SortKeyType::Number,
+			(Val::Str(_), SortKeyType::String) | (Val::Num(_), SortKeyType::Number) => {}
+			(Val::Str(_) | Val::Num(_), _) => {
+				throw_runtime!("sort elements should have the same types")
+			}
+			_ => throw_runtime!("sort key should either be a string or a number"),
+		}
+	}
+	Ok(sort_type)
+}
+
+/// * `key_getter` - None, if identity sort required
+pub fn sort(s: State, values: Cc<Vec<Val>>, key_getter: FuncVal) -> Result<Cc<Vec<Val>>> {
+	if values.len() <= 1 {
+		return Ok(values);
+	}
+	if key_getter.is_identity() {
+		// Fast path, identity key getter
+		let mut values = (*values).clone();
+		let sort_type = get_sort_type(&mut values, |k| k)?;
+		match sort_type {
+			SortKeyType::Number => values.sort_unstable_by_key(|v| match v {
+				Val::Num(n) => NonNaNf64(*n),
+				_ => unreachable!(),
+			}),
+			SortKeyType::String => values.sort_unstable_by_key(|v| match v {
+				Val::Str(s) => s.clone(),
+				_ => unreachable!(),
+			}),
+			SortKeyType::Unknown => unreachable!(),
+		};
+		Ok(Cc::new(values))
+	} else {
+		// Slow path, user provided key getter
+		let mut vk = Vec::with_capacity(values.len());
+		for value in values.iter() {
+			vk.push((
+				value.clone(),
+				key_getter.evaluate_simple(s.clone(), &(Any(value.clone()),))?,
+			));
+		}
+		let sort_type = get_sort_type(&mut vk, |v| &mut v.1)?;
+		match sort_type {
+			SortKeyType::Number => vk.sort_by_key(|v| match v.1 {
+				Val::Num(n) => NonNaNf64(n),
+				_ => unreachable!(),
+			}),
+			SortKeyType::String => vk.sort_by_key(|v| match &v.1 {
+				Val::Str(s) => s.clone(),
+				_ => unreachable!(),
+			}),
+			SortKeyType::Unknown => unreachable!(),
+		};
+		Ok(Cc::new(vk.into_iter().map(|v| v.0).collect()))
+	}
+}
+
+#[builtin]
+#[allow(non_snake_case)]
+pub fn builtin_sort(s: State, arr: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
+	if arr.len() <= 1 {
+		return Ok(arr);
+	}
+	Ok(ArrValue::Eager(super::sort::sort(
+		s.clone(),
+		arr.evaluated(s)?,
+		keyF.unwrap_or_else(FuncVal::identity),
+	)?))
+}
modifiedcrates/jrsonnet-stdlib/src/std.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/std.jsonnet
+++ b/crates/jrsonnet-stdlib/src/std.jsonnet
@@ -2,73 +2,9 @@
   local std = self,
   local id = std.id,
 
-  # Magic legacy field
-  thisFile:: $intrinsicThisFile,
-  id:: $intrinsicId,
+  thisFile:: error 'std.thisFile is deprecated, to enable its support in jrsonnet - recompile it with "legacy-this-file" support.\nThis will slow down stdlib caching a bit, though',
 
-  # Those functions aren't normally located in stdlib
-  length:: $intrinsic(length),
-  type:: $intrinsic(type),
-  makeArray:: $intrinsic(makeArray),
-  codepoint:: $intrinsic(codepoint),
-  objectFieldsEx:: $intrinsic(objectFieldsEx),
-  objectHasEx:: $intrinsic(objectHasEx),
-  primitiveEquals:: $intrinsic(primitiveEquals),
-  modulo:: $intrinsic(modulo),
-  floor:: $intrinsic(floor),
-  ceil:: $intrinsic(ceil),
-  extVar:: $intrinsic(extVar),
-  native:: $intrinsic(native),
-  filter:: $intrinsic(filter),
-  char:: $intrinsic(char),
-  encodeUTF8:: $intrinsic(encodeUTF8),
-  decodeUTF8:: $intrinsic(decodeUTF8),
-  md5:: $intrinsic(md5),
-  trace:: $intrinsic(trace),
-  parseJson:: $intrinsic(parseJson),
-  parseYaml:: $intrinsic(parseYaml),
-
-  log:: $intrinsic(log),
-  pow:: $intrinsic(pow),
-  sqrt:: $intrinsic(sqrt),
-
-  sin:: $intrinsic(sin),
-  cos:: $intrinsic(cos),
-  tan:: $intrinsic(tan),
-  asin:: $intrinsic(asin),
-  acos:: $intrinsic(acos),
-  atan:: $intrinsic(atan),
-
-  exp:: $intrinsic(exp),
-  mantissa:: $intrinsic(mantissa),
-  exponent:: $intrinsic(exponent),
-
-  any:: $intrinsic(any),
-  all:: $intrinsic(all),
-
-  isString(v):: std.type(v) == 'string',
-  isNumber(v):: std.type(v) == 'number',
-  isBoolean(v):: std.type(v) == 'boolean',
-  isObject(v):: std.type(v) == 'object',
-  isArray(v):: std.type(v) == 'array',
-  isFunction(v):: std.type(v) == 'function',
-
-  toString(a)::
-    if std.type(a) == 'string' then a else '' + a,
-
-  substr:: $intrinsic(substr),
-
-  startsWith(a, b)::
-    if std.length(a) < std.length(b) then
-      false
-    else
-      std.substr(a, 0, std.length(b)) == b,
-
-  endsWith(a, b)::
-    if std.length(a) < std.length(b) then
-      false
-    else
-      std.substr(a, std.length(a) - std.length(b), std.length(b)) == b,
+  toString(a):: '' + a,
 
   lstripChars(str, chars)::
     if std.length(str) > 0 && std.member(chars, str[0]) then
@@ -127,32 +63,12 @@
 
   split(str, c):: std.splitLimit(str, c, -1),
 
-  splitLimit:: $intrinsic(splitLimit),
-
-  strReplace:: $intrinsic(strReplace),
-
-  asciiUpper:: $intrinsic(asciiUpper),
-
-  asciiLower:: $intrinsic(asciiLower),
-
-  range:: $intrinsic(range),
-
   repeat(what, count)::
     local joiner =
       if std.isString(what) then ''
       else if std.isArray(what) then []
       else error 'std.repeat first argument must be an array or a string';
     std.join(joiner, std.makeArray(count, function(i) what)),
-
-  slice:: $intrinsic(slice),
-
-  member:: $intrinsic(member),
-
-  count:: $intrinsic(count),
-
-  mod:: $intrinsic(mod),
-
-  map:: $intrinsic(map),
 
   mapWithIndex(func, arr)::
     if !std.isFunction(func) then
@@ -169,11 +85,7 @@
       error ('std.mapWithKey second param must be object, got ' + std.type(obj))
     else
       { [k]: func(k, obj[k]) for k in std.objectFields(obj) },
-
-  flatMap:: $intrinsic(flatMap),
 
-  join:: $intrinsic(join),
-
   lines(arr)::
     std.join('\n', arr + ['']),
 
@@ -185,13 +97,6 @@
     else
       error 'Expected string or array, got %s' % std.type(arr),
 
-
-  format:: $intrinsic(format),
-
-  foldr:: $intrinsic(foldr),
-
-  foldl:: $intrinsic(foldl),
-
   filterMap(filter_func, map_func, arr)::
     if !std.isFunction(filter_func) then
       error ('std.filterMap first param must be function, got ' + std.type(filter_func))
@@ -349,8 +254,6 @@
       renderTableInternal(value, [], [], '')
     else
       error 'TOML body must be an object. Got ' + std.type(value),
-
-  escapeStringJson:: $intrinsic(escapeStringJson),
 
   escapeStringPython(str)::
     std.escapeStringJson(str),
@@ -377,10 +280,6 @@
 
   manifestJsonMinified(value):: std.manifestJsonEx(value, '', '', ':'),
 
-  manifestJsonEx:: $intrinsic(manifestJsonEx),
-
-  manifestYamlDoc:: $intrinsic(manifestYamlDoc),
-
   manifestYamlStream(value, indent_array_in_object=false, c_document_end=true)::
     if !std.isArray(value) then
       error 'manifestYamlStream only takes arrays, got ' + std.type(value)
@@ -434,19 +333,6 @@
 
       aux(value),
 
-  local base64_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
-  local base64_inv = { [base64_table[i]]: i for i in std.range(0, 63) },
-
-  base64:: $intrinsic(base64),
-
-  base64DecodeBytes:: $intrinsic(base64DecodeBytes),
-
-  base64Decode:: $intrinsic(base64Decode),
-
-  reverse:: $intrinsic(reverse),
-
-  sort:: $intrinsic(sort),
-
   uniq(arr, keyF=id)::
     local f(a, b) =
       if std.length(a) == 0 then
@@ -534,7 +420,7 @@
     else
       patch,
 
-  get(o, f, default = null, inc_hidden = true)::
+  get(o, f, default=null, inc_hidden=true)::
     if std.objectHasEx(o, f, inc_hidden) then o[f] else default,
 
   objectFields(o)::
@@ -554,8 +440,6 @@
 
   objectValuesAll(o)::
     [o[k] for k in std.objectFieldsAll(o)],
-
-  equals:: $intrinsic(equals),
 
   resolvePath(f, r)::
     local arr = std.split(f, '/');
@@ -579,19 +463,6 @@
       if isContent(std.prune(a[x]))
     } else
       a,
-
-  findSubstr(pat, str)::
-    if !std.isString(pat) then
-      error 'findSubstr first parameter should be a string, got ' + std.type(pat)
-    else if !std.isString(str) then
-      error 'findSubstr second parameter should be a string, got ' + std.type(str)
-    else
-      local pat_len = std.length(pat);
-      local str_len = std.length(str);
-      if pat_len == 0 || str_len == 0 || pat_len > str_len then
-        []
-      else
-        std.filter(function(i) str[i:i + pat_len] == pat, std.range(0, str_len - pat_len)),
 
   find(value, arr)::
     if !std.isArray(arr) then
addedcrates/jrsonnet-stdlib/src/types.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/types.rs
@@ -0,0 +1,31 @@
+use jrsonnet_evaluator::{error::Result, function::builtin, typed::Any, IStr, Val};
+
+#[builtin]
+pub fn builtin_type(x: Any) -> Result<IStr> {
+	Ok(x.0.value_type().name().into())
+}
+
+#[builtin]
+pub fn builtin_is_string(x: Any) -> Result<bool> {
+	Ok(matches!(x.0, Val::Str(_)))
+}
+#[builtin]
+pub fn builtin_is_number(x: Any) -> Result<bool> {
+	Ok(matches!(x.0, Val::Num(_)))
+}
+#[builtin]
+pub fn builtin_is_boolean(x: Any) -> Result<bool> {
+	Ok(matches!(x.0, Val::Bool(_)))
+}
+#[builtin]
+pub fn builtin_is_object(x: Any) -> Result<bool> {
+	Ok(matches!(x.0, Val::Obj(_)))
+}
+#[builtin]
+pub fn builtin_is_array(x: Any) -> Result<bool> {
+	Ok(matches!(x.0, Val::Arr(_)))
+}
+#[builtin]
+pub fn builtin_is_function(x: Any) -> Result<bool> {
+	Ok(matches!(x.0, Val::Func(_)))
+}
deletedflake.lockdiffbeforeafterboth
--- a/flake.lock
+++ /dev/null
@@ -1,43 +0,0 @@
-{
-  "nodes": {
-    "flake-utils": {
-      "locked": {
-        "lastModified": 1623875721,
-        "narHash": "sha256-A8BU7bjS5GirpAUv4QA+QnJ4CceLHkcXdRp4xITDB0s=",
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "rev": "f7e004a55b120c02ecb6219596820fcd32ca8772",
-        "type": "github"
-      },
-      "original": {
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "type": "github"
-      }
-    },
-    "nixpkgs": {
-      "locked": {
-        "lastModified": 1625281901,
-        "narHash": "sha256-DkZDtTIPzhXATqIps2ifNFpnI+PTcfMYdcrx/oFm00Q=",
-        "owner": "NixOS",
-        "repo": "nixpkgs",
-        "rev": "09c38c29f2c719cd76ca17a596c2fdac9e186ceb",
-        "type": "github"
-      },
-      "original": {
-        "owner": "NixOS",
-        "ref": "nixos-unstable",
-        "repo": "nixpkgs",
-        "type": "github"
-      }
-    },
-    "root": {
-      "inputs": {
-        "flake-utils": "flake-utils",
-        "nixpkgs": "nixpkgs"
-      }
-    }
-  },
-  "root": "root",
-  "version": 7
-}
deletedflake.nixdiffbeforeafterboth
--- a/flake.nix
+++ /dev/null
@@ -1,59 +0,0 @@
-{
-  description = "Dotfiles manager";
-  inputs = {
-    nixpkgs.url = "github:nixos/nixpkgs";
-    flake-utils.url = "github:numtide/flake-utils";
-    naersk.url = "github:nix-community/naersk";
-    rust-overlay.url = "github:oxalica/rust-overlay";
-    pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix";
-  };
-  outputs = { self, nixpkgs, flake-utils, rust-overlay, pre-commit-hooks, naersk }:
-    flake-utils.lib.eachDefaultSystem (system:
-      let
-        pkgs = import nixpkgs
-          {
-            inherit system;
-            overlays = [ rust-overlay.overlay ];
-          };
-        rust = ((pkgs.rustChannelOf { date = "2021-11-11"; channel = "nightly"; }).default.override {
-          extensions = [ "rust-src" ];
-        });
-        naersk-lib = naersk.lib."${system}".override {
-          rustc = rust;
-          cargo = rust;
-        };
-      in
-      rec {
-        checks = {
-          pre-commit-check = pre-commit-hooks.lib.${system}.run {
-            src = ./.;
-            hooks = {
-              nixpkgs-fmt.enable = true;
-            };
-          };
-        };
-        defaultPackage = naersk-lib.buildPackage {
-          pname = "dotman";
-          root = ./.;
-          buildInputs = with pkgs; [
-            pkgs.sqlite
-          ];
-        };
-        devShell = pkgs.mkShell {
-          inherit (checks.pre-commit-check) shellHook;
-          nativeBuildInputs = with pkgs;[
-            pkgs.binutils
-            pkgs.pkgconfig
-            pkgs.clang
-            pkgs.x11
-            pkgs.alsaLib
-            pkgs.libudev
-            pkgs.sqlite
-            rust
-            cargo-edit
-            go-jsonnet
-          ];
-        };
-      }
-    );
-}
addedtests/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/tests/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "tests"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[dependencies]
+jrsonnet-evaluator = { path = "../crates/jrsonnet-evaluator" }
+jrsonnet-gcmodule = "0.3.4"
+jrsonnet-stdlib = { path = "../crates/jrsonnet-stdlib" }
+serde = "1.0.142"
addedtests/golden/array_comp.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/array_comp.jsonnet
@@ -0,0 +1 @@
+[[a, b] for a in [1, 2, 3] for b in [4, 5, 6]]
addedtests/golden/array_comp.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/array_comp.jsonnet.golden
@@ -0,0 +1,38 @@
+[
+   [
+      1,
+      4
+   ],
+   [
+      1,
+      5
+   ],
+   [
+      1,
+      6
+   ],
+   [
+      2,
+      4
+   ],
+   [
+      2,
+      5
+   ],
+   [
+      2,
+      6
+   ],
+   [
+      3,
+      4
+   ],
+   [
+      3,
+      5
+   ],
+   [
+      3,
+      6
+   ]
+]
\ No newline at end of file
addedtests/golden/builtin_json.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/builtin_json.jsonnet
@@ -0,0 +1 @@
+std.manifestJsonEx({ a: 3, b: 4, c: 6 }, '')
addedtests/golden/builtin_json.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/builtin_json.jsonnet.golden
@@ -0,0 +1 @@
+"{\n\"a\": 3,\n\"b\": 4,\n\"c\": 6\n}"
\ No newline at end of file
addedtests/golden/builtin_json_minified.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/builtin_json_minified.jsonnet
@@ -0,0 +1 @@
+std.manifestJsonMinified({ a: 3, b: 4, c: 6 })
addedtests/golden/builtin_json_minified.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/builtin_json_minified.jsonnet.golden
@@ -0,0 +1 @@
+"{\"a\":3,\"b\":4,\"c\":6}"
\ No newline at end of file
addedtests/golden/builtin_parseJson.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/builtin_parseJson.jsonnet
@@ -0,0 +1 @@
+std.parseJson('{"a": -1,"b": 1,"c": 3.141,"d": []}')
addedtests/golden/builtin_parseJson.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/builtin_parseJson.jsonnet.golden
@@ -0,0 +1,6 @@
+{
+   "a": -1,
+   "b": 1,
+   "c": 3.141,
+   "d": [ ]
+}
\ No newline at end of file
addedtests/golden/issue23.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/issue23.jsonnet
@@ -0,0 +1 @@
+import 'issue23.jsonnet'
addedtests/golden/issue23.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/issue23.jsonnet.golden
@@ -0,0 +1,2 @@
+infinite recursion detected
+   issue23.jsonnet:1:1-26: import "issue23.jsonnet"
\ No newline at end of file
addedtests/golden/issue40.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/issue40.jsonnet
@@ -0,0 +1,9 @@
+local conf = {
+  n: '',
+};
+
+local result = conf {
+  assert std.isNumber(self.n) : 'is number',
+};
+
+std.manifestJsonEx(result, '')
addedtests/golden/issue40.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/issue40.jsonnet.golden
@@ -0,0 +1,3 @@
+assert failed: is number
+   issue40.jsonnet:6:10-31: assertion failure
+   issue40.jsonnet:9:1-32:  function <builtin_manifest_json_ex> call
\ No newline at end of file
addedtests/golden/missing_binding.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/missing_binding.jsonnet
@@ -0,0 +1 @@
+sta
addedtests/golden/missing_binding.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/missing_binding.jsonnet.golden
@@ -0,0 +1,3 @@
+variable is not defined: sta
+There is variable with similar name present: std
+   missing_binding.jsonnet:1:1-5: variable <sta> access
\ No newline at end of file
addedtests/golden/object_comp.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/object_comp.jsonnet
@@ -0,0 +1 @@
+{ local t = 'a', ['h' + i + '_' + z]: if 'h' + (i - 1) + '_' + z in self then t + 1 else 0 + t for i in [1, 2, 3] for z in [2, 3, 4] if z != i }
addedtests/golden/object_comp.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/object_comp.jsonnet.golden
@@ -0,0 +1,9 @@
+{
+   "h1_2": "0a",
+   "h1_3": "0a",
+   "h1_4": "0a",
+   "h2_3": "a1",
+   "h2_4": "a1",
+   "h3_2": "0a",
+   "h3_4": "a1"
+}
\ No newline at end of file
addedtests/golden/test_assertThrow.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/test_assertThrow.jsonnet
@@ -0,0 +1,2 @@
+// Test that test.assertThrow will return error, if body is not errored
+test.assertThrow(1, '1')
addedtests/golden/test_assertThrow.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/test_assertThrow.jsonnet.golden
@@ -0,0 +1,2 @@
+runtime error: expected argument to throw on evaluation, but it returned instead
+   test_assertThrow.jsonnet:2:1-26: function <assert_throw> call
\ No newline at end of file
addedtests/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/lib.rs
@@ -0,0 +1 @@
+//! See tests/, suite/ and golden/ directories for tests
addedtests/suite/builtin_ascii.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/builtin_ascii.jsonnet
@@ -0,0 +1,3 @@
+std.assertEqual(std.asciiUpper('aBc😀'), 'ABC😀') &&
+std.assertEqual(std.asciiLower('aBc😀'), 'abc😀') &&
+true
addedtests/suite/builtin_base64.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/builtin_base64.jsonnet
@@ -0,0 +1,2 @@
+std.assertEqual(std.base64('test'), 'dGVzdA==') &&
+true
addedtests/suite/builtin_chars.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/builtin_chars.jsonnet
@@ -0,0 +1,3 @@
+local c = '😎';
+std.assertEqual({ c: std.codepoint(c), l: std.length(c) }, { c: 128526, l: 1 }) &&
+true
addedtests/suite/builtin_constant.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/builtin_constant.jsonnet
@@ -0,0 +1,3 @@
+local std2 = std; local std = std2 { primitiveEquals(a, b):: false };
+// In jsonnet, this expression was failing because of being desugared to std.primitiveEquals(1, 1)
+std.assertEqual(1 == 1, true)
addedtests/suite/builtin_count.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/builtin_count.jsonnet
@@ -0,0 +1,4 @@
+std.assertEqual(std.count([], ''), 0) &&
+std.assertEqual(std.count(['a', 'b', 'a'], 'd'), 0) &&
+std.assertEqual(std.count(['a', 'b', 'a'], 'a'), 2) &&
+true
addedtests/suite/builtin_join.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/builtin_join.jsonnet
@@ -0,0 +1,4 @@
+std.assertEqual(std.join([0, 0], [[1, 2], [3, 4], [5, 6]]), [1, 2, 0, 0, 3, 4, 0, 0, 5, 6]) &&
+std.assertEqual(std.join(',', ['1', '2', '3', '4']), '1,2,3,4') &&
+std.assertEqual(std.join(',', ['1', null, '2', null, '3']), '1,2,3') &&
+true
addedtests/suite/builtin_member.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/builtin_member.jsonnet
@@ -0,0 +1,7 @@
+!std.member('', '') &&
+std.member('abc', 'a') &&
+!std.member('abc', 'd') &&
+!std.member([], '') &&
+std.member(['a', 'b', 'c'], 'a') &&
+!std.member(['a', 'b', 'c'], 'd') &&
+true
addedtests/suite/function_args.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/function_args.jsonnet
@@ -0,0 +1,3 @@
+std.assertEqual(local a = function(b, c=2) b + c; a(2), 4) &&
+std.assertEqual(local a = function(b, c='Dear') b + c + d, d = 'World'; a('Hello'), 'HelloDearWorld') &&
+true
addedtests/suite/function_context.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/function_context.jsonnet
@@ -0,0 +1,10 @@
+local k = {
+  t(name=self.h): [self.h, name],
+  h: 3,
+};
+local f = {
+  t: k.t(),
+  h: 4,
+};
+std.assertEqual(f.t[0], f.t[1]) &&
+true
addedtests/suite/function_lazy_args.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/function_lazy_args.jsonnet
@@ -0,0 +1,5 @@
+local fun(a) = 2;
+std.assertEqual(fun(error '3'), 2) &&
+// But in tailstrict mode arguments are evaluated eagerly
+test.assertThrow(fun(error '3') tailstrict, 'runtime error: 3') &&
+true
addedtests/suite/local.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/local.jsonnet
@@ -0,0 +1,4 @@
+std.assertEqual(local a = 2; local b = 3; a + b, 5) &&
+std.assertEqual(local a = 1, b = a + 1; a + b, 3) &&
+std.assertEqual(local a = 1; local a = 2; a, 2) &&
+true
addedtests/suite/math.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/math.jsonnet
@@ -0,0 +1,3 @@
+std.assertEqual(2 + 2 * 2, 6) &&
+std.assertEqual(3 + (2 + 2 * 2), 9) &&
+true
addedtests/suite/object_assertion.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/object_assertion.jsonnet
@@ -0,0 +1,3 @@
+std.assertEqual({ assert 'a' in self : 'missing a' } + { a: 2 }, { a: 2 }) &&
+test.assertThrow({ assert 'a' in self : 'missing a', b: 1 }.b, 'assert failed: missing a') &&
+true
addedtests/suite/object_comp_self.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/object_comp_self.jsonnet
@@ -0,0 +1,8 @@
+std.assertEqual(std.objectFields({
+  a: {
+    [name]: name
+    for name in std.objectFields(self)
+  },
+  b: 2,
+  c: 3,
+}.a), ['a', 'b', 'c'])
addedtests/suite/object_context.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/object_context.jsonnet
@@ -0,0 +1,13 @@
+// `self` assigned to `me` was lost when being
+// referenced from field
+std.assertEqual({
+  local me = self,
+  a: 3,
+  b: me.a,
+}.b, 3) &&
+std.assertEqual({
+  local me = self,
+  a: 3,
+  b(): me.a,
+}.b(), 3) &&
+true
addedtests/suite/object_fields.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/object_fields.jsonnet
@@ -0,0 +1,4 @@
+local a = 'a', b = null;
+std.assertEqual({ [a]: 2 }, { a: 2 }) &&
+std.assertEqual({ [b]: 2 }, {}) &&
+true
addedtests/suite/object_inheritance.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/object_inheritance.jsonnet
@@ -0,0 +1,17 @@
+std.assertEqual({ a: self.b } + { b: 3 }, { a: 3, b: 3 }) &&
+std.assertEqual(
+  {
+    name: 'Alice',
+    welcome: 'Hello ' + self.name + '!',
+  },
+  { name: 'Alice', welcome: 'Hello Alice!' },
+) &&
+std.assertEqual(
+  {
+    name: 'Alice',
+    welcome: 'Hello ' + self.name + '!',
+  } + {
+    name: 'Bob',
+  }, { name: 'Bob', welcome: 'Hello Bob!' }
+) &&
+true
addedtests/suite/object_locals.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/object_locals.jsonnet
@@ -0,0 +1,4 @@
+std.assertEqual({ local a = 3, b: a }, { b: 3 }) &&
+std.assertEqual({ local a = 3, local c = a, b: c }, { b: 3 }) &&
+std.assertEqual({ local a = function(b) { [b]: 4 }, test: a('test') }, { test: { test: 4 } }) &&
+true
addedtests/suite/object_super_standalone.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/object_super_standalone.jsonnet
@@ -0,0 +1,11 @@
+local obj = {
+  a: 1,
+  b: 2,
+  c: 3,
+};
+local test = obj + {
+  fields: std.objectFields(super),
+  d: 5,
+};
+std.assertEqual(test.fields, ['a', 'b', 'c']) &&
+true
addedtests/suite/sjsonnet_issue_127.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/sjsonnet_issue_127.jsonnet
@@ -0,0 +1,6 @@
+local myFunc = function(a)
+  if (a) then "a" else "b";
+
+local b = "aaa";
+
+std.assertEqual(myFunc(b == [] || b == ['e']), "b")
addedtests/suite/string_concat.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/suite/string_concat.jsonnet
@@ -0,0 +1,4 @@
+std.assertEqual('Hello' + 'World', 'HelloWorld') &&
+std.assertEqual('Hello' * 3, 'HelloHelloHello') &&
+std.assertEqual('Hello' + 'World' * 3, 'HelloWorldWorldWorld') &&
+true
addedtests/tests/as_native.rsdiffbeforeafterboth
--- /dev/null
+++ b/tests/tests/as_native.rs
@@ -0,0 +1,20 @@
+use jrsonnet_evaluator::{error::Result, State};
+use jrsonnet_stdlib::StateExt;
+
+mod common;
+
+#[test]
+fn as_native() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+
+	let val = s.evaluate_snippet("snip".to_owned(), r#"function(a, b) a + b"#)?;
+	let func = val.as_func().expect("this is function");
+
+	let native = func.into_native::<((u32, u32), u32)>();
+
+	ensure_eq!(native(s.clone(), 1, 2)?, 3);
+	ensure_eq!(native(s, 3, 4)?, 7);
+
+	Ok(())
+}
addedtests/tests/builtin.rsdiffbeforeafterboth
--- /dev/null
+++ b/tests/tests/builtin.rs
@@ -0,0 +1,94 @@
+mod common;
+
+use jrsonnet_evaluator::{
+	error::Result,
+	function::{builtin, builtin::Builtin, CallLocation, FuncVal},
+	tb,
+	typed::Typed,
+	Context, State, Thunk, Val,
+};
+use jrsonnet_gcmodule::Cc;
+use jrsonnet_stdlib::StateExt;
+
+#[builtin]
+fn a() -> Result<u32> {
+	Ok(1)
+}
+
+#[test]
+fn basic_function() -> Result<()> {
+	let s = State::default();
+	let a: a = a {};
+	let v = u32::from_untyped(
+		a.call(s.clone(), Context::new(), CallLocation::native(), &())?,
+		s,
+	)?;
+
+	ensure_eq!(v, 1);
+	Ok(())
+}
+
+#[builtin]
+fn native_add(a: u32, b: u32) -> Result<u32> {
+	Ok(a + b)
+}
+
+#[test]
+fn call_from_code() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	s.add_global(
+		"nativeAdd".into(),
+		Thunk::evaluated(Val::Func(FuncVal::StaticBuiltin(native_add::INST))),
+	);
+
+	let v = s.evaluate_snippet(
+		"snip".to_owned(),
+		"
+            assert nativeAdd(1, 2) == 3;
+            assert nativeAdd(100, 200) == 300;
+            null
+        ",
+	)?;
+	ensure_val_eq!(s, v, Val::Null);
+	Ok(())
+}
+
+#[builtin(fields(
+    a: u32
+))]
+fn curried_add(this: &curried_add, b: u32) -> Result<u32> {
+	Ok(this.a + b)
+}
+
+#[builtin]
+fn curry_add(a: u32) -> Result<FuncVal> {
+	Ok(FuncVal::Builtin(Cc::new(tb!(curried_add { a }))))
+}
+
+#[test]
+fn nonstatic_builtin() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	s.add_global(
+		"curryAdd".into(),
+		Thunk::evaluated(Val::Func(FuncVal::StaticBuiltin(curry_add::INST))),
+	);
+
+	let v = s.evaluate_snippet(
+		"snip".to_owned(),
+		"
+            local a = curryAdd(1);
+            local b = curryAdd(4);
+
+            assert a(2) == 3;
+            assert a(200) == 201;
+
+            assert b(2) == 6;
+            assert b(200) == 204;
+            null
+        ",
+	)?;
+	ensure_val_eq!(s, v, Val::Null);
+	Ok(())
+}
addedtests/tests/common.rsdiffbeforeafterboth
--- /dev/null
+++ b/tests/tests/common.rs
@@ -0,0 +1,77 @@
+use jrsonnet_evaluator::{
+	error::Result,
+	function::{builtin, FuncVal},
+	throw_runtime, ObjValueBuilder, State, Thunk, Val,
+};
+use jrsonnet_stdlib::StateExt;
+
+#[macro_export]
+macro_rules! ensure_eq {
+	($a:expr, $b:expr $(,)?) => {{
+		let a = &$a;
+		let b = &$b;
+		if a != b {
+			::jrsonnet_evaluator::throw_runtime!("assertion failed: a != b\na={:#?}\nb={:#?}", a, b)
+		}
+	}};
+}
+
+#[macro_export]
+macro_rules! ensure {
+	($v:expr $(,)?) => {
+		if !$v {
+			::jrsonnet_evaluator::throw_runtime!("assertion failed: {}", stringify!($v))
+		}
+	};
+}
+
+#[macro_export]
+macro_rules! ensure_val_eq {
+	($s:expr, $a:expr, $b:expr) => {{
+		if !::jrsonnet_evaluator::val::equals($s.clone(), &$a.clone(), &$b.clone())? {
+			::jrsonnet_evaluator::throw_runtime!(
+				"assertion failed: a != b\na={:#?}\nb={:#?}",
+				$a.to_json(
+					$s.clone(),
+					2,
+					#[cfg(feature = "exp-preserve-order")]
+					false
+				)?,
+				$b.to_json(
+					$s.clone(),
+					2,
+					#[cfg(feature = "exp-preserve-order")]
+					false
+				)?,
+			)
+		}
+	}};
+}
+
+#[builtin]
+fn assert_throw(s: State, lazy: Thunk<Val>, message: String) -> Result<bool> {
+	match lazy.evaluate(s) {
+		Ok(_) => {
+			throw_runtime!("expected argument to throw on evaluation, but it returned instead")
+		}
+		Err(e) => {
+			let error = format!("{}", e.error());
+			ensure_eq!(message, error);
+		}
+	}
+	Ok(true)
+}
+
+#[allow(dead_code)]
+pub fn with_test(s: &State) {
+	let mut bobj = ObjValueBuilder::new();
+	bobj.member("assertThrow".into())
+		.hide()
+		.value(
+			s.clone(),
+			Val::Func(FuncVal::StaticBuiltin(assert_throw::INST)),
+		)
+		.expect("no error");
+
+	s.add_global("test".into(), Thunk::evaluated(Val::Obj(bobj.build())))
+}
addedtests/tests/golden.rsdiffbeforeafterboth
--- /dev/null
+++ b/tests/tests/golden.rs
@@ -0,0 +1,70 @@
+use std::{
+	fs, io,
+	path::{Path, PathBuf},
+};
+
+use jrsonnet_evaluator::{
+	trace::{CompactFormat, PathResolver},
+	FileImportResolver, State,
+};
+use jrsonnet_stdlib::StateExt;
+
+mod common;
+
+fn run(root: &Path, file: &Path) -> String {
+	let s = State::default();
+	s.set_trace_format(Box::new(CompactFormat {
+		resolver: PathResolver::Relative(root.to_owned()),
+		padding: 3,
+	}));
+	s.with_stdlib();
+	common::with_test(&s);
+	s.set_import_resolver(Box::new(FileImportResolver::default()));
+
+	let v = match s.import(file) {
+		Ok(v) => v,
+		Err(e) => return s.stringify_err(&e),
+	};
+	match v.to_json(
+		s.clone(),
+		3,
+		#[cfg(feature = "exp-preserve-order")]
+		false,
+	) {
+		Ok(v) => v.to_string(),
+		Err(e) => s.stringify_err(&e),
+	}
+}
+
+#[test]
+fn test() -> io::Result<()> {
+	let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+	root.push("golden");
+
+	for entry in fs::read_dir(&root)? {
+		let entry = entry?;
+		if !entry.path().extension().map_or(false, |e| e == "jsonnet") {
+			continue;
+		}
+
+		let result = run(&root, &entry.path());
+
+		let mut golden_path = entry.path();
+		golden_path.set_extension("jsonnet.golden");
+
+		if !golden_path.exists() {
+			fs::write(golden_path, &result)?;
+		} else {
+			let golden = fs::read_to_string(golden_path)?;
+
+			assert_eq!(
+				result,
+				golden,
+				"golden didn't match for {}",
+				entry.path().display()
+			)
+		}
+	}
+
+	Ok(())
+}
addedtests/tests/sanity.rsdiffbeforeafterboth
--- /dev/null
+++ b/tests/tests/sanity.rs
@@ -0,0 +1,42 @@
+use jrsonnet_evaluator::{error::Result, throw_runtime, State, Val};
+use jrsonnet_stdlib::StateExt;
+
+mod common;
+
+#[test]
+fn assert_positive() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+
+	let v = s.evaluate_snippet("snip".to_owned(), "assert 1 == 1: 'fail'; null")?;
+	ensure_val_eq!(s, v, Val::Null);
+	let v = s.evaluate_snippet("snip".to_owned(), "std.assertEqual(1, 1)")?;
+	ensure_val_eq!(s, v, Val::Bool(true));
+
+	Ok(())
+}
+
+#[test]
+fn assert_negative() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+
+	{
+		let e = match s.evaluate_snippet("snip".to_owned(), "assert 1 == 2: 'fail'; null") {
+			Ok(_) => throw_runtime!("assertion should fail"),
+			Err(e) => e,
+		};
+		let e = s.stringify_err(&e);
+		ensure!(e.starts_with("assert failed: fail\n"));
+	}
+	{
+		let e = match s.evaluate_snippet("snip".to_owned(), "std.assertEqual(1, 2)") {
+			Ok(_) => throw_runtime!("assertion should fail"),
+			Err(e) => e,
+		};
+		let e = s.stringify_err(&e);
+		ensure!(e.starts_with("runtime error: Assertion failed. 1 != 2"))
+	}
+
+	Ok(())
+}
addedtests/tests/suite.rsdiffbeforeafterboth
--- /dev/null
+++ b/tests/tests/suite.rs
@@ -0,0 +1,47 @@
+use std::{
+	fs, io,
+	path::{Path, PathBuf},
+};
+
+use jrsonnet_evaluator::{
+	trace::{CompactFormat, PathResolver},
+	FileImportResolver, State, Val,
+};
+use jrsonnet_stdlib::StateExt;
+
+mod common;
+
+fn run(root: &Path, file: &Path) {
+	let s = State::default();
+	s.set_trace_format(Box::new(CompactFormat {
+		resolver: PathResolver::Relative(root.to_owned()),
+		padding: 3,
+	}));
+	s.with_stdlib();
+	common::with_test(&s);
+	s.set_import_resolver(Box::new(FileImportResolver::default()));
+
+	match s.import(file) {
+		Ok(Val::Bool(true)) => {}
+		Ok(Val::Bool(false)) => panic!("test {} returned false", file.display()),
+		Ok(_) => panic!("test {} returned wrong type as result", file.display()),
+		Err(e) => panic!("test {} failed:\n{}", file.display(), s.stringify_err(&e)),
+	};
+}
+
+#[test]
+fn test() -> io::Result<()> {
+	let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+	root.push("suite");
+
+	for entry in fs::read_dir(&root)? {
+		let entry = entry?;
+		if !entry.path().extension().map_or(false, |e| e == "jsonnet") {
+			continue;
+		}
+
+		run(&root, &entry.path());
+	}
+
+	Ok(())
+}
addedtests/tests/typed_obj.rsdiffbeforeafterboth
--- /dev/null
+++ b/tests/tests/typed_obj.rs
@@ -0,0 +1,189 @@
+mod common;
+
+use std::fmt::Debug;
+
+use jrsonnet_evaluator::{error::Result, typed::Typed, State};
+use jrsonnet_stdlib::StateExt;
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct A {
+	a: u32,
+	b: u16,
+}
+
+fn test_roundtrip<T: Typed + PartialEq + Debug + Clone>(value: T, s: State) -> Result<()> {
+	let untyped = T::into_untyped(value.clone(), s.clone())?;
+	let value2 = T::from_untyped(untyped.clone(), s.clone())?;
+	ensure_eq!(value, value2);
+	let untyped2 = T::into_untyped(value2, s.clone())?;
+	ensure_val_eq!(s, untyped, untyped2);
+
+	Ok(())
+}
+
+#[test]
+fn simple_object() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let a = A::from_untyped(
+		s.evaluate_snippet("snip".to_owned(), "{a: 1, b: 2}")?,
+		s.clone(),
+	)?;
+	ensure_eq!(a, A { a: 1, b: 2 });
+	test_roundtrip(a, s)?;
+	Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct B {
+	a: u32,
+	#[typed(rename = "c")]
+	b: u16,
+}
+
+#[test]
+fn renamed_field() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let b = B::from_untyped(
+		s.evaluate_snippet("snip".to_owned(), "{a: 1, c: 2}")?,
+		s.clone(),
+	)?;
+	ensure_eq!(b, B { a: 1, b: 2 });
+	ensure_eq!(
+		&B::into_untyped(b.clone(), s.clone())?.to_string(s.clone())? as &str,
+		r#"{"a": 1, "c": 2}"#,
+	);
+	test_roundtrip(b, s)?;
+	Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct ObjectKind {
+	#[typed(rename = "apiVersion")]
+	api_version: String,
+	#[typed(rename = "kind")]
+	kind: String,
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct Object {
+	#[typed(flatten)]
+	kind: ObjectKind,
+	b: u16,
+}
+
+#[test]
+fn flattened_object() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let obj = Object::from_untyped(
+		s.evaluate_snippet("snip".to_owned(), "{apiVersion: 'ver', kind: 'kind', b: 2}")?,
+		s.clone(),
+	)?;
+	ensure_eq!(
+		obj,
+		Object {
+			kind: ObjectKind {
+				api_version: "ver".into(),
+				kind: "kind".into(),
+			},
+			b: 2
+		}
+	);
+	ensure_eq!(
+		&Object::into_untyped(obj.clone(), s.clone())?.to_string(s.clone())? as &str,
+		r#"{"apiVersion": "ver", "b": 2, "kind": "kind"}"#,
+	);
+	test_roundtrip(obj, s)?;
+	Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct C {
+	a: Option<u32>,
+	b: u16,
+}
+
+#[test]
+fn optional_field_some() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let c = C::from_untyped(
+		s.evaluate_snippet("snip".to_owned(), "{a: 1, b: 2}")?,
+		s.clone(),
+	)?;
+	ensure_eq!(c, C { a: Some(1), b: 2 });
+	ensure_eq!(
+		&C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
+		r#"{"a": 1, "b": 2}"#,
+	);
+	test_roundtrip(c, s)?;
+	Ok(())
+}
+
+#[test]
+fn optional_field_none() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let c = C::from_untyped(s.evaluate_snippet("snip".to_owned(), "{b: 2}")?, s.clone())?;
+	ensure_eq!(c, C { a: None, b: 2 });
+	ensure_eq!(
+		&C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
+		r#"{"b": 2}"#,
+	);
+	test_roundtrip(c, s)?;
+	Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct D {
+	#[typed(flatten(ok))]
+	e: Option<E>,
+	b: u16,
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct E {
+	v: u32,
+}
+
+#[test]
+fn flatten_optional_some() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let d = D::from_untyped(
+		s.evaluate_snippet("snip".to_owned(), "{b: 2, v:1}")?,
+		s.clone(),
+	)?;
+	ensure_eq!(
+		d,
+		D {
+			e: Some(E { v: 1 }),
+			b: 2
+		}
+	);
+	ensure_eq!(
+		&D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
+		r#"{"b": 2, "v": 1}"#,
+	);
+	test_roundtrip(d, s)?;
+	Ok(())
+}
+
+#[test]
+fn flatten_optional_none() -> Result<()> {
+	let s = State::default();
+	s.with_stdlib();
+	let d = D::from_untyped(
+		s.evaluate_snippet("snip".to_owned(), "{b: 2, v: '1'}")?,
+		s.clone(),
+	)?;
+	ensure_eq!(d, D { e: None, b: 2 });
+	ensure_eq!(
+		&D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
+		r#"{"b": 2}"#,
+	);
+	test_roundtrip(d, s)?;
+	Ok(())
+}