git.delta.rocks / jrsonnet / refs/commits / 68bea05caa11

difftreelog

source

crates/jrsonnet-stdlib/src/lib.rs10.2 KiBsourcehistory
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}