git.delta.rocks / jrsonnet / refs/commits / 387e99d7443a

difftreelog

source

bindings/jsonnet/src/lib.rs12.6 KiBsourcehistory
1#![allow(clippy::box_default)]23pub mod interop;45pub mod import;6pub mod native;7pub mod val_extract;8pub mod val_make;9pub mod val_modify;10pub mod vars_tlas;1112use std::{13	alloc::Layout,14	any::Any,15	borrow::Cow,16	cell::RefCell,17	ffi::{CStr, CString, OsStr},18	os::raw::{c_char, c_double, c_int, c_uint},19	path::{Path, PathBuf},20	rc::Rc,21};2223use jrsonnet_evaluator::{24	AsPathLike, FileImportResolver, IStr, ImportResolver, Result, State, Val, apply_tla, bail,25	gc::WithCapacityExt as _,26	manifest::{JsonFormat, ManifestFormat, ToStringFormat},27	rustc_hash::FxHashMap,28	stack::set_stack_depth_limit,29	tla::TlaArg,30	trace::{CompactFormat, PathResolver, TraceFormat},31};32use jrsonnet_gcmodule::Acyclic;33use jrsonnet_ir::SourcePath;34use jrsonnet_stdlib::ContextInitializer;3536/// WASM stub37#[cfg(target_arch = "wasm32")]38#[no_mangle]39pub extern "C" fn _start() {}4041/// Return the version string of the Jsonnet interpreter.42///43/// Conforms to [semantic versioning](http://semver.org/).44/// If this does not match `LIB_JSONNET_VERSION`45/// then there is a mismatch between header and compiled library.46#[unsafe(no_mangle)]47pub extern "C" fn jsonnet_version() -> &'static [u8; 8] {48	b"v0.22.0\0"49}5051unsafe fn parse_path(input: &CStr) -> Cow<'_, Path> {52	#[cfg(target_family = "unix")]53	{54		use std::os::unix::ffi::OsStrExt;55		let str = OsStr::from_bytes(input.to_bytes());56		Cow::Borrowed(Path::new(str))57	}58	#[cfg(not(target_family = "unix"))]59	{60		let string = input.to_str().expect("bad utf-8");61		Cow::Borrowed(string.as_ref())62	}63}6465unsafe fn unparse_path(input: &Path) -> CString {66	#[cfg(target_family = "unix")]67	{68		use std::os::unix::ffi::OsStrExt;69		let str = CString::new(input.as_os_str().as_bytes()).expect("input has zero byte in it");70		str71	}72	#[cfg(not(target_family = "unix"))]73	{74		let str = input.as_os_str().to_str().expect("bad utf-8");75		let cstr = CString::new(str).expect("input has NUL inside");76		cstr77	}78}7980#[derive(Acyclic)]81struct VMImportResolver {82	inner: RefCell<Rc<dyn ImportResolver>>,83}84impl VMImportResolver {85	fn new(value: impl ImportResolver) -> Self {86		Self {87			inner: RefCell::new(Rc::new(value)),88		}89	}90}91impl ImportResolver for VMImportResolver {92	fn load_file_contents(&self, resolved: &SourcePath) -> Result<Vec<u8>> {93		self.inner.borrow().load_file_contents(resolved)94	}9596	fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> Result<SourcePath> {97		self.inner.borrow().resolve_from(from, path)98	}99100	fn resolve_from_default(&self, path: &dyn AsPathLike) -> Result<SourcePath> {101		self.inner.borrow().resolve_from_default(path)102	}103}104105pub struct VM {106	state: State,107	manifest_format: Box<dyn ManifestFormat>,108	trailing_newline: bool,109	trace_format: Box<dyn TraceFormat>,110	tla_args: FxHashMap<IStr, TlaArg>,111}112impl VM {113	fn replace_import_resolver(&self, resolver: impl ImportResolver) {114		*(self.state.import_resolver() as &dyn Any)115			.downcast_ref::<VMImportResolver>()116			.expect("valid resolver ty")117			.inner118			.borrow_mut() = Rc::new(resolver);119	}120	fn add_jpath(&self, path: PathBuf) {121		let ir = self.state.import_resolver();122		let vmi = (ir as &dyn Any)123			.downcast_ref::<VMImportResolver>()124			.expect("valid resolver ty");125		let vmi = &mut *vmi.inner.borrow_mut();126		(vmi as &mut dyn Any)127			.downcast_mut::<FileImportResolver>()128			.expect("jpaths are not compatible with callback imports!")129			.add_jpath(path);130	}131}132133/// Creates a new Jsonnet virtual machine.134#[unsafe(no_mangle)]135#[allow(clippy::box_default)]136pub extern "C" fn jsonnet_make() -> *mut VM {137	let mut state = State::builder();138	state139		.import_resolver(VMImportResolver::new(FileImportResolver::default()))140		.context_initializer(ContextInitializer::new(PathResolver::new_cwd_fallback()));141	let state = state.build();142	Box::into_raw(Box::new(VM {143		state,144		manifest_format: Box::new(JsonFormat::default()),145		trace_format: Box::new(CompactFormat::default()),146		trailing_newline: true,147		tla_args: FxHashMap::new(),148	}))149}150151/// Complement of [`jsonnet_vm_make`].152#[unsafe(no_mangle)]153#[allow(clippy::boxed_local)]154pub extern "C" fn jsonnet_destroy(vm: Box<VM>) {155	drop(vm);156}157158/// Set the maximum stack depth.159#[unsafe(no_mangle)]160pub extern "C" fn jsonnet_max_stack(_vm: &VM, v: c_uint) {161	set_stack_depth_limit(v as usize);162}163164/// Set the number of objects required before a garbage collection cycle is allowed.165///166/// No-op for now167#[unsafe(no_mangle)]168pub extern "C" fn jsonnet_gc_min_objects(_vm: &VM, _v: c_uint) {}169170/// Run the garbage collector after this amount of growth in the number of objects171///172/// No-op for now173#[unsafe(no_mangle)]174pub extern "C" fn jsonnet_gc_growth_trigger(_vm: &VM, _v: c_double) {}175176/// Expect a string as output and don't JSON encode it.177#[unsafe(no_mangle)]178pub extern "C" fn jsonnet_string_output(vm: &mut VM, v: c_int) {179	vm.manifest_format = match v {180		0 => Box::new(JsonFormat::default()),181		1 => Box::new(ToStringFormat),182		_ => panic!("incorrect output format"),183	};184}185186/// Enable/disable trailing newline in manifested/string output.187#[unsafe(no_mangle)]188pub extern "C" fn jsonnet_set_trailing_newline(vm: &mut VM, enable: c_int) {189	vm.trailing_newline = enable != 0;190}191192/// Allocate, resize, or free a buffer.  This will abort if the memory cannot be allocated. It will193/// only return NULL if sz was zero.194///195/// # Safety196///197/// `buf` should be either previosly allocated by this library, or NULL198///199/// This function is most definitely broken, but it works somehow, see TODO inside200#[unsafe(no_mangle)]201pub unsafe extern "C" fn jsonnet_realloc(_vm: &VM, buf: *mut u8, sz: usize) -> *mut u8 {202	if buf.is_null() {203		if sz == 0 {204			return std::ptr::null_mut();205		}206		return unsafe {207			std::alloc::alloc(Layout::from_size_align(sz, std::mem::align_of::<u8>()).unwrap())208		};209	}210	// TODO: Somehow store size of allocation, because its real size is probally not 16 :D211	// OR (Alternative way of fixing this TODO)212	// TODO: Standard allocator uses malloc, and it doesn't uses allocation size,213	// TODO: so it should work in normal cases. Maybe force allocator for this library?214	let old_layout = Layout::from_size_align(16, std::mem::align_of::<u8>()).unwrap();215	if sz == 0 {216		unsafe { std::alloc::dealloc(buf, old_layout) };217		return std::ptr::null_mut();218	}219	unsafe { std::alloc::realloc(buf, old_layout, sz) }220}221222/// Clean up a JSON subtree.223///224/// This is useful if you want to abort with an error mid-way through building a complex value.225#[unsafe(no_mangle)]226#[allow(clippy::boxed_local)]227pub extern "C" fn jsonnet_json_destroy(_vm: &VM, v: Box<Val>) {228	drop(v);229}230231/// Set the number of lines of stack trace to display (0 for all of them).232#[unsafe(no_mangle)]233pub extern "C" fn jsonnet_max_trace(vm: &mut VM, v: c_uint) {234	if let Some(format) = vm.trace_format.as_any_mut().downcast_mut::<CompactFormat>() {235		format.max_trace = v as usize;236	} else {237		panic!("max_trace is not supported by current tracing format")238	}239}240241/// Evaluate a file containing Jsonnet code, return a JSON string.242///243/// The returned string should be cleaned up with `jsonnet_realloc`.244///245/// # Safety246///247/// `filename` should be a NUL-terminated string248#[unsafe(no_mangle)]249pub unsafe extern "C" fn jsonnet_evaluate_file(250	vm: &VM,251	filename: *const c_char,252	error: &mut c_int,253) -> *const c_char {254	let filename = unsafe { parse_path(CStr::from_ptr(filename)) };255	match vm256		.state257		.import(filename)258		.and_then(|val| apply_tla(&vm.tla_args, val))259		.and_then(|val| val.manifest(&vm.manifest_format))260	{261		Ok(v) => {262			*error = 0;263			CString::new(&*v as &str).unwrap().into_raw()264		}265		Err(e) => {266			*error = 1;267			let mut out = String::new();268			vm.trace_format.write_trace(&mut out, &e).unwrap();269			CString::new(&out as &str).unwrap().into_raw()270		}271	}272}273274/// Evaluate a string containing Jsonnet code, return a JSON string.275///276/// The returned string should be cleaned up with `jsonnet_realloc`.277///278/// # Safety279///280/// `filename`, `snippet` should be a NUL-terminated strings281#[unsafe(no_mangle)]282pub unsafe extern "C" fn jsonnet_evaluate_snippet(283	vm: &VM,284	filename: *const c_char,285	snippet: *const c_char,286	error: &mut c_int,287) -> *const c_char {288	let filename = unsafe { CStr::from_ptr(filename) };289	let snippet = unsafe { CStr::from_ptr(snippet) };290	match vm291		.state292		.evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())293		.and_then(|val| apply_tla(&vm.tla_args, val))294		.and_then(|val| val.manifest(&vm.manifest_format))295	{296		Ok(mut v) => {297			if vm.trailing_newline {298				v.push('\n');299			}300			*error = 0;301			CString::new(&*v as &str).unwrap().into_raw()302		}303		Err(e) => {304			*error = 1;305			let mut out = String::new();306			vm.trace_format.write_trace(&mut out, &e).unwrap();307			CString::new(&out as &str).unwrap().into_raw()308		}309	}310}311312fn val_to_multi(val: Val, format: &dyn ManifestFormat) -> Result<Vec<(IStr, IStr)>> {313	let Val::Obj(val) = val else {314		bail!("expected object as multi output")315	};316	let mut out = Vec::new();317	for (k, v) in val.iter(318		#[cfg(feature = "exp-preserve-order")]319		false,320	) {321		out.push((k, v?.manifest(format)?.into()));322	}323	Ok(out)324}325326fn multi_to_raw(multi: Vec<(IStr, IStr)>, trailing_newline: bool) -> *const c_char {327	let mut out = Vec::new();328	for (i, (k, v)) in multi.iter().enumerate() {329		if i != 0 {330			out.push(0);331		}332		out.extend_from_slice(k.as_bytes());333		out.push(0);334		out.extend_from_slice(v.as_bytes());335		if trailing_newline {336			out.push(b'\n');337		}338	}339	out.push(0);340	out.push(0);341	let v = out.as_ptr();342	std::mem::forget(out);343	v.cast::<c_char>()344}345346/// # Safety347#[unsafe(no_mangle)]348pub unsafe extern "C" fn jsonnet_evaluate_file_multi(349	vm: &VM,350	filename: *const c_char,351	error: &mut c_int,352) -> *const c_char {353	let filename = unsafe { parse_path(CStr::from_ptr(filename)) };354	match vm355		.state356		.import(filename)357		.and_then(|val| apply_tla(&vm.tla_args, val))358		.and_then(|val| val_to_multi(val, &vm.manifest_format))359	{360		Ok(v) => {361			*error = 0;362			multi_to_raw(v, vm.trailing_newline)363		}364		Err(e) => {365			*error = 1;366			let mut out = String::new();367			vm.trace_format.write_trace(&mut out, &e).unwrap();368			CString::new(&out as &str).unwrap().into_raw()369		}370	}371}372373/// # Safety374#[unsafe(no_mangle)]375pub unsafe extern "C" fn jsonnet_evaluate_snippet_multi(376	vm: &VM,377	filename: *const c_char,378	snippet: *const c_char,379	error: &mut c_int,380) -> *const c_char {381	let filename = unsafe { CStr::from_ptr(filename) };382	let snippet = unsafe { CStr::from_ptr(snippet) };383	match vm384		.state385		.evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())386		.and_then(|val| apply_tla(&vm.tla_args, val))387		.and_then(|val| val_to_multi(val, &vm.manifest_format))388	{389		Ok(v) => {390			*error = 0;391			multi_to_raw(v, vm.trailing_newline)392		}393		Err(e) => {394			*error = 1;395			let mut out = String::new();396			vm.trace_format.write_trace(&mut out, &e).unwrap();397			CString::new(&out as &str).unwrap().into_raw()398		}399	}400}401402fn val_to_stream(val: Val, format: &dyn ManifestFormat) -> Result<Vec<IStr>> {403	let Val::Arr(val) = val else {404		bail!("expected array as stream output")405	};406	let mut out = Vec::new();407	for item in val.iter() {408		out.push(item?.manifest(format)?.into());409	}410	Ok(out)411}412413fn stream_to_raw(multi: Vec<IStr>, trailing_newline: bool) -> *const c_char {414	let mut out = Vec::new();415	for (i, v) in multi.iter().enumerate() {416		if i != 0 {417			out.push(0);418		}419		out.extend_from_slice(v.as_bytes());420		if trailing_newline {421			out.push(b'\n');422		}423	}424	out.push(0);425	out.push(0);426	let v = out.as_ptr();427	std::mem::forget(out);428	v.cast::<c_char>()429}430431/// # Safety432#[unsafe(no_mangle)]433pub unsafe extern "C" fn jsonnet_evaluate_file_stream(434	vm: &VM,435	filename: *const c_char,436	error: &mut c_int,437) -> *const c_char {438	let filename = unsafe { parse_path(CStr::from_ptr(filename)) };439	match vm440		.state441		.import(filename)442		.and_then(|val| apply_tla(&vm.tla_args, val))443		.and_then(|val| val_to_stream(val, &vm.manifest_format))444	{445		Ok(v) => {446			*error = 0;447			stream_to_raw(v, vm.trailing_newline)448		}449		Err(e) => {450			*error = 1;451			let mut out = String::new();452			vm.trace_format.write_trace(&mut out, &e).unwrap();453			CString::new(&out as &str).unwrap().into_raw()454		}455	}456}457458/// # Safety459#[unsafe(no_mangle)]460pub unsafe extern "C" fn jsonnet_evaluate_snippet_stream(461	vm: &VM,462	filename: *const c_char,463	snippet: *const c_char,464	error: &mut c_int,465) -> *const c_char {466	let filename = unsafe { CStr::from_ptr(filename) };467	let snippet = unsafe { CStr::from_ptr(snippet) };468	match vm469		.state470		.evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())471		.and_then(|val| apply_tla(&vm.tla_args, val))472		.and_then(|val| val_to_stream(val, &vm.manifest_format))473	{474		Ok(v) => {475			*error = 0;476			stream_to_raw(v, vm.trailing_newline)477		}478		Err(e) => {479			*error = 1;480			let mut out = String::new();481			vm.trace_format.write_trace(&mut out, &e).unwrap();482			CString::new(&out as &str).unwrap().into_raw()483		}484	}485}