1const fs = require('fs');2const path = require('path');3const { WASI } = require('wasi');4const wasi = new WASI({5 args: process.argv,6 env: process.env,7 preopens: {},8});910class JsonnetVM {11 constructor(wasm, vm) {12 this.wasm = wasm;13 this.vm = vm;14 this.wasm.exports.jrsonnet_set_trace_format(this.vm, 1);1516 this.setImportCallback((from, to) => {17 const resolved = path.resolve(from, to);18 return {19 value: fs.readFileSync(resolved).toString('utf-8'),20 foundHere: resolved,21 };22 })23 }2425 /**26 * @param {(from: string, to: string) => {foundHere: string, value: string}} cb27 */28 setImportCallback(cb) {29 this.wasm.importCbs.set(this.vm, (base, rel, foundHere, success) => {30 const baseStr = this.wasm.readString(base);31 const relStr = this.wasm.readString(rel);32 try {33 const value = cb(baseStr, relStr);34 this.wasm.memorySlice32Len(foundHere, 1)[0] = this.allocateString(value.foundHere);35 this.wasm.memorySlice32Len(success, 1)[0] = 1;36 return this.allocateString(value.value);37 } catch (e) {38 this.wasm.memorySlice32Len(success, 1)[0] = 0;39 return this.allocateString(e.stack)40 }41 });42 this.wasm.exports.jrsonnet_apply_static_import_callback(43 this.vm,44 this.vm,45 );46 }4748 alloc(length) {49 return this.wasm.exports.jsonnet_realloc(this.vm, 0, length);50 }51 allocateString(string) {52 const byteLength = new TextEncoder().encode(string).length;53 const addr = this.alloc(byteLength + 1);54 this.wasm.writeString(addr, string);55 return addr;56 }57 dealloc(addr) {58 return this.wasm.exports.jsonnet_realloc(this.vm, addr, 0);59 }6061 evaluateFile(path) {62 const pathAddr = this.allocateString(path);63 const resultCodeAddr = this.alloc(4);64 const resultAddr = this.wasm.exports.jsonnet_evaluate_file(this.vm, pathAddr, resultCodeAddr);65 this.dealloc(pathAddr);66 const resultCode = this.wasm.memorySliceLen(resultCodeAddr, 4);67 this.dealloc(resultCodeAddr);68 const result = this.wasm.readString(resultAddr).trim();69 this.dealloc(resultAddr);70 if (resultCode[0] === 1) {71 const error = new Error(result);72 throw error;73 } else {74 return result;75 }76 }77 evaluateSnippet(path, snippet) {78 const pathAddr = this.allocateString(path);79 const snippetAddr = this.allocateString(snippet);80 const resultCodeAddr = this.alloc(4);81 const resultAddr = this.wasm.exports.jsonnet_evaluate_snippet(this.vm, pathAddr, snippetAddr, resultCodeAddr);82 this.dealloc(pathAddr);83 this.dealloc(snippetAddr);84 const resultCode = this.wasm.memorySliceLen(resultCodeAddr, 4);85 this.dealloc(resultCodeAddr);86 const result = this.wasm.readString(resultAddr);87 this.dealloc(resultAddr);88 if (resultCode[0] === 1) {89 const error = new Error(result);90 throw error;91 } else {92 return result;93 }94 }9596 /**97 * Destroys vm, any future call to this object will fail, and all resources will be freed98 */99 destroy() {100 this.wasm.exports.jsonnet_destroy(this.vm);101 this.wasm.importCbs.delete(this.vm);102 }103}104105class JsonnetWASM {106 constructor() {107 this.importCbs = new Map();108 }109110 async init(buf) {111 const wasm = await WebAssembly.compile(buf);112 const instance = await WebAssembly.instantiate(wasm, {113 wasi_snapshot_preview1: wasi.wasiImport,114 env: {115 _jrsonnet_static_import_callback: (ctx, base, rel, found_here, success) => {116 if (!this.importCbs.has(ctx)) {117 throw new Error(`Got unknown ctx callback: ${ctx}`);118 }119 return this.importCbs.get(ctx)(base, rel, found_here, success);120 }121 }122 });123 wasi.start(instance);124 this.instance = instance;125 }126 /**127 * @type Record<string, WebAssembly.ExportValue>128 */129 get exports() {130 return this.instance.exports;131 }132 get memory() {133 return this.exports.memory;134 }135 get memoryBuffer() {136 return this.memory.buffer;137 }138 memorySliceLen(start, length) {139 return new Uint8Array(this.memoryBuffer, start, length);140 }141 memorySlice32Len(start, length) {142 return new Uint32Array(this.memoryBuffer, start, length);143 }144 memorySlice(start, end) {145 return new Uint8Array(this.memoryBuffer, start, start && end && (end - start));146 }147148 readString(addr) {149 let end;150 let slice = this.memorySlice();151 for (end = addr; slice[end]; end++);152 return (new TextDecoder()).decode(this.memorySlice(addr, end));153 }154 writeString(addr, string) {155 let slice = this.memorySlice(addr);156 let result = new TextEncoder().encodeInto(string, slice);157 slice[result.written] = 0;158 }159160 version() {161 return this.readString(this.exports.jsonnet_version());162 }163164 newVM() {165 return new JsonnetVM(this, this.exports.jsonnet_make());166 }167}168169(async () => {170 try {171 const jsonnet = new JsonnetWASM();172 await jsonnet.init(fs.readFileSync(`${__dirname}/../../target/wasm32-wasi/release/jsonnet.wasm`));173 console.log(`Version = ${jsonnet.version()}`);174175 const vm = jsonnet.newVM();176 console.log(vm.evaluateSnippet('./snip.jsonnet', `177 2+2178 `));179 console.log(vm.evaluateFile('./test.jsonnet'));180 } catch (e) {181 console.log(e.stack);182 }183})();