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

difftreelog

perf std.manifestTomlEx builtin

Yaroslav Bolyukin2022-12-03parent: #68bea05.patch.diff
in: master

5 files changed

modifiedcrates/jrsonnet-cli/src/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-cli/src/manifest.rs
+++ b/crates/jrsonnet-cli/src/manifest.rs
@@ -1,4 +1,4 @@
-use std::{path::PathBuf, str::FromStr};
+use std::path::PathBuf;
 
 use clap::{Parser, ValueEnum};
 use jrsonnet_evaluator::{
@@ -6,7 +6,7 @@
 	manifest::{JsonFormat, ManifestFormat, StringFormat, ToStringFormat, YamlStreamFormat},
 	State,
 };
-use jrsonnet_stdlib::YamlFormat;
+use jrsonnet_stdlib::{TomlFormat, YamlFormat};
 
 use crate::ConfigureState;
 
@@ -16,18 +16,7 @@
 	String,
 	Json,
 	Yaml,
-}
-
-impl FromStr for ManifestFormatName {
-	type Err = &'static str;
-	fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
-		Ok(match s {
-			"string" => ManifestFormatName::String,
-			"json" => ManifestFormatName::Json,
-			"yaml" => ManifestFormatName::Yaml,
-			_ => return Err("no such format"),
-		})
-	}
+	Toml,
 }
 
 #[derive(Parser)]
@@ -44,7 +33,7 @@
 	#[clap(long, short = 'y', conflicts_with = "string")]
 	yaml_stream: bool,
 	/// Number of spaces to pad output manifest with.
-	/// `0` for hard tabs, `-1` for single line output [default: 3 for json, 2 for yaml]
+	/// `0` for hard tabs, `-1` for single line output [default: 3 for json, 2 for yaml/toml]
 	#[clap(long)]
 	line_padding: Option<usize>,
 	/// Preserve order in object manifestification
@@ -72,6 +61,11 @@
 					#[cfg(feature = "exp-preserve-order")]
 					preserve_order,
 				)),
+				ManifestFormatName::Toml => Box::new(TomlFormat::cli(
+					self.line_padding.unwrap_or(2),
+					#[cfg(feature = "exp-preserve-order")]
+					preserve_order,
+				)),
 			}
 		};
 		Ok(if self.yaml_stream {
modifiedcrates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth
before · crates/jrsonnet-stdlib/src/lib.rs
1use std::{2	cell::{Ref, RefCell, RefMut},3	collections::HashMap,4	rc::Rc,5};67use jrsonnet_evaluator::{8	error::{ErrorKind::*, Result},9	function::{builtin::Builtin, CallLocation, FuncVal, TlaArg},10	gc::{GcHashMap, TraceBox},11	tb,12	trace::PathResolver,13	Context, ContextBuilder, IStr, ObjValue, ObjValueBuilder, State, Thunk, Val,14};15use jrsonnet_gcmodule::{Cc, Trace};16use jrsonnet_parser::Source;1718mod expr;19mod types;20pub use types::*;21mod arrays;22pub use arrays::*;23mod math;24pub use math::*;25mod operator;26pub use operator::*;27mod sort;28pub use sort::*;29mod hash;30pub use hash::*;31mod encoding;32pub use encoding::*;33mod objects;34pub use objects::*;35mod manifest;36pub use manifest::*;37mod parse;38pub use parse::*;39mod strings;40pub use strings::*;41mod misc;42pub use misc::*;4344pub fn stdlib_uncached(settings: Rc<RefCell<Settings>>) -> ObjValue {45	let mut builder = ObjValueBuilder::new();4647	let expr = expr::stdlib_expr();48	let eval = jrsonnet_evaluator::evaluate(ContextBuilder::dangerous_empty_state().build(), &expr)49		.expect("stdlib.jsonnet should have no errors")50		.as_obj()51		.expect("stdlib.jsonnet should evaluate to object");5253	builder.with_super(eval);5455	for (name, builtin) in [56		// Types57		("type", builtin_type::INST),58		("isString", builtin_is_string::INST),59		("isNumber", builtin_is_number::INST),60		("isBoolean", builtin_is_boolean::INST),61		("isObject", builtin_is_object::INST),62		("isArray", builtin_is_array::INST),63		("isFunction", builtin_is_function::INST),64		// Arrays65		("makeArray", builtin_make_array::INST),66		("repeat", builtin_repeat::INST),67		("slice", builtin_slice::INST),68		("map", builtin_map::INST),69		("flatMap", builtin_flatmap::INST),70		("filter", builtin_filter::INST),71		("foldl", builtin_foldl::INST),72		("foldr", builtin_foldr::INST),73		("range", builtin_range::INST),74		("join", builtin_join::INST),75		("reverse", builtin_reverse::INST),76		("any", builtin_any::INST),77		("all", builtin_all::INST),78		("member", builtin_member::INST),79		("count", builtin_count::INST),80		// Math81		("abs", builtin_abs::INST),82		("sign", builtin_sign::INST),83		("max", builtin_max::INST),84		("min", builtin_min::INST),85		("modulo", builtin_modulo::INST),86		("floor", builtin_floor::INST),87		("ceil", builtin_ceil::INST),88		("log", builtin_log::INST),89		("pow", builtin_pow::INST),90		("sqrt", builtin_sqrt::INST),91		("sin", builtin_sin::INST),92		("cos", builtin_cos::INST),93		("tan", builtin_tan::INST),94		("asin", builtin_asin::INST),95		("acos", builtin_acos::INST),96		("atan", builtin_atan::INST),97		("exp", builtin_exp::INST),98		("mantissa", builtin_mantissa::INST),99		("exponent", builtin_exponent::INST),100		// Operator101		("mod", builtin_mod::INST),102		("primitiveEquals", builtin_primitive_equals::INST),103		("equals", builtin_equals::INST),104		("format", builtin_format::INST),105		// Sort106		("sort", builtin_sort::INST),107		// Hash108		("md5", builtin_md5::INST),109		#[cfg(feature = "exp-more-hashes")]110		("sha256", builtin_sha256::INST),111		// Encoding112		("encodeUTF8", builtin_encode_utf8::INST),113		("decodeUTF8", builtin_decode_utf8::INST),114		("base64", builtin_base64::INST),115		("base64Decode", builtin_base64_decode::INST),116		("base64DecodeBytes", builtin_base64_decode_bytes::INST),117		// Objects118		("objectFieldsEx", builtin_object_fields_ex::INST),119		("objectHasEx", builtin_object_has_ex::INST),120		// Manifest121		("escapeStringJson", builtin_escape_string_json::INST),122		("manifestJsonEx", builtin_manifest_json_ex::INST),123		("manifestYamlDoc", builtin_manifest_yaml_doc::INST),124		// Parsing125		("parseJson", builtin_parse_json::INST),126		("parseYaml", builtin_parse_yaml::INST),127		// Strings128		("codepoint", builtin_codepoint::INST),129		("substr", builtin_substr::INST),130		("char", builtin_char::INST),131		("strReplace", builtin_str_replace::INST),132		("splitLimit", builtin_splitlimit::INST),133		("asciiUpper", builtin_ascii_upper::INST),134		("asciiLower", builtin_ascii_lower::INST),135		("findSubstr", builtin_find_substr::INST),136		("parseInt", builtin_parse_int::INST),137		("parseOctal", builtin_parse_octal::INST),138		("parseHex", builtin_parse_hex::INST),139		// Misc140		("length", builtin_length::INST),141		("startsWith", builtin_starts_with::INST),142		("endsWith", builtin_ends_with::INST),143	]144	.iter()145	.cloned()146	{147		builder148			.member(name.into())149			.hide()150			.value(Val::Func(FuncVal::StaticBuiltin(builtin)))151			.expect("no conflict");152	}153154	builder155		.member("extVar".into())156		.hide()157		.value(Val::Func(FuncVal::Builtin(Cc::new(tb!(builtin_ext_var {158			settings: settings.clone()159		})))))160		.expect("no conflict");161	builder162		.member("native".into())163		.hide()164		.value(Val::Func(FuncVal::Builtin(Cc::new(tb!(builtin_native {165			settings: settings.clone()166		})))))167		.expect("no conflict");168	builder169		.member("trace".into())170		.hide()171		.value(Val::Func(FuncVal::Builtin(Cc::new(tb!(builtin_trace {172			settings173		})))))174		.expect("no conflict");175176	builder177		.member("id".into())178		.hide()179		.value(Val::Func(FuncVal::Id))180		.expect("no conflict");181182	builder.build()183}184185pub trait TracePrinter {186	fn print_trace(&self, loc: CallLocation, value: IStr);187}188189pub struct StdTracePrinter {190	resolver: PathResolver,191}192impl StdTracePrinter {193	pub fn new(resolver: PathResolver) -> Self {194		Self { resolver }195	}196}197impl TracePrinter for StdTracePrinter {198	fn print_trace(&self, loc: CallLocation, value: IStr) {199		eprint!("TRACE:");200		if let Some(loc) = loc.0 {201			let locs = loc.0.map_source_locations(&[loc.1]);202			eprint!(203				" {}:{}",204				match loc.0.source_path().path() {205					Some(p) => self.resolver.resolve(p),206					None => loc.0.source_path().to_string(),207				},208				locs[0].line209			);210		}211		eprintln!(" {}", value);212	}213}214215pub struct Settings {216	/// Used for `std.extVar`217	pub ext_vars: HashMap<IStr, TlaArg>,218	/// Used for `std.native`219	pub ext_natives: HashMap<IStr, Cc<TraceBox<dyn Builtin>>>,220	/// Helper to add globals without implementing custom ContextInitializer221	pub globals: GcHashMap<IStr, Thunk<Val>>,222	/// Used for `std.trace`223	pub trace_printer: Box<dyn TracePrinter>,224	/// Used for `std.thisFile`225	pub path_resolver: PathResolver,226}227228fn extvar_source(name: &str, code: impl Into<IStr>) -> Source {229	let source_name = format!("<extvar:{}>", name);230	Source::new_virtual(source_name.into(), code.into())231}232233#[derive(Trace)]234pub struct ContextInitializer {235	// When we don't need to support legacy-this-file, we can reuse same context for all files236	#[cfg(not(feature = "legacy-this-file"))]237	context: Context,238	// Otherwise, we can only keep first stdlib layer, and then stack thisFile on top of it239	#[cfg(feature = "legacy-this-file")]240	stdlib_obj: ObjValue,241	settings: Rc<RefCell<Settings>>,242}243impl ContextInitializer {244	pub fn new(s: State, resolver: PathResolver) -> Self {245		let settings = Settings {246			ext_vars: Default::default(),247			ext_natives: Default::default(),248			globals: Default::default(),249			trace_printer: Box::new(StdTracePrinter::new(resolver.clone())),250			path_resolver: resolver,251		};252		let settings = Rc::new(RefCell::new(settings));253		Self {254			#[cfg(not(feature = "legacy-this-file"))]255			context: {256				let mut context = ContextBuilder::with_capacity(s, 1);257				context.bind(258					"std".into(),259					Thunk::evaluated(Val::Obj(stdlib_uncached(settings.clone()))),260				);261				context.build()262			},263			#[cfg(feature = "legacy-this-file")]264			stdlib_obj: stdlib_uncached(settings.clone()),265			settings,266		}267	}268	pub fn settings(&self) -> Ref<Settings> {269		self.settings.borrow()270	}271	pub fn settings_mut(&self) -> RefMut<Settings> {272		self.settings.borrow_mut()273	}274	pub fn add_ext_var(&self, name: IStr, value: Val) {275		self.settings_mut()276			.ext_vars277			.insert(name, TlaArg::Val(value));278	}279	pub fn add_ext_str(&self, name: IStr, value: IStr) {280		self.settings_mut()281			.ext_vars282			.insert(name, TlaArg::String(value));283	}284	pub fn add_ext_code(&self, name: &str, code: impl Into<IStr>) -> Result<()> {285		let code = code.into();286		let source = extvar_source(name, code.clone());287		let parsed = jrsonnet_parser::parse(288			&code,289			&jrsonnet_parser::ParserSettings {290				source: source.clone(),291			},292		)293		.map_err(|e| ImportSyntaxError {294			path: source,295			error: Box::new(e),296		})?;297		// self.data_mut().volatile_files.insert(source_name, code);298		self.settings_mut()299			.ext_vars300			.insert(name.into(), TlaArg::Code(parsed));301		Ok(())302	}303	pub fn add_native(&self, name: IStr, cb: Cc<TraceBox<dyn Builtin>>) {304		self.settings_mut().ext_natives.insert(name, cb);305	}306}307impl jrsonnet_evaluator::ContextInitializer for ContextInitializer {308	#[cfg(not(feature = "legacy-this-file"))]309	fn initialize(&self, _s: State, _source: Source) -> jrsonnet_evaluator::Context {310		let out = self.context.clone();311		let globals = &self.settings().globals;312		if globals.is_empty() {313			return out;314		}315316		let mut out = ContextBuilder::extend(out);317		for (k, v) in globals.iter() {318			out.bind(k.clone(), v.clone());319		}320		out.build()321	}322	#[cfg(feature = "legacy-this-file")]323	fn initialize(&self, s: State, source: Source) -> Context {324		use jrsonnet_evaluator::val::StrValue;325326		let mut builder = ObjValueBuilder::new();327		builder.with_super(self.stdlib_obj.clone());328		builder329			.member("thisFile".into())330			.hide()331			.value(Val::Str(StrValue::Flat(332				match source.source_path().path() {333					Some(p) => self.settings().path_resolver.resolve(p).into(),334					None => source.source_path().to_string().into(),335				},336			)))337			.expect("this object builder is empty");338		let stdlib_with_this_file = builder.build();339340		let mut context = ContextBuilder::with_capacity(s, 1);341		context.bind(342			"std".into(),343			Thunk::evaluated(Val::Obj(stdlib_with_this_file)),344		);345		for (k, v) in self.settings().globals.iter() {346			context.bind(k.clone(), v.clone());347		}348		context.build()349	}350	fn as_any(&self) -> &dyn std::any::Any {351		self352	}353}354355pub trait StateExt {356	/// This method was previously implemented in jrsonnet-evaluator itself357	fn with_stdlib(&self);358	fn add_global(&self, name: IStr, value: Thunk<Val>);359}360361impl StateExt for State {362	fn with_stdlib(&self) {363		let initializer = ContextInitializer::new(self.clone(), PathResolver::new_cwd_fallback());364		self.settings_mut().context_initializer = tb!(initializer)365	}366	fn add_global(&self, name: IStr, value: Thunk<Val>) {367		self.settings()368			.context_initializer369			.as_any()370			.downcast_ref::<ContextInitializer>()371			.expect("not standard context initializer")372			.settings_mut()373			.globals374			.insert(name, value);375	}376}
after · crates/jrsonnet-stdlib/src/lib.rs
1use std::{2	cell::{Ref, RefCell, RefMut},3	collections::HashMap,4	rc::Rc,5};67use jrsonnet_evaluator::{8	error::{ErrorKind::*, Result},9	function::{builtin::Builtin, CallLocation, FuncVal, TlaArg},10	gc::{GcHashMap, TraceBox},11	tb,12	trace::PathResolver,13	Context, ContextBuilder, IStr, ObjValue, ObjValueBuilder, State, Thunk, Val,14};15use jrsonnet_gcmodule::{Cc, Trace};16use jrsonnet_parser::Source;1718mod expr;19mod types;20pub use types::*;21mod arrays;22pub use arrays::*;23mod math;24pub use math::*;25mod operator;26pub use operator::*;27mod sort;28pub use sort::*;29mod hash;30pub use hash::*;31mod encoding;32pub use encoding::*;33mod objects;34pub use objects::*;35mod manifest;36pub use manifest::*;37mod parse;38pub use parse::*;39mod strings;40pub use strings::*;41mod misc;42pub use misc::*;4344pub fn stdlib_uncached(settings: Rc<RefCell<Settings>>) -> ObjValue {45	let mut builder = ObjValueBuilder::new();4647	let expr = expr::stdlib_expr();48	let eval = jrsonnet_evaluator::evaluate(ContextBuilder::dangerous_empty_state().build(), &expr)49		.expect("stdlib.jsonnet should have no errors")50		.as_obj()51		.expect("stdlib.jsonnet should evaluate to object");5253	builder.with_super(eval);5455	for (name, builtin) in [56		// Types57		("type", builtin_type::INST),58		("isString", builtin_is_string::INST),59		("isNumber", builtin_is_number::INST),60		("isBoolean", builtin_is_boolean::INST),61		("isObject", builtin_is_object::INST),62		("isArray", builtin_is_array::INST),63		("isFunction", builtin_is_function::INST),64		// Arrays65		("makeArray", builtin_make_array::INST),66		("repeat", builtin_repeat::INST),67		("slice", builtin_slice::INST),68		("map", builtin_map::INST),69		("flatMap", builtin_flatmap::INST),70		("filter", builtin_filter::INST),71		("foldl", builtin_foldl::INST),72		("foldr", builtin_foldr::INST),73		("range", builtin_range::INST),74		("join", builtin_join::INST),75		("reverse", builtin_reverse::INST),76		("any", builtin_any::INST),77		("all", builtin_all::INST),78		("member", builtin_member::INST),79		("count", builtin_count::INST),80		// Math81		("abs", builtin_abs::INST),82		("sign", builtin_sign::INST),83		("max", builtin_max::INST),84		("min", builtin_min::INST),85		("modulo", builtin_modulo::INST),86		("floor", builtin_floor::INST),87		("ceil", builtin_ceil::INST),88		("log", builtin_log::INST),89		("pow", builtin_pow::INST),90		("sqrt", builtin_sqrt::INST),91		("sin", builtin_sin::INST),92		("cos", builtin_cos::INST),93		("tan", builtin_tan::INST),94		("asin", builtin_asin::INST),95		("acos", builtin_acos::INST),96		("atan", builtin_atan::INST),97		("exp", builtin_exp::INST),98		("mantissa", builtin_mantissa::INST),99		("exponent", builtin_exponent::INST),100		// Operator101		("mod", builtin_mod::INST),102		("primitiveEquals", builtin_primitive_equals::INST),103		("equals", builtin_equals::INST),104		("format", builtin_format::INST),105		// Sort106		("sort", builtin_sort::INST),107		// Hash108		("md5", builtin_md5::INST),109		#[cfg(feature = "exp-more-hashes")]110		("sha256", builtin_sha256::INST),111		// Encoding112		("encodeUTF8", builtin_encode_utf8::INST),113		("decodeUTF8", builtin_decode_utf8::INST),114		("base64", builtin_base64::INST),115		("base64Decode", builtin_base64_decode::INST),116		("base64DecodeBytes", builtin_base64_decode_bytes::INST),117		// Objects118		("objectFieldsEx", builtin_object_fields_ex::INST),119		("objectHasEx", builtin_object_has_ex::INST),120		// Manifest121		("escapeStringJson", builtin_escape_string_json::INST),122		("manifestJsonEx", builtin_manifest_json_ex::INST),123		("manifestYamlDoc", builtin_manifest_yaml_doc::INST),124		("manifestTomlEx", builtin_manifest_toml_ex::INST),125		// Parsing126		("parseJson", builtin_parse_json::INST),127		("parseYaml", builtin_parse_yaml::INST),128		// Strings129		("codepoint", builtin_codepoint::INST),130		("substr", builtin_substr::INST),131		("char", builtin_char::INST),132		("strReplace", builtin_str_replace::INST),133		("splitLimit", builtin_splitlimit::INST),134		("asciiUpper", builtin_ascii_upper::INST),135		("asciiLower", builtin_ascii_lower::INST),136		("findSubstr", builtin_find_substr::INST),137		("parseInt", builtin_parse_int::INST),138		("parseOctal", builtin_parse_octal::INST),139		("parseHex", builtin_parse_hex::INST),140		// Misc141		("length", builtin_length::INST),142		("startsWith", builtin_starts_with::INST),143		("endsWith", builtin_ends_with::INST),144	]145	.iter()146	.cloned()147	{148		builder149			.member(name.into())150			.hide()151			.value(Val::Func(FuncVal::StaticBuiltin(builtin)))152			.expect("no conflict");153	}154155	builder156		.member("extVar".into())157		.hide()158		.value(Val::Func(FuncVal::Builtin(Cc::new(tb!(builtin_ext_var {159			settings: settings.clone()160		})))))161		.expect("no conflict");162	builder163		.member("native".into())164		.hide()165		.value(Val::Func(FuncVal::Builtin(Cc::new(tb!(builtin_native {166			settings: settings.clone()167		})))))168		.expect("no conflict");169	builder170		.member("trace".into())171		.hide()172		.value(Val::Func(FuncVal::Builtin(Cc::new(tb!(builtin_trace {173			settings174		})))))175		.expect("no conflict");176177	builder178		.member("id".into())179		.hide()180		.value(Val::Func(FuncVal::Id))181		.expect("no conflict");182183	builder.build()184}185186pub trait TracePrinter {187	fn print_trace(&self, loc: CallLocation, value: IStr);188}189190pub struct StdTracePrinter {191	resolver: PathResolver,192}193impl StdTracePrinter {194	pub fn new(resolver: PathResolver) -> Self {195		Self { resolver }196	}197}198impl TracePrinter for StdTracePrinter {199	fn print_trace(&self, loc: CallLocation, value: IStr) {200		eprint!("TRACE:");201		if let Some(loc) = loc.0 {202			let locs = loc.0.map_source_locations(&[loc.1]);203			eprint!(204				" {}:{}",205				match loc.0.source_path().path() {206					Some(p) => self.resolver.resolve(p),207					None => loc.0.source_path().to_string(),208				},209				locs[0].line210			);211		}212		eprintln!(" {}", value);213	}214}215216pub struct Settings {217	/// Used for `std.extVar`218	pub ext_vars: HashMap<IStr, TlaArg>,219	/// Used for `std.native`220	pub ext_natives: HashMap<IStr, Cc<TraceBox<dyn Builtin>>>,221	/// Helper to add globals without implementing custom ContextInitializer222	pub globals: GcHashMap<IStr, Thunk<Val>>,223	/// Used for `std.trace`224	pub trace_printer: Box<dyn TracePrinter>,225	/// Used for `std.thisFile`226	pub path_resolver: PathResolver,227}228229fn extvar_source(name: &str, code: impl Into<IStr>) -> Source {230	let source_name = format!("<extvar:{}>", name);231	Source::new_virtual(source_name.into(), code.into())232}233234#[derive(Trace)]235pub struct ContextInitializer {236	// When we don't need to support legacy-this-file, we can reuse same context for all files237	#[cfg(not(feature = "legacy-this-file"))]238	context: Context,239	// Otherwise, we can only keep first stdlib layer, and then stack thisFile on top of it240	#[cfg(feature = "legacy-this-file")]241	stdlib_obj: ObjValue,242	settings: Rc<RefCell<Settings>>,243}244impl ContextInitializer {245	pub fn new(s: State, resolver: PathResolver) -> Self {246		let settings = Settings {247			ext_vars: Default::default(),248			ext_natives: Default::default(),249			globals: Default::default(),250			trace_printer: Box::new(StdTracePrinter::new(resolver.clone())),251			path_resolver: resolver,252		};253		let settings = Rc::new(RefCell::new(settings));254		Self {255			#[cfg(not(feature = "legacy-this-file"))]256			context: {257				let mut context = ContextBuilder::with_capacity(s, 1);258				context.bind(259					"std".into(),260					Thunk::evaluated(Val::Obj(stdlib_uncached(settings.clone()))),261				);262				context.build()263			},264			#[cfg(feature = "legacy-this-file")]265			stdlib_obj: stdlib_uncached(settings.clone()),266			settings,267		}268	}269	pub fn settings(&self) -> Ref<Settings> {270		self.settings.borrow()271	}272	pub fn settings_mut(&self) -> RefMut<Settings> {273		self.settings.borrow_mut()274	}275	pub fn add_ext_var(&self, name: IStr, value: Val) {276		self.settings_mut()277			.ext_vars278			.insert(name, TlaArg::Val(value));279	}280	pub fn add_ext_str(&self, name: IStr, value: IStr) {281		self.settings_mut()282			.ext_vars283			.insert(name, TlaArg::String(value));284	}285	pub fn add_ext_code(&self, name: &str, code: impl Into<IStr>) -> Result<()> {286		let code = code.into();287		let source = extvar_source(name, code.clone());288		let parsed = jrsonnet_parser::parse(289			&code,290			&jrsonnet_parser::ParserSettings {291				source: source.clone(),292			},293		)294		.map_err(|e| ImportSyntaxError {295			path: source,296			error: Box::new(e),297		})?;298		// self.data_mut().volatile_files.insert(source_name, code);299		self.settings_mut()300			.ext_vars301			.insert(name.into(), TlaArg::Code(parsed));302		Ok(())303	}304	pub fn add_native(&self, name: IStr, cb: Cc<TraceBox<dyn Builtin>>) {305		self.settings_mut().ext_natives.insert(name, cb);306	}307}308impl jrsonnet_evaluator::ContextInitializer for ContextInitializer {309	#[cfg(not(feature = "legacy-this-file"))]310	fn initialize(&self, _s: State, _source: Source) -> jrsonnet_evaluator::Context {311		let out = self.context.clone();312		let globals = &self.settings().globals;313		if globals.is_empty() {314			return out;315		}316317		let mut out = ContextBuilder::extend(out);318		for (k, v) in globals.iter() {319			out.bind(k.clone(), v.clone());320		}321		out.build()322	}323	#[cfg(feature = "legacy-this-file")]324	fn initialize(&self, s: State, source: Source) -> Context {325		use jrsonnet_evaluator::val::StrValue;326327		let mut builder = ObjValueBuilder::new();328		builder.with_super(self.stdlib_obj.clone());329		builder330			.member("thisFile".into())331			.hide()332			.value(Val::Str(StrValue::Flat(333				match source.source_path().path() {334					Some(p) => self.settings().path_resolver.resolve(p).into(),335					None => source.source_path().to_string().into(),336				},337			)))338			.expect("this object builder is empty");339		let stdlib_with_this_file = builder.build();340341		let mut context = ContextBuilder::with_capacity(s, 1);342		context.bind(343			"std".into(),344			Thunk::evaluated(Val::Obj(stdlib_with_this_file)),345		);346		for (k, v) in self.settings().globals.iter() {347			context.bind(k.clone(), v.clone());348		}349		context.build()350	}351	fn as_any(&self) -> &dyn std::any::Any {352		self353	}354}355356pub trait StateExt {357	/// This method was previously implemented in jrsonnet-evaluator itself358	fn with_stdlib(&self);359	fn add_global(&self, name: IStr, value: Thunk<Val>);360}361362impl StateExt for State {363	fn with_stdlib(&self) {364		let initializer = ContextInitializer::new(self.clone(), PathResolver::new_cwd_fallback());365		self.settings_mut().context_initializer = tb!(initializer)366	}367	fn add_global(&self, name: IStr, value: Thunk<Val>) {368		self.settings()369			.context_initializer370			.as_any()371			.downcast_ref::<ContextInitializer>()372			.expect("not standard context initializer")373			.settings_mut()374			.globals375			.insert(name, value);376	}377}
modifiedcrates/jrsonnet-stdlib/src/manifest/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/manifest/mod.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/mod.rs
@@ -1,3 +1,4 @@
+mod toml;
 mod yaml;
 
 use jrsonnet_evaluator::{
@@ -5,8 +6,9 @@
 	function::builtin,
 	manifest::{escape_string_json, JsonFormat},
 	typed::Any,
-	IStr,
+	IStr, ObjValue, Val,
 };
+pub use toml::TomlFormat;
 pub use yaml::YamlFormat;
 
 #[builtin]
@@ -47,3 +49,16 @@
 		preserve_order.unwrap_or(false),
 	))
 }
+
+#[builtin]
+pub fn builtin_manifest_toml_ex(
+	value: ObjValue,
+	indent: IStr,
+	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> Result<String> {
+	Val::Obj(value).manifest(TomlFormat::std_to_toml(
+		indent.to_string(),
+		#[cfg(feature = "exp-preserve-order")]
+		preserve_order.unwrap_or(false),
+	))
+}
addedcrates/jrsonnet-stdlib/src/manifest/toml.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/manifest/toml.rs
@@ -0,0 +1,294 @@
+use std::borrow::Cow;
+
+use jrsonnet_evaluator::{
+	manifest::{escape_string_json_buf, ManifestFormat},
+	throw,
+	val::ArrValue,
+	IStr, ObjValue, Result, Val,
+};
+
+pub struct TomlFormat<'s> {
+	/// Padding before fields, i.e
+	/// ```toml
+	/// [a]
+	///   b = 1
+	/// ## <- this
+	/// ```
+	padding: Cow<'s, str>,
+	/// Do not emit sections for objects, consisting only from sections:
+	/// ```toml
+	/// # false
+	/// [a]
+	/// [a.b]
+	///
+	/// # true
+	/// [a.b]
+	/// ```
+	skip_empty_sections: bool,
+	/// If true - then order of fields is preserved as written,
+	/// instead of sorting alphabetically
+	#[cfg(feature = "exp-preserve-order")]
+	preserve_order: bool,
+}
+impl TomlFormat<'_> {
+	pub fn cli(
+		padding: usize,
+		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,
+	) -> Self {
+		let padding = " ".repeat(padding);
+		Self {
+			padding: Cow::Owned(padding),
+			skip_empty_sections: true,
+			#[cfg(feature = "exp-preserve-order")]
+			preserve_order,
+		}
+	}
+	pub fn std_to_toml(
+		padding: String,
+		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,
+	) -> Self {
+		Self {
+			padding: Cow::Owned(padding),
+			skip_empty_sections: false,
+			#[cfg(feature = "exp-preserve-order")]
+			preserve_order,
+		}
+	}
+}
+
+fn bare_allowed(s: &str) -> bool {
+	s.bytes()
+		.all(|c| matches!(c, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'_' | b'-'))
+}
+
+fn escape_key_toml_buf(key: &str, buf: &mut String) {
+	if bare_allowed(key) {
+		buf.push_str(key);
+	} else {
+		escape_string_json_buf(key, buf);
+	}
+}
+
+fn is_section(val: &Val) -> Result<bool> {
+	Ok(match val {
+		Val::Arr(a) => {
+			if a.is_empty() {
+				return Ok(false);
+			}
+			for e in a.iter() {
+				let e = e?;
+				if !matches!(e, Val::Obj(_)) {
+					return Ok(false);
+				}
+			}
+			true
+		}
+		Val::Obj(_) => true,
+		_ => false,
+	})
+}
+
+fn manifest_value(
+	val: &Val,
+	inline: bool,
+	buf: &mut String,
+	cur_padding: &str,
+	options: &TomlFormat<'_>,
+) -> Result<()> {
+	use std::fmt::Write;
+	match val {
+		Val::Bool(true) => buf.push_str("true"),
+		Val::Bool(false) => buf.push_str("false"),
+		Val::Str(s) => {
+			escape_string_json_buf(&s.clone().into_flat(), buf);
+		}
+		Val::Num(n) => write!(buf, "{n}").unwrap(),
+		Val::Arr(a) => {
+			if a.is_empty() {
+				buf.push_str("[]");
+				return Ok(());
+			}
+			for (i, e) in a.iter().enumerate() {
+				let e = e?;
+				if i != 0 {
+					buf.push(',');
+				} else {
+					buf.push('[');
+				}
+				if inline {
+					buf.push(' ');
+				} else {
+					buf.push('\n');
+					buf.push_str(cur_padding);
+					buf.push_str(&options.padding);
+				}
+				manifest_value(&e, true, buf, "", options)?;
+			}
+			if inline {
+				buf.push(' ');
+			} else {
+				buf.push('\n');
+				buf.push_str(cur_padding);
+			}
+			buf.push(']');
+		}
+		Val::Obj(o) => {
+			if o.is_empty() {
+				buf.push_str("{}");
+			}
+			buf.push_str("{ ");
+			for (i, (k, v)) in o
+				.iter(
+					#[cfg(feature = "exp-preserve-order")]
+					options.preserve_order,
+				)
+				.enumerate()
+			{
+				let v = v?;
+				if i != 0 {
+					buf.push_str(", ");
+				}
+				escape_key_toml_buf(&k, buf);
+				buf.push_str(" = ");
+				manifest_value(&v, true, buf, "", options)?;
+			}
+			buf.push_str(" }");
+		}
+		Val::Null => {
+			throw!("tried to manifest null")
+		}
+		Val::Func(_) => {
+			throw!("tried to manifest function")
+		}
+	}
+	Ok(())
+}
+
+fn manifest_table_internal(
+	obj: &ObjValue,
+	path: &mut Vec<IStr>,
+	buf: &mut String,
+	cur_padding: &mut String,
+	options: &TomlFormat<'_>,
+) -> Result<()> {
+	let mut sections = Vec::new();
+	let mut first = true;
+	for (key, value) in obj.iter(
+		#[cfg(feature = "exp-preserve-order")]
+		options.preserve_order,
+	) {
+		let value = value?;
+		if !is_section(&value)? {
+			if !first {
+				buf.push('\n');
+			}
+			first = false;
+			buf.push_str(cur_padding);
+			escape_key_toml_buf(&key, buf);
+			buf.push_str(" = ");
+			manifest_value(&value, false, buf, cur_padding, options)?;
+		} else {
+			sections.push((key, value));
+		}
+	}
+	for (k, v) in sections {
+		if !first {
+			buf.push_str("\n\n");
+		}
+		first = false;
+		path.push(k);
+		match v {
+			Val::Obj(obj) => manifest_table(&obj, path, buf, cur_padding, options)?,
+			Val::Arr(arr) => manifest_table_array(&arr, path, buf, cur_padding, options)?,
+			_ => unreachable!("iterating over sections"),
+		}
+		path.pop();
+	}
+	Ok(())
+}
+
+fn manifest_table(
+	obj: &ObjValue,
+	path: &mut Vec<IStr>,
+	buf: &mut String,
+	cur_padding: &mut String,
+	options: &TomlFormat<'_>,
+) -> Result<()> {
+	if options.skip_empty_sections
+		&& !obj.is_empty()
+		&& obj
+			.iter(
+				#[cfg(feature = "exp-preserve-order")]
+				false,
+			)
+			.try_fold(true, |c, (_, v)| Ok(c && is_section(&v?)?) as Result<bool>)?
+	{
+		manifest_table_internal(obj, path, buf, cur_padding, options)?;
+		return Ok(());
+	}
+	buf.push_str(cur_padding);
+	buf.push('[');
+	for (i, k) in path.iter().enumerate() {
+		if i != 0 {
+			buf.push('.');
+		}
+		escape_key_toml_buf(k, buf);
+	}
+	buf.push(']');
+	if obj.is_empty() {
+		return Ok(());
+	}
+	buf.push('\n');
+	let prev_len = cur_padding.len();
+	cur_padding.push_str(&options.padding);
+	manifest_table_internal(obj, path, buf, cur_padding, options)?;
+	cur_padding.truncate(prev_len);
+	Ok(())
+}
+fn manifest_table_array(
+	arr: &ArrValue,
+	path: &mut Vec<IStr>,
+	buf: &mut String,
+	cur_padding: &mut String,
+	options: &TomlFormat<'_>,
+) -> Result<()> {
+	let mut formatted_path = String::new();
+	{
+		formatted_path.push_str(cur_padding);
+		formatted_path.push_str("[[");
+		for (i, k) in path.iter().enumerate() {
+			if i != 0 {
+				formatted_path.push('.');
+			}
+			escape_key_toml_buf(k, &mut formatted_path);
+		}
+		formatted_path.push_str("]]");
+	}
+	let prev_len = cur_padding.len();
+	cur_padding.push_str(&options.padding);
+	for (i, e) in arr.iter().enumerate() {
+		let obj = e.expect("already tested").as_obj().expect("already tested");
+		if i != 0 {
+			buf.push_str("\n\n");
+		}
+		buf.push_str(&formatted_path);
+		if obj.is_empty() {
+			continue;
+		}
+		buf.push('\n');
+		manifest_table_internal(&obj, path, buf, cur_padding, options)?;
+	}
+	cur_padding.truncate(prev_len);
+	Ok(())
+}
+
+impl ManifestFormat for TomlFormat<'_> {
+	fn manifest_buf(&self, val: Val, buf: &mut String) -> jrsonnet_evaluator::Result<()> {
+		match val {
+			Val::Obj(obj) => {
+				manifest_table_internal(&obj, &mut Vec::new(), buf, &mut String::new(), self)
+			}
+			_ => throw!("toml body should be object"),
+		}
+	}
+}
modifiedcrates/jrsonnet-stdlib/src/std.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/std.jsonnet
+++ b/crates/jrsonnet-stdlib/src/std.jsonnet
@@ -100,86 +100,6 @@
 
   manifestToml(value):: std.manifestTomlEx(value, '  '),
 
-  manifestTomlEx(value, indent)::
-    local
-      escapeStringToml = std.escapeStringJson,
-      escapeKeyToml(key) =
-        local bare_allowed = std.set(std.stringChars('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'));
-        if std.setUnion(std.set(std.stringChars(key)), bare_allowed) == bare_allowed then key else escapeStringToml(key),
-      isTableArray(v) = std.isArray(v) && std.length(v) > 0 && std.foldl(function(a, b) a && std.isObject(b), v, true),
-      isSection(v) = std.isObject(v) || isTableArray(v),
-      renderValue(v, indexedPath, inline, cindent) =
-        if v == true then
-          'true'
-        else if v == false then
-          'false'
-        else if v == null then
-          error 'Tried to manifest "null" at ' + indexedPath
-        else if std.isNumber(v) then
-          '' + v
-        else if std.isString(v) then
-          escapeStringToml(v)
-        else if std.isFunction(v) then
-          error 'Tried to manifest function at ' + indexedPath
-        else if std.isArray(v) then
-          if std.length(v) == 0 then
-            '[]'
-          else
-            local range = std.range(0, std.length(v) - 1);
-            local new_indent = if inline then '' else cindent + indent;
-            local separator = if inline then ' ' else '\n';
-            local lines = ['[' + separator]
-                          + std.join([',' + separator],
-                                     [
-                                       [new_indent + renderValue(v[i], indexedPath + [i], true, '')]
-                                       for i in range
-                                     ])
-                          + [separator + (if inline then '' else cindent) + ']'];
-            std.join('', lines)
-        else if std.isObject(v) then
-          local lines = ['{ ']
-                        + std.join([', '],
-                                   [
-                                     [escapeKeyToml(k) + ' = ' + renderValue(v[k], indexedPath + [k], true, '')]
-                                     for k in std.objectFields(v)
-                                   ])
-                        + [' }'];
-          std.join('', lines),
-      renderTableInternal(v, path, indexedPath, cindent) =
-        local kvp = std.flattenArrays([
-          [cindent + escapeKeyToml(k) + ' = ' + renderValue(v[k], indexedPath + [k], false, cindent)]
-          for k in std.objectFields(v)
-          if !isSection(v[k])
-        ]);
-        local sections = [std.join('\n', kvp)] + [
-          (
-            if std.isObject(v[k]) then
-              renderTable(v[k], path + [k], indexedPath + [k], cindent)
-            else
-              renderTableArray(v[k], path + [k], indexedPath + [k], cindent)
-          )
-          for k in std.objectFields(v)
-          if isSection(v[k])
-        ];
-        std.join('\n\n', sections),
-      renderTable(v, path, indexedPath, cindent) =
-        cindent + '[' + std.join('.', std.map(escapeKeyToml, path)) + ']'
-        + (if v == {} then '' else '\n')
-        + renderTableInternal(v, path, indexedPath, cindent + indent),
-      renderTableArray(v, path, indexedPath, cindent) =
-        local range = std.range(0, std.length(v) - 1);
-        local sections = [
-          (cindent + '[[' + std.join('.', std.map(escapeKeyToml, path)) + ']]'
-           + (if v[i] == {} then '' else '\n')
-           + renderTableInternal(v[i], path, indexedPath + [i], cindent + indent))
-          for i in range
-        ];
-        std.join('\n\n', sections);
-    if std.isObject(value) then
-      renderTableInternal(value, [], [], '')
-    else
-      error 'TOML body must be an object. Got ' + std.type(value),
-
   escapeStringPython(str)::
     std.escapeStringJson(str),