difftreelog
feat proper js wasm bindings
in: master
12 files changed
bindings/jrsonnet-web/.gitignorediffbeforeafterboth--- /dev/null
+++ b/bindings/jrsonnet-web/.gitignore
@@ -0,0 +1 @@
+/lib
bindings/jrsonnet-web/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/bindings/jrsonnet-web/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "jrsonnet-web"
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+version.workspace = true
+
+[dependencies]
+console_error_panic_hook.workspace = true
+getrandom = { workspace = true, features = ["wasm_js"] }
+hi-doc.workspace = true
+jrsonnet-evaluator.workspace = true
+jrsonnet-formatter.workspace = true
+jrsonnet-gcmodule.workspace = true
+jrsonnet-stdlib.workspace = true
+jrsonnet-types.workspace = true
+js-sys.workspace = true
+url.workspace = true
+wasm-bindgen.workspace = true
+wasm-bindgen-futures.workspace = true
+
+[lints]
+workspace = true
+
+[lib]
+name = "jsonnet_web"
+crate-type = ["cdylib"]
bindings/jrsonnet-web/deno.jsondiffbeforeafterboth1{2 "name": "@jrsonnet/jrsonnet",3 "tasks": {4 "wasmbuild": "deno run -A @deno/wasmbuild -p jrsonnet-web --skip-opt"5 },6 "imports": {7 "@deno/wasmbuild": "jsr:@deno/wasmbuild@^0.21.1",8 "@std/assert": "jsr:@std/assert@^1.0.19"9 },10 "exports": {11 ".": "./mod.ts"12 }13}bindings/jrsonnet-web/deno.lockdiffbeforeafterboth--- /dev/null
+++ b/bindings/jrsonnet-web/deno.lock
@@ -0,0 +1,92 @@
+{
+ "version": "5",
+ "specifiers": {
+ "jsr:@david/path@0.2": "0.2.0",
+ "jsr:@david/temp@~0.1.1": "0.1.1",
+ "jsr:@deno/wasmbuild@~0.21.1": "0.21.1",
+ "jsr:@std/assert@^1.0.19": "1.0.19",
+ "jsr:@std/cli@^1.0.11": "1.0.29",
+ "jsr:@std/encoding@^1.0.6": "1.0.10",
+ "jsr:@std/fmt@^1.0.4": "1.0.10",
+ "jsr:@std/fs@1": "1.0.23",
+ "jsr:@std/fs@^1.0.10": "1.0.23",
+ "jsr:@std/internal@^1.0.12": "1.0.13",
+ "jsr:@std/path@1": "1.1.4",
+ "jsr:@std/path@^1.1.4": "1.1.4",
+ "jsr:@std/streams@^1.0.17": "1.1.0",
+ "jsr:@std/tar@~0.1.4": "0.1.10"
+ },
+ "jsr": {
+ "@david/path@0.2.0": {
+ "integrity": "f2d7aa7f02ce5a55e27c09f9f1381794acb09d328f8d3c8a2e3ab3ffc294dccd",
+ "dependencies": [
+ "jsr:@std/fs@1",
+ "jsr:@std/path@1"
+ ]
+ },
+ "@david/temp@0.1.1": {
+ "integrity": "3974e3aa76536bd831294b72f5b70bfb67af3f4119ae334360e321f6ded18840",
+ "dependencies": [
+ "jsr:@david/path"
+ ]
+ },
+ "@deno/wasmbuild@0.21.1": {
+ "integrity": "f60afdffefbd61067cb8a0083cde7d22f5eb74084b074c672a87b8e92042b456",
+ "dependencies": [
+ "jsr:@david/path",
+ "jsr:@david/temp",
+ "jsr:@std/cli",
+ "jsr:@std/encoding",
+ "jsr:@std/fmt",
+ "jsr:@std/fs@^1.0.10",
+ "jsr:@std/tar"
+ ]
+ },
+ "@std/assert@1.0.19": {
+ "integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e",
+ "dependencies": [
+ "jsr:@std/internal"
+ ]
+ },
+ "@std/cli@1.0.29": {
+ "integrity": "fa4ef29130baa834d8a13b7d138240c3a2fcfba740bfb7afa646a360a15ec84f"
+ },
+ "@std/encoding@1.0.10": {
+ "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1"
+ },
+ "@std/fmt@1.0.10": {
+ "integrity": "90dfba288802ac6de82fb31d0917eb9e4450b9925b954d5e51fc29ac07419db5"
+ },
+ "@std/fs@1.0.23": {
+ "integrity": "3ecbae4ce4fee03b180fa710caff36bb5adb66631c46a6460aaad49515565a37",
+ "dependencies": [
+ "jsr:@std/internal",
+ "jsr:@std/path@^1.1.4"
+ ]
+ },
+ "@std/internal@1.0.13": {
+ "integrity": "2f9546691d4ac2d32859c82dff284aaeac980ddeca38430d07941e7e288725c0"
+ },
+ "@std/path@1.1.4": {
+ "integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5",
+ "dependencies": [
+ "jsr:@std/internal"
+ ]
+ },
+ "@std/streams@1.1.0": {
+ "integrity": "2f7024d841f343fd478afe0c958a3f0f068ef2a0d2bcc954f550f97ac1fa22e3"
+ },
+ "@std/tar@0.1.10": {
+ "integrity": "6bf907f3a4bc8bfef42973ba132d946756a6161ef6b914a9e1c06debe664db17",
+ "dependencies": [
+ "jsr:@std/streams"
+ ]
+ }
+ },
+ "workspace": {
+ "dependencies": [
+ "jsr:@deno/wasmbuild@~0.21.1",
+ "jsr:@std/assert@^1.0.19"
+ ]
+ }
+}
bindings/jrsonnet-web/example.jsonnetdiffbeforeafterbothno changes
bindings/jrsonnet-web/fmt.test.tsdiffbeforeafterboth--- /dev/null
+++ b/bindings/jrsonnet-web/fmt.test.ts
@@ -0,0 +1,7 @@
+import { assertEquals } from "@std/assert";
+import { format, FormatOptions } from "./mod.ts";
+
+Deno.test("format", () => {
+ const opts = new FormatOptions();
+ assertEquals(format("{a:1+1}", opts), "{ a: 1 + 1 }\n");
+});
bindings/jrsonnet-web/fmt.tsdiffbeforeafterbothno changes
bindings/jrsonnet-web/mod.test.tsdiffbeforeafterboth--- /dev/null
+++ b/bindings/jrsonnet-web/mod.test.ts
@@ -0,0 +1,8 @@
+import { assertEquals } from "@std/assert";
+import { WasmState } from "./mod.ts";
+
+Deno.test("basic", () => {
+ const state = new WasmState();
+
+ assertEquals(state.evaluate_snippet("test.jsonnet", "1 + 2").as_num(), 3);
+});
bindings/jrsonnet-web/mod.tsdiffbeforeafterboth--- /dev/null
+++ b/bindings/jrsonnet-web/mod.ts
@@ -0,0 +1,60 @@
+import { assert } from "@std/assert";
+import {
+ format as formatRaw,
+ type ImportResolver,
+ WasmFormatOptions,
+ WasmState,
+ WasmVal,
+} from "./lib/jsonnet_web.js";
+
+export { type ImportResolver, WasmFormatOptions, WasmState, WasmVal };
+
+class FetchImportResolver implements ImportResolver {
+ constructor(public base: string) {}
+
+ resolution = new Map<string, URL>();
+ response = new Map<string, Response>();
+
+ async resolveFrom(from: string | undefined, path: string): Promise<string> {
+ let resolved: URL;
+ if (from) {
+ resolved = new URL(path, from);
+ } else {
+ resolved = new URL(path, this.base);
+ }
+ const resolvingStr = resolved.toString();
+ resolved = this.resolution.get(resolvingStr) ?? resolved;
+
+ const resolvedStr = resolved.toString();
+ if (!this.response.has(resolvedStr)) {
+ console.log(resolved);
+ const v = await fetch(resolved);
+ this.response.set(resolvedStr, v);
+ resolved = new URL(v.url);
+ this.resolution.set(resolvingStr, resolved);
+ }
+ return resolved.toString();
+ }
+ loadFileContents(resolved: string): Promise<Uint8Array> {
+ console.log(resolved);
+ const v = this.response.get(resolved);
+ assert(v, "should be resolved");
+ return v.bytes();
+ }
+}
+
+//
+// try {
+// console.log("eval file");
+// await state.evaluate_file("example.jsonnet");
+// console.log("eval file done");
+// } catch (e) {
+// console.log(e);
+// }
+//
+export function format(
+ code: string,
+ opts: WasmFormatOptions = new WasmFormatOptions(),
+): string {
+ return formatRaw(code, opts);
+}
bindings/jrsonnet-web/src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/bindings/jrsonnet-web/src/lib.rs
@@ -0,0 +1,421 @@
+use std::result::Result;
+
+use jrsonnet_evaluator::{
+ NumValue, Result as JrResult, SourcePath, SourceUrl, State, StateBuilder, Val,
+ async_import::{ResolvedImportResolver, async_import},
+ error,
+ function::builtin::{NativeCallback, NativeCallbackHandler},
+ manifest::{JsonFormat, ManifestFormat, StringFormat, ToStringFormat, YamlStreamFormat},
+ trace::{JsFormat, PathResolver, TraceFormat},
+ with_state,
+};
+use jrsonnet_formatter::FormatOptions;
+use jrsonnet_gcmodule::Trace;
+use jrsonnet_stdlib::{IniFormat, TomlFormat, XmlJsonmlFormat, YamlFormat};
+use jrsonnet_types::ValType;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+#[derive(Clone, Copy)]
+pub enum ValKind {
+ Null,
+ Bool,
+ Num,
+ Str,
+ Arr,
+ Obj,
+ Func,
+}
+
+#[wasm_bindgen(inline_js = r"
+export class JrsonnetError extends Error {
+ constructor(message, frames) {
+ super(message);
+ this.name = 'JrsonnetError';
+ this.frames = frames;
+ }
+}
+export function makeJrsonnetError(message, frames) {
+ return new JrsonnetError(message, frames);
+}
+")]
+extern "C" {
+ #[wasm_bindgen(js_name = makeJrsonnetError)]
+ fn make_jrsonnet_error(message: &str, frames: js_sys::Array) -> JsValue;
+}
+
+#[wasm_bindgen(typescript_custom_section)]
+const TS_JRSONNET_ERROR: &'static str = r"
+export interface JrsonnetFrame {
+ desc: string;
+ path?: string;
+ line?: number;
+ column?: number;
+}
+export class JrsonnetError extends Error {
+ name: 'JrsonnetError';
+ frames: JrsonnetFrame[];
+}
+";
+
+fn jrsonnet_js_error(e: &jrsonnet_evaluator::Error) -> JsValue {
+ let msg = e.error().to_string();
+ // let msg = format.format(e).unwrap_or_else(|_| e.to_string());
+ let frames = js_sys::Array::new();
+ for el in &e.trace().0 {
+ let frame = js_sys::Object::new();
+ let _ = js_sys::Reflect::set(
+ &frame,
+ &JsValue::from_str("desc"),
+ &JsValue::from_str(&el.desc),
+ );
+ if let Some(loc) = &el.location {
+ let path = loc.0.source_path().to_string();
+ let _ = js_sys::Reflect::set(
+ &frame,
+ &JsValue::from_str("path"),
+ &JsValue::from_str(&path),
+ );
+ let mapped = loc.0.map_source_locations(&[loc.1, loc.2]);
+ let _ = js_sys::Reflect::set(
+ &frame,
+ &JsValue::from_str("line"),
+ &JsValue::from(mapped[0].line),
+ );
+ let _ = js_sys::Reflect::set(
+ &frame,
+ &JsValue::from_str("column"),
+ &JsValue::from(mapped[0].column),
+ );
+ }
+ frames.push(&frame);
+ }
+ make_jrsonnet_error(&msg, frames)
+}
+
+impl From<ValType> for ValKind {
+ fn from(v: ValType) -> Self {
+ match v {
+ ValType::Null => Self::Null,
+ ValType::Bool => Self::Bool,
+ ValType::Num => Self::Num,
+ ValType::Str => Self::Str,
+ ValType::Arr => Self::Arr,
+ ValType::Obj => Self::Obj,
+ ValType::Func => Self::Func,
+ }
+ }
+}
+
+#[wasm_bindgen]
+pub struct WasmVal {
+ val: Val,
+ state: Option<State>,
+}
+
+impl WasmVal {
+ fn new(val: Val) -> Self {
+ Self { val, state: None }
+ }
+ fn with_state(val: Val, state: State) -> Self {
+ Self {
+ val,
+ state: Some(state),
+ }
+ }
+ fn child(&self, val: Val) -> Self {
+ Self {
+ val,
+ state: self.state.clone(),
+ }
+ }
+ fn run<R>(&self, f: impl FnOnce(&Val) -> R) -> R {
+ if let Some(state) = &self.state {
+ let _guard = state.try_enter();
+ f(&self.val)
+ } else {
+ f(&self.val)
+ }
+ }
+ fn manifest_with(&self, format: impl ManifestFormat) -> Result<String, JsValue> {
+ self.run(|v| v.manifest(format))
+ .map_err(|e| jrsonnet_js_error(&e))
+ }
+}
+
+#[wasm_bindgen]
+impl WasmVal {
+ pub fn null() -> Self {
+ Self::new(Val::Null)
+ }
+ pub fn bool(b: bool) -> Self {
+ Self::new(Val::Bool(b))
+ }
+ pub fn num(n: f64) -> Result<Self, JsError> {
+ let n = NumValue::new(n)
+ .ok_or_else(|| JsError::new("only finite numbers are supported by jsonnet"))?;
+ Ok(Self::new(Val::num(n)))
+ }
+ pub fn string(s: String) -> Self {
+ Self::new(Val::string(s))
+ }
+ pub fn arr(items: Vec<WasmVal>) -> Self {
+ Self::new(Val::arr(
+ items.into_iter().map(|v| v.val).collect::<Vec<_>>(),
+ ))
+ }
+ pub fn func(
+ params: Vec<String>,
+
+ #[wasm_bindgen(unchecked_param_type = "(...args: WasmVal[]) => WasmVal")]
+ callback: js_sys::Function,
+ ) -> Self {
+ #[allow(deprecated)]
+ Self::new(Val::function(NativeCallback::new(
+ params,
+ JsHandler { func: callback },
+ )))
+ }
+
+ #[wasm_bindgen(getter)]
+ pub fn kind(&self) -> ValKind {
+ self.val.value_type().into()
+ }
+ pub fn as_bool(&self) -> Option<bool> {
+ self.val.as_bool()
+ }
+ pub fn as_num(&self) -> Option<f64> {
+ self.val.as_num()
+ }
+ pub fn as_string(&self) -> Option<String> {
+ self.val.as_str().map(|s| s.to_string())
+ }
+ pub fn arr_len(&self) -> Option<u32> {
+ self.val.as_arr().map(|a| a.len())
+ }
+ pub fn arr_at(&self, index: u32) -> Result<Option<WasmVal>, JsValue> {
+ let Some(a) = self.val.as_arr() else {
+ return Ok(None);
+ };
+ self.run(|_| a.get(index))
+ .map(|opt| opt.map(|v| self.child(v)))
+ .map_err(|e| jrsonnet_js_error(&e))
+ }
+ pub fn obj_keys(&self) -> Option<Vec<String>> {
+ self.val
+ .as_obj()
+ .map(|o| o.fields().into_iter().map(|s| s.to_string()).collect())
+ }
+ pub fn obj_get(&self, key: String) -> Result<Option<WasmVal>, JsValue> {
+ let Some(o) = self.val.as_obj() else {
+ return Ok(None);
+ };
+ self.run(|_| o.get(key.into()))
+ .map(|opt| opt.map(|v| self.child(v)))
+ .map_err(|e| jrsonnet_js_error(&e))
+ }
+
+ pub fn manifest_json(&self, indent: u32) -> Result<String, JsValue> {
+ self.manifest_with(JsonFormat::cli(indent as usize))
+ }
+ pub fn manifest_to_string(&self) -> Result<String, JsValue> {
+ self.manifest_with(ToStringFormat)
+ }
+ pub fn manifest_string(&self) -> Result<String, JsValue> {
+ self.manifest_with(StringFormat)
+ }
+ pub fn manifest_yaml(&self, indent: u32, quote_keys: bool) -> Result<String, JsValue> {
+ self.manifest_with(YamlFormat::std_to_yaml(indent != 0, quote_keys))
+ }
+ pub fn manifest_yaml_stream(
+ &self,
+ indent: u32,
+ quote_keys: bool,
+ c_document_end: bool,
+ ) -> Result<String, JsValue> {
+ self.manifest_with(YamlStreamFormat::std_yaml_stream(
+ YamlFormat::std_to_yaml(indent != 0, quote_keys),
+ c_document_end,
+ ))
+ }
+ pub fn manifest_xml_jsonml(&self) -> Result<String, JsValue> {
+ self.manifest_with(XmlJsonmlFormat::std_to_xml())
+ }
+ pub fn manifest_toml(&self, indent: u32) -> Result<String, JsValue> {
+ self.manifest_with(TomlFormat::std_to_toml(" ".repeat(indent as usize)))
+ }
+ pub fn manifest_ini(&self) -> Result<String, JsValue> {
+ self.manifest_with(IniFormat::std())
+ }
+}
+
+#[derive(Trace)]
+struct JsHandler {
+ #[trace(skip)]
+ func: js_sys::Function,
+}
+
+#[wasm_bindgen(inline_js = r"
+export function js_invoke_val_callback(cb, args) {
+ return cb.apply(null, args);
+}
+")]
+extern "C" {
+ #[wasm_bindgen(catch)]
+ fn js_invoke_val_callback(
+ cb: &js_sys::Function,
+ args: &js_sys::Array,
+ ) -> Result<WasmVal, JsValue>;
+}
+
+impl NativeCallbackHandler for JsHandler {
+ fn call(&self, args: &[Val]) -> JrResult<Val> {
+ let js_args = js_sys::Array::new();
+ let state = with_state(|s| s);
+ for arg in args {
+ js_args.push(&JsValue::from(WasmVal::with_state(
+ arg.clone(),
+ state.clone(),
+ )));
+ }
+ let result = js_invoke_val_callback(&self.func, &js_args).map_err(|e| {
+ let msg = e
+ .as_string()
+ .or_else(|| {
+ e.dyn_ref::<js_sys::Error>()
+ .map(|err| String::from(err.message()))
+ })
+ .unwrap_or_else(|| format!("{e:?}"));
+ error!("js callback threw: {msg}")
+ })?;
+ Ok(result.val)
+ }
+}
+
+#[wasm_bindgen]
+pub struct WasmState {
+ state: State,
+ resolver: JsAsyncResolver,
+}
+#[wasm_bindgen]
+impl WasmState {
+ #[wasm_bindgen(constructor)]
+ pub fn new(resolver: ImportResolverJs) -> Self {
+ console_error_panic_hook::set_once();
+ let mut state = StateBuilder::default();
+ state.import_resolver(ResolvedImportResolver::new());
+ let std = jrsonnet_stdlib::ContextInitializer::new(PathResolver::Absolute);
+ state.context_initializer(std);
+ let state = state.build();
+ Self {
+ state,
+ resolver: JsAsyncResolver { js: resolver },
+ }
+ }
+
+ #[wasm_bindgen]
+ pub fn evaluate_snippet(&self, name: &str, snippet: &str) -> Result<WasmVal, JsValue> {
+ let _guard = self.state.enter();
+ self.state
+ .evaluate_snippet(name, snippet)
+ .map(|v| WasmVal::with_state(v, self.state.clone()))
+ .map_err(|e| jrsonnet_js_error(&e))
+ }
+
+ pub async fn evaluate_file(&self, path: String) -> Result<WasmVal, JsValue> {
+ let path = async_import(self.state.clone(), self.resolver.clone(), &path.as_str()).await?;
+ let _guard = self.state.enter();
+ self.state
+ .import_resolved(path)
+ .map(|v| WasmVal::with_state(v, self.state.clone()))
+ .map_err(|e| jrsonnet_js_error(&e))
+ }
+}
+
+#[wasm_bindgen]
+extern "C" {
+ #[wasm_bindgen(typescript_type = "ImportResolver")]
+ #[derive(Clone)]
+ pub type ImportResolverJs;
+
+ #[wasm_bindgen(catch, method, structural, js_name = resolveFrom)]
+ fn resolve_from(
+ this: &ImportResolverJs,
+ from: Option<String>,
+ path: &str,
+ ) -> Result<js_sys::Promise, JsValue>;
+
+ #[wasm_bindgen(catch, method, structural, js_name = loadFileContents)]
+ fn load_file_contents(
+ this: &ImportResolverJs,
+ resolved: &str,
+ ) -> Result<js_sys::Promise, JsValue>;
+}
+
+#[wasm_bindgen(typescript_custom_section)]
+const TS_IMPORT_RESOLVER: &'static str = r"
+export interface ImportResolver {
+ resolveFrom(from: string | undefined, path: string): Promise<string>;
+ loadFileContents(resolved: string): Promise<Uint8Array>;
+}
+";
+
+#[derive(Clone)]
+struct JsAsyncResolver {
+ js: ImportResolverJs,
+}
+
+impl jrsonnet_evaluator::async_import::AsyncImportResolver for JsAsyncResolver {
+ type Error = JsValue;
+
+ async fn resolve_from(
+ &self,
+ from: &SourcePath,
+ path: &dyn jrsonnet_evaluator::AsPathLike,
+ ) -> Result<SourcePath, JsValue> {
+ let from_js = (!from.is_default()).then(|| from.to_string());
+ let path_str = path.as_path().as_ref().to_string_lossy().into_owned();
+ let promise = self.js.resolve_from(from_js, &path_str)?;
+ let resolved_js = wasm_bindgen_futures::JsFuture::from(promise).await?;
+ let resolved_str = resolved_js
+ .as_string()
+ .ok_or_else(|| JsValue::from_str("resolveFrom must return string"))?;
+ let url = url::Url::parse(&resolved_str).map_err(|e| JsValue::from_str(&e.to_string()))?;
+ Ok(SourcePath::new(SourceUrl::new(url)))
+ }
+
+ async fn load_file_contents(&self, resolved: &SourcePath) -> Result<Vec<u8>, JsValue> {
+ let resolved_str = resolved.to_string();
+ let promise = self.js.load_file_contents(&resolved_str)?;
+ let bytes_js = wasm_bindgen_futures::JsFuture::from(promise).await?;
+ let arr = bytes_js
+ .dyn_into::<js_sys::Uint8Array>()
+ .map_err(|_| JsValue::from_str("loadFileContents must return Uint8Array"))?;
+ Ok(arr.to_vec())
+ }
+}
+
+#[wasm_bindgen]
+pub struct WasmFormatOptions {}
+#[wasm_bindgen]
+impl WasmFormatOptions {
+ #[wasm_bindgen(constructor)]
+ pub fn new() -> Self {
+ Self {}
+ }
+
+ fn build(&self) -> FormatOptions {
+ FormatOptions { indent: 0 }
+ }
+}
+
+#[wasm_bindgen]
+pub fn format(src: &str, opts: &WasmFormatOptions) -> Result<String, String> {
+ match jrsonnet_formatter::format(src, &opts.build()) {
+ Ok(v) => Ok(v),
+ Err(e) => {
+ let e = e.build();
+ Err(hi_doc::source_to_ansi(&e))
+ }
+ }
+}
bindings/js/index.jsdiffbeforeafterboth--- a/bindings/js/index.js
+++ /dev/null
@@ -1,183 +0,0 @@
-const fs = require('fs');
-const path = require('path');
-const { WASI } = require('wasi');
-const wasi = new WASI({
- args: process.argv,
- env: process.env,
- preopens: {},
-});
-
-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) {
- return this.wasm.exports.jsonnet_realloc(this.vm, 0, length);
- }
- allocateString(string) {
- const byteLength = new TextEncoder().encode(string).length;
- const addr = this.alloc(byteLength + 1);
- this.wasm.writeString(addr, string);
- return addr;
- }
- dealloc(addr) {
- return this.wasm.exports.jsonnet_realloc(this.vm, addr, 0);
- }
-
- evaluateFile(path) {
- const pathAddr = this.allocateString(path);
- const resultCodeAddr = this.alloc(4);
- const resultAddr = this.wasm.exports.jsonnet_evaluate_file(this.vm, pathAddr, resultCodeAddr);
- this.dealloc(pathAddr);
- const resultCode = this.wasm.memorySliceLen(resultCodeAddr, 4);
- this.dealloc(resultCodeAddr);
- const result = this.wasm.readString(resultAddr).trim();
- this.dealloc(resultAddr);
- if (resultCode[0] === 1) {
- const error = new Error(result);
- throw error;
- } else {
- return result;
- }
- }
- evaluateSnippet(path, snippet) {
- const pathAddr = this.allocateString(path);
- const snippetAddr = this.allocateString(snippet);
- const resultCodeAddr = this.alloc(4);
- const resultAddr = this.wasm.exports.jsonnet_evaluate_snippet(this.vm, pathAddr, snippetAddr, resultCodeAddr);
- this.dealloc(pathAddr);
- this.dealloc(snippetAddr);
- const resultCode = this.wasm.memorySliceLen(resultCodeAddr, 4);
- this.dealloc(resultCodeAddr);
- const result = this.wasm.readString(resultAddr);
- this.dealloc(resultAddr);
- if (resultCode[0] === 1) {
- const error = new Error(result);
- throw error;
- } else {
- return result;
- }
- }
-
- /**
- * 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() {
- this.importCbs = new Map();
- }
-
- async init(buf) {
- const wasm = await WebAssembly.compile(buf);
- 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;
- }
- get memory() {
- return this.exports.memory;
- }
- get memoryBuffer() {
- return this.memory.buffer;
- }
- 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));
- }
-
- readString(addr) {
- let end;
- let slice = this.memorySlice();
- for (end = addr; slice[end]; end++);
- return (new TextDecoder()).decode(this.memorySlice(addr, end));
- }
- writeString(addr, string) {
- let slice = this.memorySlice(addr);
- let result = new TextEncoder().encodeInto(string, slice);
- slice[result.written] = 0;
- }
-
- version() {
- return this.readString(this.exports.jsonnet_version());
- }
-
- newVM() {
- return new JsonnetVM(this, this.exports.jsonnet_make());
- }
-}
-
-(async () => {
- try {
- const jsonnet = new JsonnetWASM();
- 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', `
- 2+2
- `));
- console.log(vm.evaluateFile('./test.jsonnet'));
- } catch (e) {
- console.log(e.stack);
- }
-})();
flake.nixdiffbeforeafterboth--- a/flake.nix
+++ b/flake.nix
@@ -70,6 +70,7 @@
])
rustfmt
rust-analyzer
+ pkgs.fenix.targets.wasm32-unknown-unknown.stable.rust-std
];
craneLib = (inputs.crane.mkLib pkgs).overrideToolchain toolchain;
treefmt =