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;363738#[cfg(target_arch = "wasm32")]39#[no_mangle]40pub extern "C" fn _start() {}41424344454647#[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}132133134#[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}149150151#[no_mangle]152#[allow(clippy::boxed_local)]153pub extern "C" fn jsonnet_destroy(vm: Box<VM>) {154 drop(vm);155}156157158#[no_mangle]159pub extern "C" fn jsonnet_max_stack(_vm: &VM, v: c_uint) {160 set_stack_depth_limit(v as usize);161}162163164165166#[no_mangle]167pub extern "C" fn jsonnet_gc_min_objects(_vm: &VM, _v: c_uint) {}168169170171172#[no_mangle]173pub extern "C" fn jsonnet_gc_growth_trigger(_vm: &VM, _v: c_double) {}174175176#[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}184185186187188189190191192193#[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 204 205 206 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}214215216217218#[no_mangle]219#[allow(clippy::boxed_local)]220pub extern "C" fn jsonnet_json_destroy(_vm: &VM, v: Box<Val>) {221 drop(v);222}223224225#[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}233234235236237238239240241#[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}266267268269270271272273274#[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}332333334#[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}359360361#[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}414415416#[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}441442443#[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}