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;353637#[cfg(target_arch = "wasm32")]38#[no_mangle]39pub extern "C" fn _start() {}40414243444546#[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}130131132#[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}148149150#[unsafe(no_mangle)]151#[allow(clippy::boxed_local)]152pub extern "C" fn jsonnet_destroy(vm: Box<VM>) {153 drop(vm);154}155156157#[unsafe(no_mangle)]158pub extern "C" fn jsonnet_max_stack(_vm: &VM, v: c_uint) {159 set_stack_depth_limit(v as usize);160}161162163164165#[unsafe(no_mangle)]166pub extern "C" fn jsonnet_gc_min_objects(_vm: &VM, _v: c_uint) {}167168169170171#[unsafe(no_mangle)]172pub extern "C" fn jsonnet_gc_growth_trigger(_vm: &VM, _v: c_double) {}173174175#[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}183184185#[unsafe(no_mangle)]186pub extern "C" fn jsonnet_set_trailing_newline(vm: &mut VM, enable: c_int) {187 vm.trailing_newline = enable != 0;188}189190191192193194195196197198#[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 209 210 211 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}219220221222223#[unsafe(no_mangle)]224#[allow(clippy::boxed_local)]225pub extern "C" fn jsonnet_json_destroy(_vm: &VM, v: Box<Val>) {226 drop(v);227}228229230#[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}238239240241242243244245246#[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}271272273274275276277278279#[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}343344345#[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}370371372#[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}428429430#[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}455456457#[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}