git.delta.rocks / jrsonnet / refs/commits / 9cb049ebb4a6

difftreelog

feat proper js wasm bindings

nlwklwxtYaroslav Bolyukin2026-05-05parent: #b9cb94a.patch.diff
in: master

12 files changed

addedbindings/jrsonnet-web/.gitignorediffbeforeafterboth
--- /dev/null
+++ b/bindings/jrsonnet-web/.gitignore
@@ -0,0 +1 @@
+/lib
addedbindings/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"]
addedbindings/jrsonnet-web/deno.jsondiffbeforeafterboth
--- /dev/null
+++ b/bindings/jrsonnet-web/deno.json
@@ -0,0 +1,13 @@
+{
+	"name": "@jrsonnet/jrsonnet",
+	"tasks": {
+		"wasmbuild": "deno run -A @deno/wasmbuild -p jrsonnet-web --skip-opt"
+	},
+	"imports": {
+		"@deno/wasmbuild": "jsr:@deno/wasmbuild@^0.21.1",
+		"@std/assert": "jsr:@std/assert@^1.0.19"
+	},
+	"exports": {
+		".": "./mod.ts"
+	}
+}
addedbindings/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"
+    ]
+  }
+}
addedbindings/jrsonnet-web/example.jsonnetdiffbeforeafterboth

no changes

addedbindings/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");
+});
addedbindings/jrsonnet-web/fmt.tsdiffbeforeafterboth

no changes

addedbindings/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);
+});
addedbindings/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);
+}
addedbindings/jrsonnet-web/src/lib.rsdiffbeforeafterboth

no changes

deletedbindings/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);
-	}
-})();
modifiedflake.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 =