git.delta.rocks / jrsonnet / refs/heads / master

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		CString::new(input.as_os_str().as_bytes()).expect("input has zero byte in it")70	}71	#[cfg(not(target_family = "unix"))]72	{73		let str = input.as_os_str().to_str().expect("bad utf-8");74		CString::new(str).expect("input has NUL inside")75	}76}7778#[derive(Acyclic)]79struct VMImportResolver {80	inner: RefCell<Rc<dyn ImportResolver>>,81}82impl VMImportResolver {83	fn new(value: impl ImportResolver) -> Self {84		Self {85			inner: RefCell::new(Rc::new(value)),86		}87	}88}89impl ImportResolver for VMImportResolver {90	fn load_file_contents(&self, resolved: &SourcePath) -> Result<Vec<u8>> {91		self.inner.borrow().load_file_contents(resolved)92	}9394	fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> Result<SourcePath> {95		self.inner.borrow().resolve_from(from, path)96	}9798	fn resolve_from_default(&self, path: &dyn AsPathLike) -> Result<SourcePath> {99		self.inner.borrow().resolve_from_default(path)100	}101}102103pub struct VM {104	state: State,105	manifest_format: Box<dyn ManifestFormat>,106	trailing_newline: bool,107	trace_format: Box<dyn TraceFormat>,108	tla_args: FxHashMap<IStr, TlaArg>,109}110impl VM {111	fn replace_import_resolver(&self, resolver: impl ImportResolver) {112		*(self.state.import_resolver() as &dyn Any)113			.downcast_ref::<VMImportResolver>()114			.expect("valid resolver ty")115			.inner116			.borrow_mut() = Rc::new(resolver);117	}118	fn add_jpath(&self, path: PathBuf) {119		let ir = self.state.import_resolver();120		let vmi = (ir as &dyn Any)121			.downcast_ref::<VMImportResolver>()122			.expect("valid resolver ty");123		let vmi = &mut *vmi.inner.borrow_mut();124		(vmi as &mut dyn Any)125			.downcast_mut::<FileImportResolver>()126			.expect("jpaths are not compatible with callback imports!")127			.add_jpath(path);128	}129}130131/// Creates a new Jsonnet virtual machine.132#[unsafe(no_mangle)]133#[allow(clippy::box_default)]134pub extern "C" fn jsonnet_make() -> *mut VM {135	let mut state = State::builder();136	state137		.import_resolver(VMImportResolver::new(FileImportResolver::default()))138		.context_initializer(ContextInitializer::new(PathResolver::new_cwd_fallback()));139	let state = state.build();140	Box::into_raw(Box::new(VM {141		state,142		manifest_format: Box::new(JsonFormat::default()),143		trace_format: Box::new(CompactFormat::default()),144		trailing_newline: true,145		tla_args: FxHashMap::new(),146	}))147}148149/// Complement of [`jsonnet_vm_make`].150#[unsafe(no_mangle)]151#[allow(clippy::boxed_local)]152pub extern "C" fn jsonnet_destroy(vm: Box<VM>) {153	drop(vm);154}155156/// Set the maximum stack depth.157#[unsafe(no_mangle)]158pub extern "C" fn jsonnet_max_stack(_vm: &VM, v: c_uint) {159	set_stack_depth_limit(v as usize);160}161162/// Set the number of objects required before a garbage collection cycle is allowed.163///164/// No-op for now165#[unsafe(no_mangle)]166pub extern "C" fn jsonnet_gc_min_objects(_vm: &VM, _v: c_uint) {}167168/// Run the garbage collector after this amount of growth in the number of objects169///170/// No-op for now171#[unsafe(no_mangle)]172pub extern "C" fn jsonnet_gc_growth_trigger(_vm: &VM, _v: c_double) {}173174/// Expect a string as output and don't JSON encode it.175#[unsafe(no_mangle)]176pub extern "C" fn jsonnet_string_output(vm: &mut VM, v: c_int) {177	vm.manifest_format = match v {178		0 => Box::new(JsonFormat::default()),179		1 => Box::new(ToStringFormat),180		_ => panic!("incorrect output format"),181	};182}183184/// Enable/disable trailing newline in manifested/string output.185#[unsafe(no_mangle)]186pub extern "C" fn jsonnet_set_trailing_newline(vm: &mut VM, enable: c_int) {187	vm.trailing_newline = enable != 0;188}189190/// Allocate, resize, or free a buffer.  This will abort if the memory cannot be allocated. It will191/// only return NULL if sz was zero.192///193/// # Safety194///195/// `buf` should be either previosly allocated by this library, or NULL196///197/// This function is most definitely broken, but it works somehow, see TODO inside198#[unsafe(no_mangle)]199pub unsafe extern "C" fn jsonnet_realloc(_vm: &VM, buf: *mut u8, sz: usize) -> *mut u8 {200	if buf.is_null() {201		if sz == 0 {202			return std::ptr::null_mut();203		}204		return unsafe {205			std::alloc::alloc(Layout::from_size_align(sz, std::mem::align_of::<u8>()).unwrap())206		};207	}208	// TODO: Somehow store size of allocation, because its real size is probally not 16 :D209	// OR (Alternative way of fixing this TODO)210	// TODO: Standard allocator uses malloc, and it doesn't uses allocation size,211	// TODO: so it should work in normal cases. Maybe force allocator for this library?212	let old_layout = Layout::from_size_align(16, std::mem::align_of::<u8>()).unwrap();213	if sz == 0 {214		unsafe { std::alloc::dealloc(buf, old_layout) };215		return std::ptr::null_mut();216	}217	unsafe { std::alloc::realloc(buf, old_layout, sz) }218}219220/// Clean up a JSON subtree.221///222/// This is useful if you want to abort with an error mid-way through building a complex value.223#[unsafe(no_mangle)]224#[allow(clippy::boxed_local)]225pub extern "C" fn jsonnet_json_destroy(_vm: &VM, v: Box<Val>) {226	drop(v);227}228229/// Set the number of lines of stack trace to display (0 for all of them).230#[unsafe(no_mangle)]231pub extern "C" fn jsonnet_max_trace(vm: &mut VM, v: c_uint) {232	if let Some(format) = vm.trace_format.as_any_mut().downcast_mut::<CompactFormat>() {233		format.max_trace = v as usize;234	} else {235		panic!("max_trace is not supported by current tracing format")236	}237}238239/// Evaluate a file containing Jsonnet code, return a JSON string.240///241/// The returned string should be cleaned up with `jsonnet_realloc`.242///243/// # Safety244///245/// `filename` should be a NUL-terminated string246#[unsafe(no_mangle)]247pub unsafe extern "C" fn jsonnet_evaluate_file(248	vm: &VM,249	filename: *const c_char,250	error: &mut c_int,251) -> *const c_char {252	let filename = unsafe { parse_path(CStr::from_ptr(filename)) };253	match vm254		.state255		.import(filename)256		.and_then(|val| apply_tla(&vm.tla_args, val))257		.and_then(|val| val.manifest(&vm.manifest_format))258	{259		Ok(v) => {260			*error = 0;261			CString::new(&*v as &str).unwrap().into_raw()262		}263		Err(e) => {264			*error = 1;265			let mut out = String::new();266			vm.trace_format.write_trace(&mut out, &e).unwrap();267			CString::new(&out as &str).unwrap().into_raw()268		}269	}270}271272/// Evaluate a string containing Jsonnet code, return a JSON string.273///274/// The returned string should be cleaned up with `jsonnet_realloc`.275///276/// # Safety277///278/// `filename`, `snippet` should be a NUL-terminated strings279#[unsafe(no_mangle)]280pub unsafe extern "C" fn jsonnet_evaluate_snippet(281	vm: &VM,282	filename: *const c_char,283	snippet: *const c_char,284	error: &mut c_int,285) -> *const c_char {286	let filename = unsafe { CStr::from_ptr(filename) };287	let snippet = unsafe { CStr::from_ptr(snippet) };288	match vm289		.state290		.evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())291		.and_then(|val| apply_tla(&vm.tla_args, val))292		.and_then(|val| val.manifest(&vm.manifest_format))293	{294		Ok(mut v) => {295			if vm.trailing_newline {296				v.push('\n');297			}298			*error = 0;299			CString::new(&*v as &str).unwrap().into_raw()300		}301		Err(e) => {302			*error = 1;303			let mut out = String::new();304			vm.trace_format.write_trace(&mut out, &e).unwrap();305			CString::new(&out as &str).unwrap().into_raw()306		}307	}308}309310fn val_to_multi(val: Val, format: &dyn ManifestFormat) -> Result<Vec<(IStr, IStr)>> {311	let Val::Obj(val) = val else {312		bail!("expected object as multi output")313	};314	let mut out = Vec::new();315	for (k, v) in val.iter(316		#[cfg(feature = "exp-preserve-order")]317		false,318	) {319		out.push((k, v?.manifest(format)?.into()));320	}321	Ok(out)322}323324fn multi_to_raw(multi: Vec<(IStr, IStr)>, trailing_newline: bool) -> *const c_char {325	let mut out = Vec::new();326	for (i, (k, v)) in multi.iter().enumerate() {327		if i != 0 {328			out.push(0);329		}330		out.extend_from_slice(k.as_bytes());331		out.push(0);332		out.extend_from_slice(v.as_bytes());333		if trailing_newline {334			out.push(b'\n');335		}336	}337	out.push(0);338	out.push(0);339	let v = out.as_ptr();340	std::mem::forget(out);341	v.cast::<c_char>()342}343344/// # Safety345#[unsafe(no_mangle)]346pub unsafe extern "C" fn jsonnet_evaluate_file_multi(347	vm: &VM,348	filename: *const c_char,349	error: &mut c_int,350) -> *const c_char {351	let filename = unsafe { parse_path(CStr::from_ptr(filename)) };352	match vm353		.state354		.import(filename)355		.and_then(|val| apply_tla(&vm.tla_args, val))356		.and_then(|val| val_to_multi(val, &vm.manifest_format))357	{358		Ok(v) => {359			*error = 0;360			multi_to_raw(v, vm.trailing_newline)361		}362		Err(e) => {363			*error = 1;364			let mut out = String::new();365			vm.trace_format.write_trace(&mut out, &e).unwrap();366			CString::new(&out as &str).unwrap().into_raw()367		}368	}369}370371/// # Safety372#[unsafe(no_mangle)]373pub unsafe extern "C" fn jsonnet_evaluate_snippet_multi(374	vm: &VM,375	filename: *const c_char,376	snippet: *const c_char,377	error: &mut c_int,378) -> *const c_char {379	let filename = unsafe { CStr::from_ptr(filename) };380	let snippet = unsafe { CStr::from_ptr(snippet) };381	match vm382		.state383		.evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())384		.and_then(|val| apply_tla(&vm.tla_args, val))385		.and_then(|val| val_to_multi(val, &vm.manifest_format))386	{387		Ok(v) => {388			*error = 0;389			multi_to_raw(v, vm.trailing_newline)390		}391		Err(e) => {392			*error = 1;393			let mut out = String::new();394			vm.trace_format.write_trace(&mut out, &e).unwrap();395			CString::new(&out as &str).unwrap().into_raw()396		}397	}398}399400fn val_to_stream(val: Val, format: &dyn ManifestFormat) -> Result<Vec<IStr>> {401	let Val::Arr(val) = val else {402		bail!("expected array as stream output")403	};404	let mut out = Vec::new();405	for item in val.iter() {406		out.push(item?.manifest(format)?.into());407	}408	Ok(out)409}410411fn stream_to_raw(multi: Vec<IStr>, trailing_newline: bool) -> *const c_char {412	let mut out = Vec::new();413	for (i, v) in multi.iter().enumerate() {414		if i != 0 {415			out.push(0);416		}417		out.extend_from_slice(v.as_bytes());418		if trailing_newline {419			out.push(b'\n');420		}421	}422	out.push(0);423	out.push(0);424	let v = out.as_ptr();425	std::mem::forget(out);426	v.cast::<c_char>()427}428429/// # Safety430#[unsafe(no_mangle)]431pub unsafe extern "C" fn jsonnet_evaluate_file_stream(432	vm: &VM,433	filename: *const c_char,434	error: &mut c_int,435) -> *const c_char {436	let filename = unsafe { parse_path(CStr::from_ptr(filename)) };437	match vm438		.state439		.import(filename)440		.and_then(|val| apply_tla(&vm.tla_args, val))441		.and_then(|val| val_to_stream(val, &vm.manifest_format))442	{443		Ok(v) => {444			*error = 0;445			stream_to_raw(v, vm.trailing_newline)446		}447		Err(e) => {448			*error = 1;449			let mut out = String::new();450			vm.trace_format.write_trace(&mut out, &e).unwrap();451			CString::new(&out as &str).unwrap().into_raw()452		}453	}454}455456/// # Safety457#[unsafe(no_mangle)]458pub unsafe extern "C" fn jsonnet_evaluate_snippet_stream(459	vm: &VM,460	filename: *const c_char,461	snippet: *const c_char,462	error: &mut c_int,463) -> *const c_char {464	let filename = unsafe { CStr::from_ptr(filename) };465	let snippet = unsafe { CStr::from_ptr(snippet) };466	match vm467		.state468		.evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())469		.and_then(|val| apply_tla(&vm.tla_args, val))470		.and_then(|val| val_to_stream(val, &vm.manifest_format))471	{472		Ok(v) => {473			*error = 0;474			stream_to_raw(v, vm.trailing_newline)475		}476		Err(e) => {477			*error = 1;478			let mut out = String::new();479			vm.trace_format.write_trace(&mut out, &e).unwrap();480			CString::new(&out as &str).unwrap().into_raw()481		}482	}483}