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

difftreelog

refactor split libjsonnet code into modules

Лач2020-07-19parent: #3943cce.patch.diff
in: master

11 files changed

addedbindings/c/.gitignorediffbeforeafterboth
--- /dev/null
+++ b/bindings/c/.gitignore
@@ -0,0 +1 @@
+libjsonnet_test_file
modifiedbindings/js/index.jsdiffbeforeafterboth
--- a/bindings/js/index.js
+++ b/bindings/js/index.js
@@ -1,16 +1,48 @@
 const fs = require('fs');
+const path = require('path');
 const { WASI } = require('wasi');
 const wasi = new WASI({
 	args: process.argv,
 	env: process.env,
 	preopens: {},
 });
-const importObject = { wasi_snapshot_preview1: wasi.wasiImport };
 
 class JsonnetVM {
 	constructor(wasm, vm) {
 		this.wasm = wasm;
 		this.vm = vm;
+		this.wasm.exports.jrsonnet_set_trace_format(this.vm, 1);
+
+		this.setImportCallback((from, to) => {
+			const resolved = path.resolve(from, to);
+			return {
+				value: fs.readFileSync(resolved).toString('utf-8'),
+				foundHere: resolved,
+			};
+		})
+	}
+
+	/**
+	 * @param {(from: string, to: string) => {foundHere: string, value: string}} cb
+	 */
+	setImportCallback(cb) {
+		this.wasm.importCbs.set(this.vm, (base, rel, foundHere, success) => {
+			const baseStr = this.wasm.readString(base);
+			const relStr = this.wasm.readString(rel);
+			try {
+				const value = cb(baseStr, relStr);
+				this.wasm.memorySlice32Len(foundHere, 1)[0] = this.allocateString(value.foundHere);
+				this.wasm.memorySlice32Len(success, 1)[0] = 1;
+				return this.allocateString(value.value);
+			} catch (e) {
+				this.wasm.memorySlice32Len(success, 1)[0] = 0;
+				return this.allocateString(e.stack)
+			}
+		});
+		this.wasm.exports.jrsonnet_apply_static_import_callback(
+			this.vm,
+			this.vm,
+		);
 	}
 
 	alloc(length) {
@@ -18,7 +50,7 @@
 	}
 	allocateString(string) {
 		const byteLength = new TextEncoder().encode(string).length;
-		const addr = this.alloc(byteLength);
+		const addr = this.alloc(byteLength + 1);
 		this.wasm.writeString(addr, string);
 		return addr;
 	}
@@ -36,7 +68,7 @@
 		const result = this.wasm.readString(resultAddr).trim();
 		this.dealloc(resultAddr);
 		if (resultCode[0] === 1) {
-			const error = new Error(this.normalizeErrorString(result));
+			const error = new Error(result);
 			throw error;
 		} else {
 			return result;
@@ -54,31 +86,46 @@
 		const result = this.wasm.readString(resultAddr);
 		this.dealloc(resultAddr);
 		if (resultCode[0] === 1) {
-			const error = new Error(this.normalizeErrorString(result));
+			const error = new Error(result);
 			throw error;
 		} else {
 			return result;
 		}
 	}
-	normalizeErrorString(str) {
-		str = str.trim();
-		const newLine = str.indexOf('\n');
-		if (newLine === -1) return str;
-		let message = str.slice(0, newLine);
-		let trace = str.slice(newLine + 1).split('\n').map(s => s.split(' ---- ')).map(([p, v]) => `    at ${v} (${p})`).join('\n');
-		return `${message}\n${trace}`;
+
+	/**
+	 * Destroys vm, any future call to this object will fail, and all resources will be freed
+	 */
+	destroy() {
+		this.wasm.exports.jsonnet_destroy(this.vm);
+		this.wasm.importCbs.delete(this.vm);
 	}
 }
 
 class JsonnetWASM {
-	constructor() { }
+	constructor() {
+		this.importCbs = new Map();
+	}
 
 	async init(buf) {
 		const wasm = await WebAssembly.compile(buf);
-		const instance = await WebAssembly.instantiate(wasm, importObject);
+		const instance = await WebAssembly.instantiate(wasm, {
+			wasi_snapshot_preview1: wasi.wasiImport,
+			env: {
+				_jrsonnet_static_import_callback: (ctx, base, rel, found_here, success) => {
+					if (!this.importCbs.has(ctx)) {
+						throw new Error(`Got unknown ctx callback: ${ctx}`);
+					}
+					return this.importCbs.get(ctx)(base, rel, found_here, success);
+				}
+			}
+		});
 		wasi.start(instance);
 		this.instance = instance;
 	}
+	/**
+	 * @type Record<string, WebAssembly.ExportValue>
+	 */
 	get exports() {
 		return this.instance.exports;
 	}
@@ -91,6 +138,9 @@
 	memorySliceLen(start, length) {
 		return new Uint8Array(this.memoryBuffer, start, length);
 	}
+	memorySlice32Len(start, length) {
+		return new Uint32Array(this.memoryBuffer, start, length);
+	}
 	memorySlice(start, end) {
 		return new Uint8Array(this.memoryBuffer, start, start && end && (end - start));
 	}
@@ -119,15 +169,13 @@
 (async () => {
 	try {
 		const jsonnet = new JsonnetWASM();
-		await jsonnet.init(fs.readFileSync(`${__dirname}/../../target/wasm32-wasi/release/jsonnet.wasi.wasm`));
+		await jsonnet.init(fs.readFileSync(`${__dirname}/../../target/wasm32-wasi/release/jsonnet.wasm`));
 		console.log(`Version = ${jsonnet.version()}`);
 
 		const vm = jsonnet.newVM();
 		console.log(vm.evaluateSnippet('./snip.jsonnet', `
-			local a(b) = error "sad" + b;
-			local c() = a(2 + 2);
-			c()
-		`))
+			2+2
+		`));
 		console.log(vm.evaluateFile('./test.jsonnet'));
 	} catch (e) {
 		console.log(e.stack);
modifiedbindings/jsonnet/Cargo.tomldiffbeforeafterboth
--- a/bindings/jsonnet/Cargo.toml
+++ b/bindings/jsonnet/Cargo.toml
@@ -9,7 +9,6 @@
 [dependencies]
 jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "1.0.0" }
 jrsonnet-parser = { path = "../../crates/jrsonnet-parser", version = "1.0.0" }
-libc = "0.2.71"
 
 [lib]
 crate-type = ["cdylib"]
addedbindings/jsonnet/src/interop.rsdiffbeforeafterboth
--- /dev/null
+++ b/bindings/jsonnet/src/interop.rs
@@ -0,0 +1,36 @@
+//! Jrsonnet specific additional binding helpers
+
+use crate::import::jsonnet_import_callback;
+use jrsonnet_evaluator::EvaluationState;
+use std::{
+	ffi::c_void,
+	os::raw::{c_char, c_int},
+};
+
+extern "C" {
+	pub fn _jrsonnet_static_import_callback(
+		ctx: *mut c_void,
+		base: *const c_char,
+		rel: *const c_char,
+		found_here: *mut *const c_char,
+		success: &mut c_int,
+	) -> *const c_char;
+}
+
+/// # Safety
+#[no_mangle]
+pub unsafe extern "C" fn jrsonnet_apply_static_import_callback(
+	vm: &EvaluationState,
+	ctx: *mut c_void,
+) {
+	jsonnet_import_callback(vm, _jrsonnet_static_import_callback, ctx)
+}
+
+#[no_mangle]
+pub extern "C" fn jrsonnet_set_trace_format(vm: &EvaluationState, format: u8) {
+	use jrsonnet_evaluator::trace::JSFormat;
+	match format {
+		1 => vm.set_trace_format(Box::new(JSFormat)),
+		_ => panic!("unknown trace format"),
+	}
+}
modifiedbindings/jsonnet/src/lib.rsdiffbeforeafterboth
before · bindings/jsonnet/src/lib.rs
1use jrsonnet_evaluator::{2	create_error, create_error_result, Error, EvaluationState, ImportResolver, LazyBinding,3	LazyVal, ObjMember, ObjValue, Result, Val,4};5use jrsonnet_parser::Visibility;6use libc::{c_char, c_double, c_int, c_uint};7use std::{8	alloc::Layout,9	any::Any,10	cell::RefCell,11	collections::HashMap,12	ffi::{CStr, CString},13	fs::File,14	io::Read,15	path::PathBuf,16	rc::Rc,17};1819#[no_mangle]20#[cfg(target = "wasm32-wasi")]21pub extern "C" fn _start() {}2223#[no_mangle]24pub extern "C" fn jsonnet_version() -> &'static [u8; 8] {25	b"v0.16.0\0"26}2728#[derive(Default)]29struct NativeImportResolver {30	library_paths: RefCell<Vec<PathBuf>>,31}32impl NativeImportResolver {33	fn add_jpath(&self, path: PathBuf) {34		self.library_paths.borrow_mut().push(path);35	}36}37impl ImportResolver for NativeImportResolver {38	fn resolve_file(&self, from: &PathBuf, path: &PathBuf) -> Result<Rc<PathBuf>> {39		let mut new_path = from.clone();40		new_path.push(path);41		if new_path.exists() {42			Ok(Rc::new(new_path))43		} else {44			for library_path in self.library_paths.borrow().iter() {45				let mut cloned = library_path.clone();46				cloned.push(path);47				if cloned.exists() {48					return Ok(Rc::new(cloned));49				}50			}51			create_error_result(Error::ImportFileNotFound(from.clone(), path.clone()))52		}53	}54	fn load_file_contents(&self, id: &PathBuf) -> Result<Rc<str>> {55		let mut file =56			File::open(id).map_err(|_e| create_error(Error::ResolvedFileNotFound(id.clone())))?;57		let mut out = String::new();58		file.read_to_string(&mut out)59			.map_err(|_e| create_error(Error::ImportBadFileUtf8(id.clone())))?;60		Ok(out.into())61	}62	unsafe fn as_any(&self) -> &dyn Any {63		self64	}65}6667#[no_mangle]68pub extern "C" fn jsonnet_make() -> *mut EvaluationState {69	let state = EvaluationState::default();70	state.with_stdlib();71	state.settings_mut().import_resolver = Box::new(NativeImportResolver::default());72	Box::into_raw(Box::new(state))73}7475/// # Safety76#[no_mangle]77#[allow(clippy::boxed_local)]78pub unsafe extern "C" fn jsonnet_destroy(vm: *mut EvaluationState) {79	Box::from_raw(vm);80}8182#[no_mangle]83pub extern "C" fn jsonnet_max_stack(vm: &EvaluationState, v: c_uint) {84	vm.settings_mut().max_stack = v as usize;85}8687// jrsonnet currently have no GC, so these functions is no-op88#[no_mangle]89pub extern "C" fn jsonnet_gc_min_objects(_vm: &EvaluationState, _v: c_uint) {}90#[no_mangle]91pub extern "C" fn jsonnet_gc_growth_trigger(_vm: &EvaluationState, _v: c_double) {}9293// TODO94#[no_mangle]95pub extern "C" fn jsonnet_string_output(_vm: &EvaluationState, _v: c_int) {96	todo!()97}9899#[no_mangle]100pub extern "C" fn jsonnet_json_extract_string(_vm: &EvaluationState, v: &Val) -> *mut c_char {101	match v.unwrap_if_lazy().unwrap() {102		Val::Str(s) => CString::new(&*s as &str).unwrap().into_raw(),103		_ => std::ptr::null_mut(),104	}105}106#[no_mangle]107pub extern "C" fn jsonnet_json_extract_number(108	_vm: &EvaluationState,109	v: &Val,110	out: &mut c_double,111) -> c_int {112	match v.unwrap_if_lazy().unwrap() {113		Val::Num(n) => {114			*out = n;115			1116		}117		_ => 0,118	}119}120#[no_mangle]121pub extern "C" fn jsonnet_json_extract_bool(_vm: &EvaluationState, v: &Val) -> c_int {122	match v.unwrap_if_lazy().unwrap() {123		Val::Bool(false) => 0,124		Val::Bool(true) => 1,125		_ => 2,126	}127}128#[no_mangle]129pub extern "C" fn jsonnet_json_extract_null(_vm: &EvaluationState, v: &Val) -> c_int {130	match v.unwrap_if_lazy().unwrap() {131		Val::Null => 1,132		_ => 0,133	}134}135136/// # Safety137///138/// This function is safe, if received v is a pointer to normal C string139#[no_mangle]140pub unsafe extern "C" fn jsonnet_json_make_string(141	_vm: &EvaluationState,142	v: *const c_char,143) -> *mut Val {144	let cstr = CStr::from_ptr(v);145	let str = cstr.to_str().unwrap();146	Box::into_raw(Box::new(Val::Str(str.into())))147}148149#[no_mangle]150pub extern "C" fn jsonnet_json_make_number(_vm: &EvaluationState, v: c_double) -> *mut Val {151	Box::into_raw(Box::new(Val::Num(v)))152}153154#[no_mangle]155pub extern "C" fn jsonnet_json_make_bool(_vm: &EvaluationState, v: c_int) -> *mut Val {156	assert!(v == 0 || v == 1);157	Box::into_raw(Box::new(Val::Bool(v == 1)))158}159160#[no_mangle]161pub extern "C" fn jsonnet_json_make_null(_vm: &EvaluationState) -> *mut Val {162	Box::into_raw(Box::new(Val::Null))163}164165#[no_mangle]166pub extern "C" fn jsonnet_json_make_array(_vm: &EvaluationState) -> *mut Val {167	Box::into_raw(Box::new(Val::Arr(Rc::new(Vec::new()))))168}169170#[no_mangle]171pub extern "C" fn jsonnet_json_array_append(_vm: &EvaluationState, arr: &mut Val, val: &Val) {172	match arr {173		Val::Arr(old) => {174			// TODO: Mutate array, instead of recreating them175			let mut new = Vec::new();176			new.extend(old.iter().cloned());177			new.push(val.clone());178			*arr = Val::Arr(Rc::new(new));179		}180		_ => panic!("should receive array"),181	}182}183184#[no_mangle]185pub extern "C" fn jsonnet_json_make_object(_vm: &EvaluationState) -> *mut Val {186	Box::into_raw(Box::new(Val::Obj(ObjValue::new_empty())))187}188189/// # Safety190///191/// This function is safe if passed name is ok192#[no_mangle]193pub unsafe extern "C" fn jsonnet_json_object_append(194	_vm: &EvaluationState,195	obj: &mut Val,196	name: *const c_char,197	val: &Val,198) {199	match obj {200		Val::Obj(old) => {201			let mut new = HashMap::new();202			new.insert(203				CStr::from_ptr(name).to_str().unwrap().into(),204				ObjMember {205					add: false,206					visibility: Visibility::Normal,207					invoke: LazyBinding::Bound(LazyVal::new_resolved(val.clone())),208					location: None,209				},210			);211			let new_obj = ObjValue::new(Some(old.clone()), Rc::new(new));212			*obj = Val::Obj(new_obj);213		}214		_ => panic!("should receive array"),215	}216}217218/// # Safety219///220/// This function is most definitely broken, but it works somehow, see TODO inside221#[no_mangle]222pub unsafe extern "C" fn jsonnet_realloc(223	_vm: &EvaluationState,224	buf: *mut u8,225	sz: usize,226) -> *mut u8 {227	if buf.is_null() {228		assert!(sz != 0);229		return std::alloc::alloc(Layout::from_size_align(sz, std::mem::align_of::<u8>()).unwrap());230	}231	// TODO: Somehow store size of allocation, because its real size is probally not 16 :D232	// OR (Alternative way of fixing this TODO)233	// TODO: Standard allocator uses malloc, and it doesn't uses allocation size,234	// TODO: so it should work in normal cases. Maybe force allocator for this library?235	let old_layout = Layout::from_size_align(16, std::mem::align_of::<u8>()).unwrap();236	if sz == 0 {237		std::alloc::dealloc(buf, old_layout);238		return std::ptr::null_mut();239	}240	std::alloc::realloc(buf, old_layout, sz)241}242243/// # Safety244#[no_mangle]245#[allow(clippy::boxed_local)]246pub unsafe extern "C" fn jsonnet_json_destroy(_vm: &EvaluationState, v: *mut Val) {247	Box::from_raw(v);248}249250#[no_mangle]251pub extern "C" fn jsonnet_import_callback() {252	todo!()253}254#[no_mangle]255pub extern "C" fn jsonnet_native_callback() {256	todo!()257}258#[no_mangle]259pub extern "C" fn jsonnet_ext_var() {260	todo!()261}262#[no_mangle]263pub extern "C" fn jsonnet_ext_code() {264	todo!()265}266#[no_mangle]267pub extern "C" fn jsonnet_tla_var() {268	todo!()269}270#[no_mangle]271pub extern "C" fn jsonnet_tla_code() {272	todo!()273}274#[no_mangle]275pub extern "C" fn jsonnet_max_trace() {276	todo!()277}278279/// # Safety280///281/// This function is safe, if received v is a pointer to normal C string282#[no_mangle]283pub unsafe extern "C" fn jsonnet_jpath_add(vm: &EvaluationState, v: *const c_char) {284	let cstr = CStr::from_ptr(v);285	let path = PathBuf::from(cstr.to_str().unwrap());286	let any_resolver = &vm.settings().import_resolver;287	let resolver = any_resolver288		.as_any()289		.downcast_ref::<NativeImportResolver>()290		.unwrap();291	resolver.add_jpath(path);292}293294/// # Safety295///296/// This function is safe, if received v is a pointer to normal C string297#[no_mangle]298pub unsafe extern "C" fn jsonnet_evaluate_file(299	vm: &EvaluationState,300	filename: *const c_char,301	error: &mut c_int,302) -> *const c_char {303	vm.run_in_state(|| {304		use std::fmt::Write;305		let filename = CStr::from_ptr(filename);306		match vm.evaluate_file_to_json(&PathBuf::from(filename.to_str().unwrap())) {307			Ok(v) => {308				*error = 0;309				CString::new(&*v as &str).unwrap().into_raw()310			}311			Err(e) => {312				*error = 1;313				let mut out = String::new();314				writeln!(out, "{:?}", e.0).unwrap();315				for i in (e.1).0.iter() {316					writeln!(out, "{:?}", i.0).unwrap();317				}318				CString::new(&out as &str).unwrap().into_raw()319			}320		}321	})322}323324/// # Safety325///326/// This function is safe, if received v is a pointer to normal C string327#[no_mangle]328pub unsafe extern "C" fn jsonnet_evaluate_snippet(329	vm: &EvaluationState,330	filename: *const c_char,331	snippet: *const c_char,332	error: &mut c_int,333) -> *const c_char {334	vm.run_in_state(|| {335		use std::fmt::Write;336		let filename = CStr::from_ptr(filename);337		let snippet = CStr::from_ptr(snippet);338		match vm.evaluate_snippet_to_json(339			&PathBuf::from(filename.to_str().unwrap()),340			&snippet.to_str().unwrap(),341		) {342			Ok(v) => {343				*error = 0;344				CString::new(&*v as &str).unwrap().into_raw()345			}346			Err(e) => {347				*error = 1;348				let mut out = String::new();349				writeln!(out, "{:?}", e.0).unwrap();350				for i in (e.1).0.iter() {351					writeln!(out, "{:?} ---- {}", i.0, i.1).unwrap();352				}353				CString::new(&out as &str).unwrap().into_raw()354			}355		}356	})357}358359#[no_mangle]360pub extern "C" fn jsonnet_evaluate_file_multi() {361	todo!()362}363#[no_mangle]364pub extern "C" fn jsonnet_evaluate_snippet_multi() {365	todo!()366}367#[no_mangle]368pub extern "C" fn jsonnet_evaluate_file_stream() {369	todo!()370}371#[no_mangle]372pub extern "C" fn jsonnet_evaluate_snippet_stream() {373	todo!()374}
addedbindings/jsonnet/src/val_extract.rsdiffbeforeafterboth
--- /dev/null
+++ b/bindings/jsonnet/src/val_extract.rs
@@ -0,0 +1,45 @@
+//! Extract values from VM
+
+use jrsonnet_evaluator::{EvaluationState, Val};
+
+use std::{
+	ffi::CString,
+	os::raw::{c_char, c_double, c_int},
+};
+
+#[no_mangle]
+pub extern "C" fn jsonnet_json_extract_string(_vm: &EvaluationState, v: &Val) -> *mut c_char {
+	match v.unwrap_if_lazy().unwrap() {
+		Val::Str(s) => CString::new(&*s as &str).unwrap().into_raw(),
+		_ => std::ptr::null_mut(),
+	}
+}
+#[no_mangle]
+pub extern "C" fn jsonnet_json_extract_number(
+	_vm: &EvaluationState,
+	v: &Val,
+	out: &mut c_double,
+) -> c_int {
+	match v.unwrap_if_lazy().unwrap() {
+		Val::Num(n) => {
+			*out = n;
+			1
+		}
+		_ => 0,
+	}
+}
+#[no_mangle]
+pub extern "C" fn jsonnet_json_extract_bool(_vm: &EvaluationState, v: &Val) -> c_int {
+	match v.unwrap_if_lazy().unwrap() {
+		Val::Bool(false) => 0,
+		Val::Bool(true) => 1,
+		_ => 2,
+	}
+}
+#[no_mangle]
+pub extern "C" fn jsonnet_json_extract_null(_vm: &EvaluationState, v: &Val) -> c_int {
+	match v.unwrap_if_lazy().unwrap() {
+		Val::Null => 1,
+		_ => 0,
+	}
+}
addedbindings/jsonnet/src/val_make.rsdiffbeforeafterboth
--- /dev/null
+++ b/bindings/jsonnet/src/val_make.rs
@@ -0,0 +1,47 @@
+//! Create values in VM
+
+use jrsonnet_evaluator::{EvaluationState, ObjValue, Val};
+use std::{
+	ffi::CStr,
+	os::raw::{c_char, c_double, c_int},
+	rc::Rc,
+};
+
+/// # Safety
+///
+/// This function is safe, if received v is a pointer to normal C string
+#[no_mangle]
+pub unsafe extern "C" fn jsonnet_json_make_string(
+	_vm: &EvaluationState,
+	v: *const c_char,
+) -> *mut Val {
+	let cstr = CStr::from_ptr(v);
+	let str = cstr.to_str().unwrap();
+	Box::into_raw(Box::new(Val::Str(str.into())))
+}
+
+#[no_mangle]
+pub extern "C" fn jsonnet_json_make_number(_vm: &EvaluationState, v: c_double) -> *mut Val {
+	Box::into_raw(Box::new(Val::Num(v)))
+}
+
+#[no_mangle]
+pub extern "C" fn jsonnet_json_make_bool(_vm: &EvaluationState, v: c_int) -> *mut Val {
+	assert!(v == 0 || v == 1);
+	Box::into_raw(Box::new(Val::Bool(v == 1)))
+}
+
+#[no_mangle]
+pub extern "C" fn jsonnet_json_make_null(_vm: &EvaluationState) -> *mut Val {
+	Box::into_raw(Box::new(Val::Null))
+}
+
+#[no_mangle]
+pub extern "C" fn jsonnet_json_make_array(_vm: &EvaluationState) -> *mut Val {
+	Box::into_raw(Box::new(Val::Arr(Rc::new(Vec::new()))))
+}
+
+#[no_mangle]
+pub extern "C" fn jsonnet_json_make_object(_vm: &EvaluationState) -> *mut Val {
+	Box::into_raw(Box::new(Val::Obj(ObjValue::new_empty())))
+}
addedbindings/jsonnet/src/val_modify.rsdiffbeforeafterboth
--- /dev/null
+++ b/bindings/jsonnet/src/val_modify.rs
@@ -0,0 +1,55 @@
+//! Modify VM values
+//! Only tested with variables, which haven't altered by code before appearing here
+//! In jrsonnet every value is immutable, and this code is probally broken
+
+use jrsonnet_evaluator::{EvaluationState, LazyBinding, LazyVal, ObjMember, ObjValue, Val};
+use jrsonnet_parser::Visibility;
+use std::{collections::HashMap, ffi::CStr, os::raw::c_char, rc::Rc};
+
+/// # Safety
+///
+/// Received arr value should be correct pointer to array allocated by make_array
+#[no_mangle]
+pub unsafe extern "C" fn jsonnet_json_array_append(
+	_vm: &EvaluationState,
+	arr: *mut Val,
+	val: &Val,
+) {
+	match *Box::from_raw(arr) {
+		Val::Arr(old) => {
+			let mut new = Rc::try_unwrap(old).expect("arr with no refs");
+			new.push(val.clone());
+			*arr = Val::Arr(Rc::new(new));
+		}
+		_ => panic!("should receive array"),
+	}
+}
+
+/// # Safety
+///
+/// This function is safe if passed name is ok
+#[no_mangle]
+pub unsafe extern "C" fn jsonnet_json_object_append(
+	_vm: &EvaluationState,
+	obj: &mut Val,
+	name: *const c_char,
+	val: &Val,
+) {
+	match obj {
+		Val::Obj(old) => {
+			let mut new = HashMap::new();
+			new.insert(
+				CStr::from_ptr(name).to_str().unwrap().into(),
+				ObjMember {
+					add: false,
+					visibility: Visibility::Normal,
+					invoke: LazyBinding::Bound(LazyVal::new_resolved(val.clone())),
+					location: None,
+				},
+			);
+			let new_obj = ObjValue::new(Some(old.clone()), Rc::new(new));
+			*obj = Val::Obj(new_obj);
+		}
+		_ => panic!("should receive object"),
+	}
+}
addedbindings/jsonnet/src/vars_tlas.rsdiffbeforeafterboth
--- /dev/null
+++ b/bindings/jsonnet/src/vars_tlas.rs
@@ -0,0 +1,64 @@
+//! Manipulate external variables and top level arguments
+
+use jrsonnet_evaluator::EvaluationState;
+use std::{ffi::CStr, os::raw::c_char};
+
+/// # Safety
+#[no_mangle]
+pub unsafe extern "C" fn jsonnet_ext_var(
+	vm: &EvaluationState,
+	name: *const c_char,
+	value: *const c_char,
+) {
+	let name = CStr::from_ptr(name);
+	let value = CStr::from_ptr(value);
+	vm.add_ext_str(
+		name.to_str().unwrap().into(),
+		value.to_str().unwrap().into(),
+	)
+}
+
+/// # Safety
+#[no_mangle]
+pub unsafe extern "C" fn jsonnet_ext_code(
+	vm: &EvaluationState,
+	name: *const c_char,
+	value: *const c_char,
+) {
+	let name = CStr::from_ptr(name);
+	let value = CStr::from_ptr(value);
+	vm.add_ext_code(
+		name.to_str().unwrap().into(),
+		value.to_str().unwrap().into(),
+	)
+	.unwrap()
+}
+/// # Safety
+#[no_mangle]
+pub unsafe extern "C" fn jsonnet_tla_var(
+	vm: &EvaluationState,
+	name: *const c_char,
+	value: *const c_char,
+) {
+	let name = CStr::from_ptr(name);
+	let value = CStr::from_ptr(value);
+	vm.add_tla_str(
+		name.to_str().unwrap().into(),
+		value.to_str().unwrap().into(),
+	)
+}
+/// # Safety
+#[no_mangle]
+pub unsafe extern "C" fn jsonnet_tla_code(
+	vm: &EvaluationState,
+	name: *const c_char,
+	value: *const c_char,
+) {
+	let name = CStr::from_ptr(name);
+	let value = CStr::from_ptr(value);
+	vm.add_tla_code(
+		name.to_str().unwrap().into(),
+		value.to_str().unwrap().into(),
+	)
+	.unwrap()
+}
deletedbindings/test.jsonnetdiffbeforeafterboth
--- a/bindings/test.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-2 + 2
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -56,10 +56,17 @@
 	StackOverflow,
 	FractionalIndex,
 	DivisionByZero,
+
+	StringManifestOutputIsNotAString,
+
+	ImportCallbackError(String),
 }
 
 #[derive(Clone, Debug)]
-pub struct StackTraceElement(pub ExprLocation, pub String);
+pub struct StackTraceElement {
+	pub location: ExprLocation,
+	pub desc: String,
+}
 #[derive(Debug, Clone)]
 pub struct StackTrace(pub Vec<StackTraceElement>);