--- /dev/null +++ b/bindings/jrsonnet-web/.gitignore @@ -0,0 +1 @@ +/lib --- /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"] --- /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" + } +} --- /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" + ] + } +} --- /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"); +}); --- /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); +}); --- /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(); + response = new Map(); + + async resolveFrom(from: string | undefined, path: string): Promise { + 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 { + 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); +} --- /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 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, +} + +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(&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 { + 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 { + 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) -> Self { + Self::new(Val::arr( + items.into_iter().map(|v| v.val).collect::>(), + )) + } + pub fn func( + params: Vec, + + #[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 { + self.val.as_bool() + } + pub fn as_num(&self) -> Option { + self.val.as_num() + } + pub fn as_string(&self) -> Option { + self.val.as_str().map(|s| s.to_string()) + } + pub fn arr_len(&self) -> Option { + self.val.as_arr().map(|a| a.len()) + } + pub fn arr_at(&self, index: u32) -> Result, 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> { + self.val + .as_obj() + .map(|o| o.fields().into_iter().map(|s| s.to_string()).collect()) + } + pub fn obj_get(&self, key: String) -> Result, 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 { + self.manifest_with(JsonFormat::cli(indent as usize)) + } + pub fn manifest_to_string(&self) -> Result { + self.manifest_with(ToStringFormat) + } + pub fn manifest_string(&self) -> Result { + self.manifest_with(StringFormat) + } + pub fn manifest_yaml(&self, indent: u32, quote_keys: bool) -> Result { + 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 { + 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 { + self.manifest_with(XmlJsonmlFormat::std_to_xml()) + } + pub fn manifest_toml(&self, indent: u32) -> Result { + self.manifest_with(TomlFormat::std_to_toml(" ".repeat(indent as usize))) + } + pub fn manifest_ini(&self) -> Result { + 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; +} + +impl NativeCallbackHandler for JsHandler { + fn call(&self, args: &[Val]) -> JrResult { + 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::() + .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 { + 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 { + 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, + path: &str, + ) -> Result; + + #[wasm_bindgen(catch, method, structural, js_name = loadFileContents)] + fn load_file_contents( + this: &ImportResolverJs, + resolved: &str, + ) -> Result; +} + +#[wasm_bindgen(typescript_custom_section)] +const TS_IMPORT_RESOLVER: &'static str = r" +export interface ImportResolver { + resolveFrom(from: string | undefined, path: string): Promise; + loadFileContents(resolved: string): Promise; +} +"; + +#[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 { + 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, 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::() + .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 { + match jrsonnet_formatter::format(src, &opts.build()) { + Ok(v) => Ok(v), + Err(e) => { + let e = e.build(); + Err(hi_doc::source_to_ansi(&e)) + } + } +} --- 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 - */ - 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); - } -})(); --- 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 =