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

difftreelog

source

bindings/jsonnet/src/lib.rs12.0 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	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	AsPathLike, FileImportResolver, IStr, ImportResolver, Result, State, Val,32};33use jrsonnet_gcmodule::Acyclic;34use jrsonnet_ir::SourcePath;35use jrsonnet_stdlib::ContextInitializer;3637/// WASM stub38#[cfg(target_arch = "wasm32")]39#[no_mangle]40pub extern "C" fn _start() {}4142/// Return the version string of the Jsonnet interpreter.43///44/// Conforms to [semantic versioning](http://semver.org/).45/// If this does not match `LIB_JSONNET_VERSION`46/// then there is a mismatch between header and compiled library.47#[no_mangle]48pub extern "C" fn jsonnet_version() -> &'static [u8; 8] {49	b"v0.20.0\0"50}5152unsafe fn parse_path(input: &CStr) -> Cow<'_, Path> {53	#[cfg(target_family = "unix")]54	{55		use std::os::unix::ffi::OsStrExt;56		let str = OsStr::from_bytes(input.to_bytes());57		Cow::Borrowed(Path::new(str))58	}59	#[cfg(not(target_family = "unix"))]60	{61		let string = input.to_str().expect("bad utf-8");62		Cow::Borrowed(string.as_ref())63	}64}6566unsafe fn unparse_path(input: &Path) -> CString {67	#[cfg(target_family = "unix")]68	{69		use std::os::unix::ffi::OsStrExt;70		let str = CString::new(input.as_os_str().as_bytes()).expect("input has zero byte in it");71		str72	}73	#[cfg(not(target_family = "unix"))]74	{75		let str = input.as_os_str().to_str().expect("bad utf-8");76		let cstr = CString::new(str).expect("input has NUL inside");77		cstr78	}79}8081#[derive(Acyclic)]82struct VMImportResolver {83	inner: RefCell<Rc<dyn ImportResolver>>,84}85impl VMImportResolver {86	fn new(value: impl ImportResolver) -> Self {87		Self {88			inner: RefCell::new(Rc::new(value)),89		}90	}91}92impl ImportResolver for VMImportResolver {93	fn load_file_contents(&self, resolved: &SourcePath) -> Result<Vec<u8>> {94		self.inner.borrow().load_file_contents(resolved)95	}9697	fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> Result<SourcePath> {98		self.inner.borrow().resolve_from(from, path)99	}100101	fn resolve_from_default(&self, path: &dyn AsPathLike) -> Result<SourcePath> {102		self.inner.borrow().resolve_from_default(path)103	}104}105106pub struct VM {107	state: State,108	manifest_format: Box<dyn ManifestFormat>,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#[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		tla_args: FxHashMap::new(),147	}))148}149150/// Complement of [`jsonnet_vm_make`].151#[no_mangle]152#[allow(clippy::boxed_local)]153pub extern "C" fn jsonnet_destroy(vm: Box<VM>) {154	drop(vm);155}156157/// Set the maximum stack depth.158#[no_mangle]159pub extern "C" fn jsonnet_max_stack(_vm: &VM, v: c_uint) {160	set_stack_depth_limit(v as usize);161}162163/// Set the number of objects required before a garbage collection cycle is allowed.164///165/// No-op for now166#[no_mangle]167pub extern "C" fn jsonnet_gc_min_objects(_vm: &VM, _v: c_uint) {}168169/// Run the garbage collector after this amount of growth in the number of objects170///171/// No-op for now172#[no_mangle]173pub extern "C" fn jsonnet_gc_growth_trigger(_vm: &VM, _v: c_double) {}174175/// Expect a string as output and don't JSON encode it.176#[no_mangle]177pub extern "C" fn jsonnet_string_output(vm: &mut VM, v: c_int) {178	vm.manifest_format = match v {179		0 => Box::new(JsonFormat::default()),180		1 => Box::new(ToStringFormat),181		_ => panic!("incorrect output format"),182	};183}184185/// Allocate, resize, or free a buffer.  This will abort if the memory cannot be allocated. It will186/// only return NULL if sz was zero.187///188/// # Safety189///190/// `buf` should be either previosly allocated by this library, or NULL191///192/// This function is most definitely broken, but it works somehow, see TODO inside193#[no_mangle]194pub unsafe extern "C" fn jsonnet_realloc(_vm: &VM, buf: *mut u8, sz: usize) -> *mut u8 {195	if buf.is_null() {196		if sz == 0 {197			return std::ptr::null_mut();198		}199		return unsafe {200			std::alloc::alloc(Layout::from_size_align(sz, std::mem::align_of::<u8>()).unwrap())201		};202	}203	// TODO: Somehow store size of allocation, because its real size is probally not 16 :D204	// OR (Alternative way of fixing this TODO)205	// TODO: Standard allocator uses malloc, and it doesn't uses allocation size,206	// TODO: so it should work in normal cases. Maybe force allocator for this library?207	let old_layout = Layout::from_size_align(16, std::mem::align_of::<u8>()).unwrap();208	if sz == 0 {209		unsafe { std::alloc::dealloc(buf, old_layout) };210		return std::ptr::null_mut();211	}212	unsafe { std::alloc::realloc(buf, old_layout, sz) }213}214215/// Clean up a JSON subtree.216///217/// This is useful if you want to abort with an error mid-way through building a complex value.218#[no_mangle]219#[allow(clippy::boxed_local)]220pub extern "C" fn jsonnet_json_destroy(_vm: &VM, v: Box<Val>) {221	drop(v);222}223224/// Set the number of lines of stack trace to display (0 for all of them).225#[no_mangle]226pub extern "C" fn jsonnet_max_trace(vm: &mut VM, v: c_uint) {227	if let Some(format) = vm.trace_format.as_any_mut().downcast_mut::<CompactFormat>() {228		format.max_trace = v as usize;229	} else {230		panic!("max_trace is not supported by current tracing format")231	}232}233234/// Evaluate a file containing Jsonnet code, return a JSON string.235///236/// The returned string should be cleaned up with `jsonnet_realloc`.237///238/// # Safety239///240/// `filename` should be a NUL-terminated string241#[no_mangle]242pub unsafe extern "C" fn jsonnet_evaluate_file(243	vm: &VM,244	filename: *const c_char,245	error: &mut c_int,246) -> *const c_char {247	let filename = unsafe { parse_path(CStr::from_ptr(filename)) };248	match vm249		.state250		.import(filename)251		.and_then(|val| apply_tla(&vm.tla_args, val))252		.and_then(|val| val.manifest(&vm.manifest_format))253	{254		Ok(v) => {255			*error = 0;256			CString::new(&*v as &str).unwrap().into_raw()257		}258		Err(e) => {259			*error = 1;260			let mut out = String::new();261			vm.trace_format.write_trace(&mut out, &e).unwrap();262			CString::new(&out as &str).unwrap().into_raw()263		}264	}265}266267/// Evaluate a string containing Jsonnet code, return a JSON string.268///269/// The returned string should be cleaned up with `jsonnet_realloc`.270///271/// # Safety272///273/// `filename`, `snippet` should be a NUL-terminated strings274#[no_mangle]275pub unsafe extern "C" fn jsonnet_evaluate_snippet(276	vm: &VM,277	filename: *const c_char,278	snippet: *const c_char,279	error: &mut c_int,280) -> *const c_char {281	let filename = unsafe { CStr::from_ptr(filename) };282	let snippet = unsafe { CStr::from_ptr(snippet) };283	match vm284		.state285		.evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())286		.and_then(|val| apply_tla(&vm.tla_args, val))287		.and_then(|val| val.manifest(&vm.manifest_format))288	{289		Ok(v) => {290			*error = 0;291			CString::new(&*v as &str).unwrap().into_raw()292		}293		Err(e) => {294			*error = 1;295			let mut out = String::new();296			vm.trace_format.write_trace(&mut out, &e).unwrap();297			CString::new(&out as &str).unwrap().into_raw()298		}299	}300}301302fn val_to_multi(val: Val, format: &dyn ManifestFormat) -> Result<Vec<(IStr, IStr)>> {303	let Val::Obj(val) = val else {304		bail!("expected object as multi output")305	};306	let mut out = Vec::new();307	for (k, v) in val.iter(308		#[cfg(feature = "exp-preserve-order")]309		false,310	) {311		out.push((k, v?.manifest(format)?.into()));312	}313	Ok(out)314}315316fn multi_to_raw(multi: Vec<(IStr, IStr)>) -> *const c_char {317	let mut out = Vec::new();318	for (i, (k, v)) in multi.iter().enumerate() {319		if i != 0 {320			out.push(0);321		}322		out.extend_from_slice(k.as_bytes());323		out.push(0);324		out.extend_from_slice(v.as_bytes());325	}326	out.push(0);327	out.push(0);328	let v = out.as_ptr();329	std::mem::forget(out);330	v.cast::<c_char>()331}332333/// # Safety334#[no_mangle]335pub unsafe extern "C" fn jsonnet_evaluate_file_multi(336	vm: &VM,337	filename: *const c_char,338	error: &mut c_int,339) -> *const c_char {340	let filename = unsafe { parse_path(CStr::from_ptr(filename)) };341	match vm342		.state343		.import(filename)344		.and_then(|val| apply_tla(&vm.tla_args, val))345		.and_then(|val| val_to_multi(val, &vm.manifest_format))346	{347		Ok(v) => {348			*error = 0;349			multi_to_raw(v)350		}351		Err(e) => {352			*error = 1;353			let mut out = String::new();354			vm.trace_format.write_trace(&mut out, &e).unwrap();355			CString::new(&out as &str).unwrap().into_raw()356		}357	}358}359360/// # Safety361#[no_mangle]362pub unsafe extern "C" fn jsonnet_evaluate_snippet_multi(363	vm: &VM,364	filename: *const c_char,365	snippet: *const c_char,366	error: &mut c_int,367) -> *const c_char {368	let filename = unsafe { CStr::from_ptr(filename) };369	let snippet = unsafe { CStr::from_ptr(snippet) };370	match vm371		.state372		.evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())373		.and_then(|val| apply_tla(&vm.tla_args, val))374		.and_then(|val| val_to_multi(val, &vm.manifest_format))375	{376		Ok(v) => {377			*error = 0;378			multi_to_raw(v)379		}380		Err(e) => {381			*error = 1;382			let mut out = String::new();383			vm.trace_format.write_trace(&mut out, &e).unwrap();384			CString::new(&out as &str).unwrap().into_raw()385		}386	}387}388389fn val_to_stream(val: Val, format: &dyn ManifestFormat) -> Result<Vec<IStr>> {390	let Val::Arr(val) = val else {391		bail!("expected array as stream output")392	};393	let mut out = Vec::new();394	for item in val.iter() {395		out.push(item?.manifest(format)?.into());396	}397	Ok(out)398}399400fn stream_to_raw(multi: Vec<IStr>) -> *const c_char {401	let mut out = Vec::new();402	for (i, v) in multi.iter().enumerate() {403		if i != 0 {404			out.push(0);405		}406		out.extend_from_slice(v.as_bytes());407	}408	out.push(0);409	out.push(0);410	let v = out.as_ptr();411	std::mem::forget(out);412	v.cast::<c_char>()413}414415/// # Safety416#[no_mangle]417pub unsafe extern "C" fn jsonnet_evaluate_file_stream(418	vm: &VM,419	filename: *const c_char,420	error: &mut c_int,421) -> *const c_char {422	let filename = unsafe { parse_path(CStr::from_ptr(filename)) };423	match vm424		.state425		.import(filename)426		.and_then(|val| apply_tla(&vm.tla_args, val))427		.and_then(|val| val_to_stream(val, &vm.manifest_format))428	{429		Ok(v) => {430			*error = 0;431			stream_to_raw(v)432		}433		Err(e) => {434			*error = 1;435			let mut out = String::new();436			vm.trace_format.write_trace(&mut out, &e).unwrap();437			CString::new(&out as &str).unwrap().into_raw()438		}439	}440}441442/// # Safety443#[no_mangle]444pub unsafe extern "C" fn jsonnet_evaluate_snippet_stream(445	vm: &VM,446	filename: *const c_char,447	snippet: *const c_char,448	error: &mut c_int,449) -> *const c_char {450	let filename = unsafe { CStr::from_ptr(filename) };451	let snippet = unsafe { CStr::from_ptr(snippet) };452	match vm453		.state454		.evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())455		.and_then(|val| apply_tla(&vm.tla_args, val))456		.and_then(|val| val_to_stream(val, &vm.manifest_format))457	{458		Ok(v) => {459			*error = 0;460			stream_to_raw(v)461		}462		Err(e) => {463			*error = 1;464			let mut out = String::new();465			vm.trace_format.write_trace(&mut out, &e).unwrap();466			CString::new(&out as &str).unwrap().into_raw()467		}468	}469}