diff --git a/Cargo.lock b/Cargo.lock index bcb67381f..ef7a35c8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -354,16 +354,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "console_log" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f" -dependencies = [ - "log", - "web-sys", -] - [[package]] name = "core-foundation" version = "0.9.3" @@ -2326,7 +2316,6 @@ dependencies = [ "bytes", "chrono", "console_error_panic_hook", - "console_log", "cpu-time", "criterion", "crossterm", @@ -2343,6 +2332,7 @@ dependencies = [ "hostname", "iai-callgrind", "indexmap", + "js-sys", "lazy_static", "lexical", "libc", diff --git a/Cargo.toml b/Cargo.toml index b36e4e138..5a7cc04bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,11 +95,11 @@ tokio = { version = "1.28.2", features = [ [target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] console_error_panic_hook = "0.1" -console_log = "1.0" wasm-bindgen = "0.2.87" wasm-bindgen-futures = "0.4" serde-wasm-bindgen = "0.5" web-sys = { version = "0.3", features = ["Document", "Window", "Element"] } +js-sys = "0.3" [target.'cfg(target_os = "wasi")'.dependencies] ring-wasi = { version = "0.16.25" } diff --git a/build/instructions_template.rs b/build/instructions_template.rs index 69cf2d12e..9b92ccd67 100644 --- a/build/instructions_template.rs +++ b/build/instructions_template.rs @@ -573,6 +573,8 @@ enum SystemClauseType { ForeignCall, #[strum_discriminants(strum(props(Arity = "2", Name = "$define_foreign_struct")))] DefineForeignStruct, + #[strum_discriminants(strum(props(Arity = "2", Name = "$js_eval")))] + JsEval, #[strum_discriminants(strum(props(Arity = "3", Name = "$predicate_defined")))] PredicateDefined, #[strum_discriminants(strum(props(Arity = "3", Name = "$strip_module")))] @@ -1774,6 +1776,7 @@ fn generate_instruction_preface() -> TokenStream { &Instruction::CallLoadForeignLib | &Instruction::CallForeignCall | &Instruction::CallDefineForeignStruct | + &Instruction::CallJsEval | &Instruction::CallPredicateDefined | &Instruction::CallStripModule | &Instruction::CallCurrentTime | @@ -2008,6 +2011,7 @@ fn generate_instruction_preface() -> TokenStream { &Instruction::ExecuteLoadForeignLib | &Instruction::ExecuteForeignCall | &Instruction::ExecuteDefineForeignStruct | + &Instruction::ExecuteJsEval | &Instruction::ExecutePredicateDefined | &Instruction::ExecuteStripModule | &Instruction::ExecuteCurrentTime | diff --git a/src/lib.rs b/src/lib.rs index bf7ddc57c..56d967eb2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,7 +51,8 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn eval_code(s: &str) -> String { use machine::mock_wam::*; - use web_sys::console; + + console_error_panic_hook::set_once(); let mut wam = Machine::with_test_streams(); let bytes = wam.test_load_string(s); diff --git a/src/lib/wasm.pl b/src/lib/wasm.pl new file mode 100644 index 000000000..2ade16417 --- /dev/null +++ b/src/lib/wasm.pl @@ -0,0 +1,29 @@ +/** Predicates for the WebAssembly platform + +This module contains predicates that are only available in +the WASM (WebAssembly) version of Scryer Prolog. +*/ + +:- module(wasm, [js_eval/2]). + +:- use_module(library(error)). + +%% js_eval(+JsCode, -Result). +% +% Executes a JavaScript snippet `JsCode` using the platform +% `eval` function. `Result` takes the return value of that code. +% Strings, booleans, numbers, null and undefined are directly mapped to Prolog. +% Arrays, objects, bigints, symbols and functions are not mapped. +% Instead, a `js_{type}` atom will be returned. +% +% Example (on a browser): +% +% ``` +% ?- js_eval("prompt('What is your name?')", Name). +% % A prompt is showed, with a textbox. +% Name = "Whatever was written on the textbox". +% ``` +js_eval(JsCode, Result) :- + must_be(chars, JsCode), + can_be(chars, Result), + '$js_eval'(JsCode, Result). diff --git a/src/machine/dispatch.rs b/src/machine/dispatch.rs index 6afce9263..e75fccd56 100644 --- a/src/machine/dispatch.rs +++ b/src/machine/dispatch.rs @@ -4128,6 +4128,14 @@ impl Machine { try_or_throw!(self.machine_st, self.define_foreign_struct()); step_or_fail!(self, self.machine_st.p = self.machine_st.cp); } + &Instruction::CallJsEval => { + try_or_throw!(self.machine_st, self.js_eval()); + step_or_fail!(self, self.machine_st.p += 1); + } + &Instruction::ExecuteJsEval => { + try_or_throw!(self.machine_st, self.js_eval()); + step_or_fail!(self, self.machine_st.p = self.machine_st.cp); + } &Instruction::CallCurrentTime => { self.current_time(); step_or_fail!(self, self.machine_st.p += 1); diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index aa78f1a10..18952bde9 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -4879,6 +4879,69 @@ impl Machine { Ok(()) } + #[cfg(not(target_arch = "wasm32"))] + #[inline(always)] + pub(crate) fn js_eval(&mut self) -> CallResult { + unimplemented!() + } + + #[cfg(target_arch = "wasm32")] + #[inline(always)] + pub(crate) fn js_eval(&mut self) -> CallResult { + + let code = self.deref_register(1); + let result_reg = self.deref_register(2); + if let Some(code) = self.machine_st.value_to_str_like(code) { + let result = match js_sys::eval(&code.as_str()) { + Ok(result) => self.unify_js_value(result, result_reg), + Err(result) => self.unify_js_value(result, result_reg), + }; + return Ok(()); + } + self.machine_st.fail = true; + Ok(()) + } + + fn unify_js_value(&mut self, result: wasm_bindgen::JsValue, result_reg: HeapCellValue) { + match result.as_bool() { + Some(result) => { + match result { + true => self.machine_st.unify_atom(atom!("true"), result_reg), + false => self.machine_st.unify_atom(atom!("false"), result_reg), + } + } + None => match result.as_f64() { + Some(result) => { + let n = float_alloc!(result, self.machine_st.arena); + self.machine_st.unify_f64(n, result_reg); + } + None => match result.as_string() { + Some(result) => { + let result = AtomTable::build_with(&self.machine_st.atom_tbl, &result); + self.machine_st.unify_complete_string(result, result_reg); + } + None => if result.is_null() { + self.machine_st.unify_atom(atom!("null"), result_reg); + } else if result.is_undefined() { + self.machine_st.unify_atom(atom!("undefined"), result_reg); + } else if result.is_symbol() { + self.machine_st.unify_atom(atom!("js_symbol"), result_reg); + } else if result.is_object() { + self.machine_st.unify_atom(atom!("js_object"), result_reg); + } else if result.is_array() { + self.machine_st.unify_atom(atom!("js_array"), result_reg); + } else if result.is_function() { + self.machine_st.unify_atom(atom!("js_function"), result_reg); + } else if result.is_bigint() { + self.machine_st.unify_atom(atom!("js_bigint"), result_reg); + } else { + self.machine_st.unify_atom(atom!("js_unknown_type"), result_reg); + } + } + } + } + } + #[inline(always)] pub(crate) fn current_time(&mut self) { let timestamp = self.systemtime_to_timestamp(SystemTime::now());