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

difftreelog

source

bindings/jsonnet/src/lib.rs12.2 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	function::TlaArg,26	gc::WithCapacityExt as _,27	manifest::{JsonFormat, ManifestFormat, ToStringFormat},28	rustc_hash::FxHashMap,29	stack::set_stack_depth_limit,30	trace::{CompactFormat, PathResolver, TraceFormat},31	FileImportResolver, IStr, ImportResolver, Result, State, Val,32};33use jrsonnet_gcmodule::Acyclic;34use jrsonnet_parser::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/// 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#[no_mangle]47pub extern "C" fn jsonnet_version() -> &'static [u8; 8] {48	b"v0.20.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) -> Cow<'_, CStr> {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		Cow::Owned(str)71	}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		Cow::Owned(cstr)77	}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: &str) -> Result<SourcePath> {97		self.inner.borrow().resolve_from(from, path)98	}99100	fn resolve_from_default(&self, path: &str) -> Result<SourcePath> {101		self.inner.borrow().resolve_from_default(path)102	}103104	fn resolve(&self, path: &Path) -> Result<SourcePath> {105		self.inner.borrow().resolve(path)106	}107}108109pub struct VM {110	state: State,111	manifest_format: Box<dyn ManifestFormat>,112	trace_format: Box<dyn TraceFormat>,113	tla_args: FxHashMap<IStr, TlaArg>,114}115impl VM {116	fn replace_import_resolver(&self, resolver: impl ImportResolver) {117		*(self.state.import_resolver() as &dyn Any)118			.downcast_ref::<VMImportResolver>()119			.expect("valid resolver ty")120			.inner121			.borrow_mut() = Rc::new(resolver);122	}123	fn add_jpath(&self, path: PathBuf) {124		let ir = self.state.import_resolver();125		let vmi = (ir as &dyn Any)126			.downcast_ref::<VMImportResolver>()127			.expect("valid resolver ty");128		let vmi = &mut *vmi.inner.borrow_mut();129		(vmi as &mut dyn Any)130			.downcast_mut::<FileImportResolver>()131			.expect("jpaths are not compatible with callback imports!")132			.add_jpath(path);133	}134}135136/// Creates a new Jsonnet virtual machine.137#[no_mangle]138#[allow(clippy::box_default)]139pub extern "C" fn jsonnet_make() -> *mut VM {140	let mut state = State::builder();141	state142		.import_resolver(VMImportResolver::new(FileImportResolver::default()))143		.context_initializer(ContextInitializer::new(PathResolver::new_cwd_fallback()));144	let state = state.build();145	Box::into_raw(Box::new(VM {146		state,147		manifest_format: Box::new(JsonFormat::default()),148		trace_format: Box::new(CompactFormat::default()),149		tla_args: FxHashMap::new(),150	}))151}152153/// Complement of [`jsonnet_vm_make`].154#[no_mangle]155#[allow(clippy::boxed_local)]156pub extern "C" fn jsonnet_destroy(vm: Box<VM>) {157	drop(vm);158}159160/// Set the maximum stack depth.161#[no_mangle]162pub extern "C" fn jsonnet_max_stack(_vm: &VM, v: c_uint) {163	set_stack_depth_limit(v as usize);164}165166/// Set the number of objects required before a garbage collection cycle is allowed.167///168/// No-op for now169#[no_mangle]170pub extern "C" fn jsonnet_gc_min_objects(_vm: &VM, _v: c_uint) {}171172/// Run the garbage collector after this amount of growth in the number of objects173///174/// No-op for now175#[no_mangle]176pub extern "C" fn jsonnet_gc_growth_trigger(_vm: &VM, _v: c_double) {}177178/// Expect a string as output and don't JSON encode it.179#[no_mangle]180pub extern "C" fn jsonnet_string_output(vm: &mut VM, v: c_int) {181	vm.manifest_format = match v {182		0 => Box::new(JsonFormat::default()),183		1 => Box::new(ToStringFormat),184		_ => panic!("incorrect output format"),185	};186}187188/// Allocate, resize, or free a buffer.  This will abort if the memory cannot be allocated. It will189/// only return NULL if sz was zero.190///191/// # Safety192///193/// `buf` should be either previosly allocated by this library, or NULL194///195/// This function is most definitely broken, but it works somehow, see TODO inside196#[no_mangle]197pub unsafe extern "C" fn jsonnet_realloc(_vm: &VM, buf: *mut u8, sz: usize) -> *mut u8 {198	if buf.is_null() {199		if sz == 0 {200			return std::ptr::null_mut();201		}202		return unsafe {203			std::alloc::alloc(Layout::from_size_align(sz, std::mem::align_of::<u8>()).unwrap())204		};205	}206	// TODO: Somehow store size of allocation, because its real size is probally not 16 :D207	// OR (Alternative way of fixing this TODO)208	// TODO: Standard allocator uses malloc, and it doesn't uses allocation size,209	// TODO: so it should work in normal cases. Maybe force allocator for this library?210	let old_layout = Layout::from_size_align(16, std::mem::align_of::<u8>()).unwrap();211	if sz == 0 {212		unsafe { std::alloc::dealloc(buf, old_layout) };213		return std::ptr::null_mut();214	}215	unsafe { std::alloc::realloc(buf, old_layout, sz) }216}217218/// Clean up a JSON subtree.219///220/// This is useful if you want to abort with an error mid-way through building a complex value.221#[no_mangle]222#[allow(clippy::boxed_local)]223pub extern "C" fn jsonnet_json_destroy(_vm: &VM, v: Box<Val>) {224	drop(v);225}226227/// Set the number of lines of stack trace to display (0 for all of them).228#[no_mangle]229pub extern "C" fn jsonnet_max_trace(vm: &mut VM, v: c_uint) {230	if let Some(format) = vm.trace_format.as_any_mut().downcast_mut::<CompactFormat>() {231		format.max_trace = v as usize;232	} else {233		panic!("max_trace is not supported by current tracing format")234	}235}236237/// Evaluate a file containing Jsonnet code, return a JSON string.238///239/// The returned string should be cleaned up with `jsonnet_realloc`.240///241/// # Safety242///243/// `filename` should be a NUL-terminated string244#[no_mangle]245pub unsafe extern "C" fn jsonnet_evaluate_file(246	vm: &VM,247	filename: *const c_char,248	error: &mut c_int,249) -> *const c_char {250	let filename = unsafe { parse_path(CStr::from_ptr(filename)) };251	match vm252		.state253		.import(filename)254		.and_then(|val| apply_tla(vm.state.clone(), &vm.tla_args, val))255		.and_then(|val| val.manifest(&vm.manifest_format))256	{257		Ok(v) => {258			*error = 0;259			CString::new(&*v as &str).unwrap().into_raw()260		}261		Err(e) => {262			*error = 1;263			let mut out = String::new();264			vm.trace_format.write_trace(&mut out, &e).unwrap();265			CString::new(&out as &str).unwrap().into_raw()266		}267	}268}269270/// Evaluate a string containing Jsonnet code, return a JSON string.271///272/// The returned string should be cleaned up with `jsonnet_realloc`.273///274/// # Safety275///276/// `filename`, `snippet` should be a NUL-terminated strings277#[no_mangle]278pub unsafe extern "C" fn jsonnet_evaluate_snippet(279	vm: &VM,280	filename: *const c_char,281	snippet: *const c_char,282	error: &mut c_int,283) -> *const c_char {284	let filename = unsafe { CStr::from_ptr(filename) };285	let snippet = unsafe { CStr::from_ptr(snippet) };286	match vm287		.state288		.evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())289		.and_then(|val| apply_tla(vm.state.clone(), &vm.tla_args, val))290		.and_then(|val| val.manifest(&vm.manifest_format))291	{292		Ok(v) => {293			*error = 0;294			CString::new(&*v as &str).unwrap().into_raw()295		}296		Err(e) => {297			*error = 1;298			let mut out = String::new();299			vm.trace_format.write_trace(&mut out, &e).unwrap();300			CString::new(&out as &str).unwrap().into_raw()301		}302	}303}304305fn val_to_multi(val: Val, format: &dyn ManifestFormat) -> Result<Vec<(IStr, IStr)>> {306	let Val::Obj(val) = val else {307		bail!("expected object as multi output")308	};309	let mut out = Vec::new();310	for (k, v) in val.iter(311		#[cfg(feature = "exp-preserve-order")]312		false,313	) {314		out.push((k, v?.manifest(format)?.into()));315	}316	Ok(out)317}318319fn multi_to_raw(multi: Vec<(IStr, IStr)>) -> *const c_char {320	let mut out = Vec::new();321	for (i, (k, v)) in multi.iter().enumerate() {322		if i != 0 {323			out.push(0);324		}325		out.extend_from_slice(k.as_bytes());326		out.push(0);327		out.extend_from_slice(v.as_bytes());328	}329	out.push(0);330	out.push(0);331	let v = out.as_ptr();332	std::mem::forget(out);333	v.cast::<c_char>()334}335336/// # Safety337#[no_mangle]338pub unsafe extern "C" fn jsonnet_evaluate_file_multi(339	vm: &VM,340	filename: *const c_char,341	error: &mut c_int,342) -> *const c_char {343	let filename = unsafe { parse_path(CStr::from_ptr(filename)) };344	match vm345		.state346		.import(filename)347		.and_then(|val| apply_tla(vm.state.clone(), &vm.tla_args, val))348		.and_then(|val| val_to_multi(val, &vm.manifest_format))349	{350		Ok(v) => {351			*error = 0;352			multi_to_raw(v)353		}354		Err(e) => {355			*error = 1;356			let mut out = String::new();357			vm.trace_format.write_trace(&mut out, &e).unwrap();358			CString::new(&out as &str).unwrap().into_raw()359		}360	}361}362363/// # Safety364#[no_mangle]365pub unsafe extern "C" fn jsonnet_evaluate_snippet_multi(366	vm: &VM,367	filename: *const c_char,368	snippet: *const c_char,369	error: &mut c_int,370) -> *const c_char {371	let filename = unsafe { CStr::from_ptr(filename) };372	let snippet = unsafe { CStr::from_ptr(snippet) };373	match vm374		.state375		.evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())376		.and_then(|val| apply_tla(vm.state.clone(), &vm.tla_args, val))377		.and_then(|val| val_to_multi(val, &vm.manifest_format))378	{379		Ok(v) => {380			*error = 0;381			multi_to_raw(v)382		}383		Err(e) => {384			*error = 1;385			let mut out = String::new();386			vm.trace_format.write_trace(&mut out, &e).unwrap();387			CString::new(&out as &str).unwrap().into_raw()388		}389	}390}391392fn val_to_stream(val: Val, format: &dyn ManifestFormat) -> Result<Vec<IStr>> {393	let Val::Arr(val) = val else {394		bail!("expected array as stream output")395	};396	let mut out = Vec::new();397	for item in val.iter() {398		out.push(item?.manifest(format)?.into());399	}400	Ok(out)401}402403fn stream_to_raw(multi: Vec<IStr>) -> *const c_char {404	let mut out = Vec::new();405	for (i, v) in multi.iter().enumerate() {406		if i != 0 {407			out.push(0);408		}409		out.extend_from_slice(v.as_bytes());410	}411	out.push(0);412	out.push(0);413	let v = out.as_ptr();414	std::mem::forget(out);415	v.cast::<c_char>()416}417418/// # Safety419#[no_mangle]420pub unsafe extern "C" fn jsonnet_evaluate_file_stream(421	vm: &VM,422	filename: *const c_char,423	error: &mut c_int,424) -> *const c_char {425	let filename = unsafe { parse_path(CStr::from_ptr(filename)) };426	match vm427		.state428		.import(filename)429		.and_then(|val| apply_tla(vm.state.clone(), &vm.tla_args, val))430		.and_then(|val| val_to_stream(val, &vm.manifest_format))431	{432		Ok(v) => {433			*error = 0;434			stream_to_raw(v)435		}436		Err(e) => {437			*error = 1;438			let mut out = String::new();439			vm.trace_format.write_trace(&mut out, &e).unwrap();440			CString::new(&out as &str).unwrap().into_raw()441		}442	}443}444445/// # Safety446#[no_mangle]447pub unsafe extern "C" fn jsonnet_evaluate_snippet_stream(448	vm: &VM,449	filename: *const c_char,450	snippet: *const c_char,451	error: &mut c_int,452) -> *const c_char {453	let filename = unsafe { CStr::from_ptr(filename) };454	let snippet = unsafe { CStr::from_ptr(snippet) };455	match vm456		.state457		.evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())458		.and_then(|val| apply_tla(vm.state.clone(), &vm.tla_args, val))459		.and_then(|val| val_to_stream(val, &vm.manifest_format))460	{461		Ok(v) => {462			*error = 0;463			stream_to_raw(v)464		}465		Err(e) => {466			*error = 1;467			let mut out = String::new();468			vm.trace_format.write_trace(&mut out, &e).unwrap();469			CString::new(&out as &str).unwrap().into_raw()470		}471	}472}