Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move jsonnet to wasm #366

Merged
merged 6 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions JS/jsonnet/src/jsonnet.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ if (!isArakoo) {
return this.vm;
}

#setFunc(name, func) {
__jsonnet_func_map[name] = func;
}

evaluateSnippet(snippet) {
let vm = this.#getVm();
return __jsonnet_evaluate_snippet(vm, snippet);
Expand All @@ -79,6 +83,42 @@ if (!isArakoo) {
let vm = this.#getVm();
return __jsonnet_evaluate_file(vm, filename);
}

javascriptCallback(name, func) {
let numOfArgs = func.length;
console.debug("Constructor name is: ", func.constructor.name);
if (func.constructor && func.constructor.name === "AsyncFunction"){
console.debug("In if part")
if (numOfArgs > 0) {
this.#setFunc(name,async (args) => {
console.debug("Args recieved in async function: ", args)
let result = await eval(func)(...JSON.parse(args));
return result.toString();
});
} else {
this.#setFunc(name, async () => {
let result = await eval(func)();
return result;
});
}
} else {
console.debug("In else part")
if (numOfArgs > 0) {
this.#setFunc(name, (args) => {
console.debug("Args recieved: ", args)
let result = eval(func)(...JSON.parse(args));
return result.toString();
});
} else {
this.#setFunc(name, () => {
let result = eval(func)();
return result;
});
}
}
__jsonnet_register_func(this.vm, name, numOfArgs);
return this;
}

destroy() {
let vm = this.#getVm();
Expand Down
4 changes: 2 additions & 2 deletions JS/jsonnet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use std::alloc;
use wasm_bindgen::prelude::*;
use console_error_panic_hook;

mod context;
pub mod context;

#[wasm_bindgen(module = "/read-file.js")]
extern "C" {
Expand All @@ -35,7 +35,7 @@ extern "C" {
}

pub struct VM {
state: State,
pub state: State,
manifest_format: Box<dyn ManifestFormat>,
trace_format: Box<dyn TraceFormat>,
tla_args: GcHashMap<IStr, TlaArg>,
Expand Down
5 changes: 4 additions & 1 deletion JS/wasm/crates/arakoo-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ quickjs-wasm-rs = "3.0.0"
bytes = { version = "1.6.0", features = ["serde"] }
fastrand = "2.1.0"
log = {version = "*"}
env_logger = {version = "*"}
env_logger = {version = "*"}
arakoo-jsonnet ={ path = "../../../jsonnet"}
jrsonnet-gcmodule = { version = "0.3.6" }
jrsonnet-evaluator = { version = "0.5.0-pre95" }
218 changes: 208 additions & 10 deletions JS/wasm/crates/arakoo-core/src/apis/jsonnet/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
use std::{
collections::HashMap,
ops::Deref,
sync::{Arc, Mutex},
};

use crate::apis::{console, jsonnet};

use super::{wit::edgechains, APIConfig, JSApiSet};
use arakoo_jsonnet::{self};
use javy::quickjs::{JSContextRef, JSValue, JSValueRef};
use jrsonnet_evaluator::{
function::builtin::{NativeCallback, NativeCallbackHandler},
Error, Val,
};
use log::debug;
use quickjs_wasm_rs::{from_qjs_value, to_qjs_value};
// use jrsonnet_evaluator::function::

pub(super) struct Jsonnet;

Expand All @@ -24,6 +40,15 @@ impl JSApiSet for Jsonnet {
"__jsonnet_evaluate_file",
context.wrap_callback(jsonnet_evaluate_file_closure())?,
)?;
let jsonnet_func_map = JSValue::Object(HashMap::new());
global.set_property(
"__jsonnet_func_map",
to_qjs_value(context, &jsonnet_func_map)?,
)?;
global.set_property(
"__jsonnet_register_func",
context.wrap_callback(jsonnet_register_func_closure())?,
)?;
global.set_property(
"__jsonnet_destroy",
context.wrap_callback(jsonnet_destroy_closure())?,
Expand All @@ -32,9 +57,143 @@ impl JSApiSet for Jsonnet {
}
}

#[derive(jrsonnet_gcmodule::Trace)]
pub struct NativeJSCallback(String);

impl NativeCallbackHandler for NativeJSCallback {
fn call(
&self,
args: &[jrsonnet_evaluator::Val],
) -> jrsonnet_evaluator::Result<jrsonnet_evaluator::Val> {
debug!("NativeJSCallback called: {:?}", self.0);
let super_context = **super::CONTEXT.get().unwrap();
let global = super_context
.global_object()
.expect("Unable to get super context");
let func_map = global
.get_property("__jsonnet_func_map")
.expect("Unable to get global object");
let func = func_map
.get_property(self.0.clone())
.expect("Unable to get property");
// debug!(
// "func: {:?}",
// from_qjs_value(func).expect("Unable to convert map ref to map")
// );
let result;
if args.len() > 0 {
let args_str = serde_json::to_string(args).expect("Error converting args to JSON");
let args_str = JSValue::String(args_str);
debug!(
"Calling function: {} with args = {}",
self.0,
args_str.to_string()
);
result = func
.call(
&to_qjs_value(super_context, &JSValue::Undefined)
.expect("Unable to convert undefined"),
&[to_qjs_value(super_context, &args_str)
.expect("Unable to convert string to qjs value")],
)
.expect("Unable to call function");
// let result = from_qjs_value(result).expect("Unable to convert qjs value to value");
// debug!("Result of calling JS function: {}", result.as_str().unwrap());
} else {
let emtpy_str = JSValue::String("".to_string());
let context = **super::CONTEXT.get().unwrap();
result = func
.call(
&to_qjs_value(context, &JSValue::Undefined)
.expect("Unable to convert undefined"),
&[to_qjs_value(context, &emtpy_str)
.expect("Unable to convert string to qjs value")],
)
.expect("Unable to call function");
// let result = from_qjs_value(result).expect("Unable to convert qjs value to value");
}
if result.is_object() {
debug!("Result is object");
let constructor = result
.get_property("constructor")
.expect("Unable to get constructor");
if !constructor.is_null_or_undefined() {
let constructor_name = constructor
.get_property("name")
.expect("Unable to find name in constructor")
.to_string();
if constructor_name == "Promise" {
let resolved_result: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
let resolved_error: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
let then_func = result
.get_property("then")
.expect("Unable to find then on promise");
if then_func.is_function() {
let resolved_result = resolved_result.clone();
let resolved_error = resolved_error.clone();
then_func
.call(
&result,
&[
super_context
.wrap_callback(move |context, _this, args| {
resolved_result
.lock()
.unwrap()
.replace(args.get(0).unwrap().to_string());
Ok(JSValue::Undefined)
})
.expect("unable to wrap callback"),
super_context
.wrap_callback(move |context, _this, args| {
// resolvedError.replace(Some(args.get(0).unwrap().to_string()));
resolved_error
.lock()
.unwrap()
.replace(args.get(0).unwrap().to_string());
Ok(JSValue::Undefined)
})
.expect("Unable to wrap callback"),
],
)
.expect("Unable to call then function");
super_context
.execute_pending()
.expect("Unable to execute pending tasks");
} else {
panic!("then is not a function");
}

let result = resolved_result.lock().unwrap();
let error = resolved_error.lock().unwrap();
if result.is_some() {
Ok(Val::Str(result.as_ref().unwrap().into()))
} else {
Ok(Val::Str(error.as_ref().unwrap().into()))
}
} else {
Ok(Val::Str(
"Unable to find constructor property of returned type from function".into(),
))
}
} else {
Ok(Val::Str("Result is an object but retuned object does not contain constructor function".into()))
}
} else if result.is_str() {
Ok(Val::Str(result.as_str().unwrap().into()))
} else {
// debug!("Result is unknown");
Ok(Val::Str("Function does not return any result or promise".into()))
}
}
}

fn jsonnet_make_closure(
) -> impl FnMut(&JSContextRef, JSValueRef, &[JSValueRef]) -> anyhow::Result<JSValue> {
move |_ctx, _this, args| Ok(JSValue::Float(edgechains::jsonnet::jsonnet_make() as f64))
move |_ctx, _this, args| {
let ptr = arakoo_jsonnet::jsonnet_make();
Ok(JSValue::from(ptr as u64 as f64))
}
}

fn jsonnet_ext_string_closure(
Expand All @@ -47,10 +206,12 @@ fn jsonnet_ext_string_closure(
args.len() - 1
));
}
let vm = args.get(0).unwrap().as_f64().unwrap();
let vm = args.get(0).unwrap().as_f64()?;
let key = args.get(1).unwrap().to_string();
let value = args.get(2).unwrap().to_string();
edgechains::jsonnet::jsonnet_ext_string(vm as u64, &key, &value);
// edgechains::jsonnet::jsonnet_ext_string(vm as u64, &key, &value);
let ptr = vm as u64;
arakoo_jsonnet::jsonnet_ext_string(ptr as *mut arakoo_jsonnet::VM, &key, &value);
Ok(JSValue::Undefined)
}
}
Expand All @@ -62,10 +223,16 @@ fn jsonnet_evaluate_snippet_closure(
if args.len() != 2 {
return Err(anyhow::anyhow!("Expected 2 arguments, got {}", args.len()));
}
let vm = args.get(0).unwrap().as_f64().unwrap();
let vm = args.get(0).unwrap().as_f64()?;
let code = args.get(1).unwrap().to_string();
let code = code.as_str();
let out = edgechains::jsonnet::jsonnet_evaluate_snippet(vm as u64, "snippet", code);
// let out = edgechains::jsonnet::jsonnet_evaluate_snippet(vm as u64, "snippet", code);
let out = arakoo_jsonnet::jsonnet_evaluate_snippet(
vm as u64 as *mut arakoo_jsonnet::VM,
"snippet",
code,
);
debug!("Result of evaluating snippet: {}", out.to_string());
Ok(out.into())
}
}
Expand All @@ -77,23 +244,54 @@ fn jsonnet_evaluate_file_closure(
if args.len() != 2 {
return Err(anyhow::anyhow!("Expected 2 arguments, got {}", args.len()));
}
let vm = args.get(0).unwrap().as_f64().unwrap();
let vm = args.get(0).unwrap().as_f64()?;
let path = args.get(1).unwrap().to_string();
let path = path.as_str();
let out = edgechains::jsonnet::jsonnet_evaluate_file(vm as u64, path);
let code = edgechains::utils::read_file(path.as_str());
let out = arakoo_jsonnet::jsonnet_evaluate_snippet(
vm as u64 as *mut arakoo_jsonnet::VM,
"snippet",
&code,
);
Ok(out.into())
}
}

fn jsonnet_register_func_closure(
) -> impl FnMut(&JSContextRef, JSValueRef, &[JSValueRef]) -> anyhow::Result<JSValue> {
move |_ctx, _this, args| {
// check the number of arguments
if args.len() != 3 {
return Err(anyhow::anyhow!("Expected 3 arguments, got {}", args.len()));
}
let vm = args.get(0).unwrap().as_f64().unwrap();
let func_name = args.get(1).unwrap().to_string();
let args_num = args.get(2).unwrap().as_f64().unwrap();
// edgechains::jsonnet::jsonnet_register_func(vm as u64, &func_name, args_num as u32);
let vm = unsafe { &*(vm as u64 as *mut arakoo_jsonnet::VM) };
let any_resolver = vm.state.context_initializer();
let args_vec = vec![String::from("x"); args_num as usize];
any_resolver
.as_any()
.downcast_ref::<arakoo_jsonnet::context::ArakooContextInitializer>()
.expect("only arakoo context initializer supported")
.add_native(
func_name.clone(),
NativeCallback::new(args_vec, NativeJSCallback(func_name.clone())),
);
debug!("Registered function: {}", func_name);
Ok(JSValue::Undefined)
}
}

fn jsonnet_destroy_closure(
) -> impl FnMut(&JSContextRef, JSValueRef, &[JSValueRef]) -> anyhow::Result<JSValue> {
move |_ctx, _this, args| {
// check the number of arguments
if args.len() != 1 {
return Err(anyhow::anyhow!("Expected 1 arguments, got {}", args.len()));
}
let vm = args.get(0).unwrap().as_f64().unwrap();
edgechains::jsonnet::jsonnet_destroy(vm as u64);
let vm = args.get(0).unwrap().as_f64()?;
arakoo_jsonnet::jsonnet_destroy(vm as u64 as *mut arakoo_jsonnet::VM);
Ok(JSValue::Undefined)
}
}
2 changes: 2 additions & 0 deletions JS/wasm/crates/arakoo-core/src/apis/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ pub use api_config::APIConfig;
pub use console::LogStream;
pub use runtime_ext::RuntimeExt;

use super::CONTEXT;

pub mod http;
pub mod types;

Expand Down
2 changes: 1 addition & 1 deletion JS/wasm/crates/arakoo-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ static mut RUNTIME_INSTANCE: Option<Runtime> = None;
// fn on_reject(context: &JSContextRef, _this: JSValueRef, args: &[JSValueRef]) -> Result<JSValue> {
// // (*args).clone_into(&mut cloned_args);
// let mut qjs_value = Option::None;
// if (args.len() > 0) {
// if args.len() > 0 {
// for arg in args {
// qjs_value = Some(from_qjs_value(*arg).unwrap());
// println!("Arg reject : {:?}", qjs_value.as_ref().unwrap());
Expand Down
Loading
Loading