From b32273fc790a62168982ec288ca412605f704be2 Mon Sep 17 00:00:00 2001 From: bakaq Date: Fri, 31 Jan 2025 09:24:39 -0300 Subject: [PATCH 1/6] Make Wasm compilable --- Cargo.toml | 11 +++++++++++ flake.nix | 15 ++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a299e690..0eb6b7a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -139,6 +139,17 @@ opt-level = 3 lto = true opt-level = 3 +[profile.wasm-dev] +inherits = "dev" +opt-level = 1 +lto = "off" + +[profile.wasm-release] +inherits = "release" +lto = "off" +panic = "abort" +codegen-units = 256 + [[bench]] name = "run_criterion" harness = false diff --git a/flake.nix b/flake.nix index 852aa4af..14e38943 100644 --- a/flake.nix +++ b/flake.nix @@ -20,6 +20,10 @@ rustToolchainDev = super.rust-bin.stable.latest.default.override { extensions = [ "rust-src" "rust-analyzer" ]; }; + rustToolchainDevWasm = super.rust-bin.stable.latest.default.override { + extensions = [ "rust-src" "rust-analyzer" ]; + targets = [ "wasm32-unknown-unknown" ]; + }; rustToolchainNightly = super.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default.override { extensions = [ "rust-src" "rust-analyzer" "miri" ]; @@ -38,12 +42,21 @@ in { devShells = { - default = pkgs.mkShell { + default = pkgs.mkShell.override { stdenv = pkgs.clangMultiStdenv; } { nativeBuildInputs = nativeBuildInputs; buildInputs = buildInputs ++ (with pkgs; [ rustToolchainDev ]); }; + wasm-js = pkgs.mkShell.override { stdenv = pkgs.clangMultiStdenv; } { + nativeBuildInputs = nativeBuildInputs; + buildInputs = buildInputs ++ (with pkgs; [ + wasm-pack + rustToolchainDevWasm + ]); + TARGET_CC = "${pkgs.clangMultiStdenv.cc}/bin/clang"; + hardeningDisable = [ "all" ]; + }; # For use with Miri and stuff like it nightly = pkgs.mkShell { nativeBuildInputs = nativeBuildInputs; From 93e804eeaa880865fcb2e97b959004309c07d054 Mon Sep 17 00:00:00 2001 From: bakaq Date: Fri, 31 Jan 2025 09:24:39 -0300 Subject: [PATCH 2/6] Rework Wasm interface --- Cargo.lock | 58 ++++++- Cargo.toml | 1 + src/bin/scryer-prolog.rs | 6 +- src/lib.rs | 16 +- src/wasm.rs | 320 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 385 insertions(+), 16 deletions(-) create mode 100644 src/wasm.rs diff --git a/Cargo.lock b/Cargo.lock index 623d5cf7..8be9620b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,6 +39,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -1129,6 +1135,12 @@ dependencies = [ "http 0.2.12", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -1844,6 +1856,30 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "ouroboros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.72", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -2132,6 +2168,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", + "version_check", + "yansi", +] + [[package]] name = "quick-xml" version = "0.26.0" @@ -2580,6 +2629,7 @@ dependencies = [ "native-tls", "num-order", "ordered-float", + "ouroboros", "phf 0.11.2", "pprof", "predicates-core", @@ -2966,7 +3016,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "rustversion", @@ -3780,6 +3830,12 @@ dependencies = [ "tap", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index 0eb6b7a1..4d89a016 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,6 +116,7 @@ web-sys = { version = "0.3", features = [ "Performance", ] } js-sys = "0.3" +ouroboros = "0.18" [dev-dependencies] maplit = "1.0.2" diff --git a/src/bin/scryer-prolog.rs b/src/bin/scryer-prolog.rs index 763314ec..4a9dd2cd 100644 --- a/src/bin/scryer-prolog.rs +++ b/src/bin/scryer-prolog.rs @@ -1,3 +1,7 @@ fn main() -> std::process::ExitCode { - scryer_prolog::run_binary() + #[cfg(target_arch = "wasm32")] + return std::process::ExitCode::SUCCESS; + + #[cfg(not(target_arch = "wasm32"))] + return scryer_prolog::run_binary(); } diff --git a/src/lib.rs b/src/lib.rs index 85ee726a..fcb65ff0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,27 +39,15 @@ mod repl_helper; mod targets; pub(crate) mod types; -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; - // Re-exports pub use machine::config::*; pub use machine::lib_machine::*; pub use machine::Machine; -/// Eval a source file in Wasm. #[cfg(target_arch = "wasm32")] -#[wasm_bindgen] -pub fn eval_code(s: &str) -> String { - use machine::mock_wam::*; - - console_error_panic_hook::set_once(); - - let mut wam = MachineBuilder::default().build(); - let bytes = wam.test_load_string(s); - String::from_utf8_lossy(&bytes).to_string() -} +pub mod wasm; +#[cfg(not(target_arch = "wasm32"))] /// The entry point for the Scryer Prolog CLI. pub fn run_binary() -> std::process::ExitCode { use crate::atom_table::Atom; diff --git a/src/wasm.rs b/src/wasm.rs new file mode 100644 index 00000000..d08e1860 --- /dev/null +++ b/src/wasm.rs @@ -0,0 +1,320 @@ +//! Wasm interface + +#![allow(missing_docs)] + +use std::mem; +use std::sync::mpsc; +use std::sync::mpsc::{Receiver, Sender}; + +use ouroboros::self_referencing; +use wasm_bindgen::prelude::*; + +use crate::*; + +#[wasm_bindgen(js_name = MachineBuilder)] +#[derive(Default)] +pub struct WasmMachineBuilder { + inner: MachineBuilder, +} + +#[wasm_bindgen(js_class = MachineBuilder)] +impl WasmMachineBuilder { + pub fn new() -> Self { + Default::default() + } + + pub fn build(&mut self) -> WasmMachine { + WasmMachine { + inner: Ok(std::mem::take(&mut self.inner).build()), + } + } +} + +#[wasm_bindgen(inline_js = " + export function self_iterable(obj) { + obj[Symbol.iterator] = function () { + return this + }; + } +")] +extern "C" { + fn self_iterable(obj: &JsValue); +} + +#[wasm_bindgen(js_name = Machine)] +pub struct WasmMachine { + inner: Result>, +} + +#[wasm_bindgen(js_class = Machine)] +impl WasmMachine { + #[wasm_bindgen(js_name = runQuery)] + pub fn run_query(&mut self, query: String) -> Result { + if self.inner.is_err() { + // We have a receiver, try to get the Machine + let machine = self + .inner + .as_mut() + .unwrap_err() + .try_recv() + .map_err(|_| js_sys::Error::new("Another query is still active"))?; + let _ = mem::replace(&mut self.inner, Ok(machine)); + } + + assert!(self.inner.is_ok()); + + // Installs a receiver and gets the machine + let (sender, receiver) = mpsc::channel(); + let machine = mem::replace(&mut self.inner, Err(receiver)).unwrap(); + + let query_state: JsValue = WasmQueryState { + inner: Some( + WasmQueryStateInnerBuilder { + machine, + drop_channel: sender, + query_state_builder: move |m: &mut Machine| m.run_query(query), + } + .build(), + ), + } + .into(); + + self_iterable(&query_state); + Ok(query_state) + } +} + +#[self_referencing] +struct WasmQueryStateInner { + machine: Machine, + drop_channel: Sender, + #[covariant] + #[borrows(mut machine)] + query_state: QueryState<'this>, +} + +#[wasm_bindgen(js_name = QueryState)] +pub struct WasmQueryState { + inner: Option, +} + +#[wasm_bindgen(js_class = QueryState)] +impl WasmQueryState { + #[wasm_bindgen(js_name = next)] + pub fn next_answer(&mut self) -> Result { + let ret = js_sys::Object::new(); + let mut error = None; + let mut to_drop = false; + match &mut self.inner { + Some(ref mut inner) => { + inner.with_query_state_mut(|query_state| match query_state.next() { + Some(Ok(leaf_answer)) => { + js_sys::Reflect::set(&ret, &"value".into(), &leaf_answer.into()).unwrap(); + js_sys::Reflect::set(&ret, &"done".into(), &false.into()).unwrap(); + } + Some(Err(error_term)) => { + let js_error = js_sys::Error::new("Prolog error"); + js_error.set_cause(&error_term.into()); + error = Some(js_error); + } + None => { + js_sys::Reflect::set(&ret, &"done".into(), &true.into()).unwrap(); + to_drop = true; + } + }) + } + None => return Err(js_sys::Error::new("This query was already dropped").into()), + } + + if let Some(e) = error { + self.drop_inner(); + return Err(JsValue::from(e)); + } + + if to_drop { + self.drop_inner(); + } + + Ok(ret.into()) + } + + #[wasm_bindgen(js_name = drop)] + pub fn drop_inner(&mut self) { + let ouroboros_impl_wasm_query_state_inner::Heads { + machine, + drop_channel, + } = self.inner.take().unwrap().into_heads(); + drop_channel.send(machine).unwrap(); + } +} + +#[wasm_bindgen(inline_js = r#" + export class LeafAnswer { + constructor(bindings) { + this.bindings = bindings; + } + } + + export class PrologInteger { + constructor(integerStr) { + this.type = "integer"; + this.integer = BigInt(integerStr); + } + } + + export class PrologRational { + constructor(numeratorStr, denominatorStr) { + this.type = "rational"; + this.numerator = BigInt(numeratorStr); + this.denominator = BigInt(denominatorStr); + } + } + + export class PrologFloat { + constructor(float) { + this.type = "float"; + this.float = float; + } + } + + export class PrologAtom { + constructor(atom) { + this.type = "atom"; + this.atom = atom; + } + } + + export class PrologString { + constructor(string) { + this.type = "string"; + this.string = string; + } + } + + export class PrologList { + constructor(list) { + this.type = "list"; + this.list = list; + } + } + + export class PrologCompound { + constructor(functor, args) { + this.type = "compound"; + this.functor = functor; + this.args = args; + } + } + + export class PrologVariable { + constructor(variable) { + this.type = "variable"; + this.variable = variable; + } + } + + export class PrologException { + constructor(exception) { + this.exception = exception; + } + } +"#)] +extern "C" { + #[wasm_bindgen(js_name = LeafAnswer)] + pub type JsLeafAnswer; + + #[wasm_bindgen(constructor, js_class = LeafAnswer)] + pub fn new(bindings: js_sys::Object) -> JsLeafAnswer; + + #[wasm_bindgen(js_name = PrologInteger)] + pub type JsPrologInteger; + + #[wasm_bindgen(constructor, js_class = PrologInteger)] + pub fn new(int_str: &str) -> JsPrologInteger; + + #[wasm_bindgen(js_name = PrologRational)] + pub type JsPrologRational; + + #[wasm_bindgen(constructor, js_class = PrologRational)] + pub fn new(numer_str: &str, denom_str: &str) -> JsPrologRational; + + #[wasm_bindgen(js_name = PrologFloat)] + pub type JsPrologFloat; + + #[wasm_bindgen(constructor, js_class = PrologFloat)] + pub fn new(float: f64) -> JsPrologFloat; + + #[wasm_bindgen(js_name = PrologAtom)] + pub type JsPrologAtom; + + #[wasm_bindgen(constructor, js_class = PrologAtom)] + pub fn new(atom: &str) -> JsPrologAtom; + + #[wasm_bindgen(js_name = PrologString)] + pub type JsPrologString; + + #[wasm_bindgen(constructor, js_class = PrologString)] + pub fn new(string: &str) -> JsPrologString; + + #[wasm_bindgen(js_name = PrologList)] + pub type JsPrologList; + + #[wasm_bindgen(constructor, js_class = PrologList)] + pub fn new(list: js_sys::Array) -> JsPrologList; + + #[wasm_bindgen(js_name = PrologCompound)] + pub type JsPrologCompound; + + #[wasm_bindgen(constructor, js_class = PrologCompound)] + pub fn new(functor: &str, args: js_sys::Array) -> JsPrologCompound; + + #[wasm_bindgen(js_name = PrologVariable)] + pub type JsPrologVariable; + + #[wasm_bindgen(constructor, js_class = PrologVariable)] + pub fn new(variable: &str) -> JsPrologVariable; + + #[wasm_bindgen(js_name = PrologException)] + pub type JsPrologException; + + #[wasm_bindgen(constructor, js_class = PrologException)] + pub fn new(exception: JsValue) -> JsPrologException; +} + +impl From for JsValue { + fn from(leaf_answer: LeafAnswer) -> JsValue { + match leaf_answer { + LeafAnswer::True => true.into(), + LeafAnswer::False => false.into(), + LeafAnswer::Exception(e) => JsPrologException::new(e.into()).into(), + LeafAnswer::LeafAnswer { bindings } => { + let bindings_obj = js_sys::Object::new(); + for (var, term) in bindings.into_iter() { + js_sys::Reflect::set(&bindings_obj, &var.into(), &term.into()).unwrap(); + } + JsLeafAnswer::new(bindings_obj).into() + } + } + } +} + +impl From for JsValue { + fn from(term: Term) -> JsValue { + match term { + Term::Integer(i) => JsPrologInteger::new(&i.to_string()).into(), + Term::Rational(r) => { + JsPrologRational::new(&r.numerator().to_string(), &r.denominator().to_string()) + .into() + } + Term::Float(f) => JsPrologFloat::new(f).into(), + Term::Atom(a) => JsPrologAtom::new(&a).into(), + Term::String(s) => JsPrologString::new(&s).into(), + Term::List(l) => JsPrologList::new(l.into_iter().map(JsValue::from).collect()).into(), + Term::Compound(functor, args) => { + JsPrologCompound::new(&functor, args.into_iter().map(JsValue::from).collect()) + .into() + } + Term::Var(v) => JsPrologVariable::new(&v).into(), + } + } +} From cea8e6b1c97699a349073ca580df2c7836441caf Mon Sep 17 00:00:00 2001 From: bakaq Date: Wed, 19 Feb 2025 22:50:41 -0300 Subject: [PATCH 3/6] MachineBuilder constructor --- src/wasm.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wasm.rs b/src/wasm.rs index d08e1860..758d7a6a 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -19,6 +19,7 @@ pub struct WasmMachineBuilder { #[wasm_bindgen(js_class = MachineBuilder)] impl WasmMachineBuilder { + #[wasm_bindgen(constructor)] pub fn new() -> Self { Default::default() } From 236ed93c6cb99b80a085f8dd0248eae9a6194fbd Mon Sep 17 00:00:00 2001 From: bakaq Date: Thu, 20 Feb 2025 00:12:30 -0300 Subject: [PATCH 4/6] Send pure objects instead of classes --- src/wasm.rs | 245 +++++++++++++++++++--------------------------------- 1 file changed, 88 insertions(+), 157 deletions(-) diff --git a/src/wasm.rs b/src/wasm.rs index 758d7a6a..cdfa6e91 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -31,17 +31,6 @@ impl WasmMachineBuilder { } } -#[wasm_bindgen(inline_js = " - export function self_iterable(obj) { - obj[Symbol.iterator] = function () { - return this - }; - } -")] -extern "C" { - fn self_iterable(obj: &JsValue); -} - #[wasm_bindgen(js_name = Machine)] pub struct WasmMachine { inner: Result>, @@ -80,7 +69,6 @@ impl WasmMachine { } .into(); - self_iterable(&query_state); Ok(query_state) } } @@ -149,151 +137,29 @@ impl WasmQueryState { } } -#[wasm_bindgen(inline_js = r#" - export class LeafAnswer { - constructor(bindings) { - this.bindings = bindings; - } - } - - export class PrologInteger { - constructor(integerStr) { - this.type = "integer"; - this.integer = BigInt(integerStr); - } - } - - export class PrologRational { - constructor(numeratorStr, denominatorStr) { - this.type = "rational"; - this.numerator = BigInt(numeratorStr); - this.denominator = BigInt(denominatorStr); - } - } - - export class PrologFloat { - constructor(float) { - this.type = "float"; - this.float = float; - } - } - - export class PrologAtom { - constructor(atom) { - this.type = "atom"; - this.atom = atom; - } - } - - export class PrologString { - constructor(string) { - this.type = "string"; - this.string = string; - } - } - - export class PrologList { - constructor(list) { - this.type = "list"; - this.list = list; - } - } - - export class PrologCompound { - constructor(functor, args) { - this.type = "compound"; - this.functor = functor; - this.args = args; - } - } - - export class PrologVariable { - constructor(variable) { - this.type = "variable"; - this.variable = variable; - } - } - - export class PrologException { - constructor(exception) { - this.exception = exception; - } - } -"#)] -extern "C" { - #[wasm_bindgen(js_name = LeafAnswer)] - pub type JsLeafAnswer; - - #[wasm_bindgen(constructor, js_class = LeafAnswer)] - pub fn new(bindings: js_sys::Object) -> JsLeafAnswer; - - #[wasm_bindgen(js_name = PrologInteger)] - pub type JsPrologInteger; - - #[wasm_bindgen(constructor, js_class = PrologInteger)] - pub fn new(int_str: &str) -> JsPrologInteger; - - #[wasm_bindgen(js_name = PrologRational)] - pub type JsPrologRational; - - #[wasm_bindgen(constructor, js_class = PrologRational)] - pub fn new(numer_str: &str, denom_str: &str) -> JsPrologRational; - - #[wasm_bindgen(js_name = PrologFloat)] - pub type JsPrologFloat; - - #[wasm_bindgen(constructor, js_class = PrologFloat)] - pub fn new(float: f64) -> JsPrologFloat; - - #[wasm_bindgen(js_name = PrologAtom)] - pub type JsPrologAtom; - - #[wasm_bindgen(constructor, js_class = PrologAtom)] - pub fn new(atom: &str) -> JsPrologAtom; - - #[wasm_bindgen(js_name = PrologString)] - pub type JsPrologString; - - #[wasm_bindgen(constructor, js_class = PrologString)] - pub fn new(string: &str) -> JsPrologString; - - #[wasm_bindgen(js_name = PrologList)] - pub type JsPrologList; - - #[wasm_bindgen(constructor, js_class = PrologList)] - pub fn new(list: js_sys::Array) -> JsPrologList; - - #[wasm_bindgen(js_name = PrologCompound)] - pub type JsPrologCompound; - - #[wasm_bindgen(constructor, js_class = PrologCompound)] - pub fn new(functor: &str, args: js_sys::Array) -> JsPrologCompound; - - #[wasm_bindgen(js_name = PrologVariable)] - pub type JsPrologVariable; - - #[wasm_bindgen(constructor, js_class = PrologVariable)] - pub fn new(variable: &str) -> JsPrologVariable; - - #[wasm_bindgen(js_name = PrologException)] - pub type JsPrologException; - - #[wasm_bindgen(constructor, js_class = PrologException)] - pub fn new(exception: JsValue) -> JsPrologException; -} - impl From for JsValue { fn from(leaf_answer: LeafAnswer) -> JsValue { match leaf_answer { LeafAnswer::True => true.into(), LeafAnswer::False => false.into(), - LeafAnswer::Exception(e) => JsPrologException::new(e.into()).into(), + LeafAnswer::Exception(e) => { + let obj = js_sys::Object::new(); + js_sys::Reflect::set(&obj, &"type".into(), &"exception".into()).unwrap(); + js_sys::Reflect::set(&obj, &"exception".into(), &e.into()).unwrap(); + obj.into() + } LeafAnswer::LeafAnswer { bindings } => { let bindings_obj = js_sys::Object::new(); for (var, term) in bindings.into_iter() { js_sys::Reflect::set(&bindings_obj, &var.into(), &term.into()).unwrap(); } - JsLeafAnswer::new(bindings_obj).into() + + let leaf_answer_obj = js_sys::Object::new(); + js_sys::Reflect::set(&leaf_answer_obj, &"type".into(), &"leafAnswer".into()) + .unwrap(); + js_sys::Reflect::set(&leaf_answer_obj, &"bindings".into(), &bindings_obj.into()) + .unwrap(); + leaf_answer_obj.into() } } } @@ -302,20 +168,85 @@ impl From for JsValue { impl From for JsValue { fn from(term: Term) -> JsValue { match term { - Term::Integer(i) => JsPrologInteger::new(&i.to_string()).into(), + Term::Integer(i) => { + let obj = js_sys::Object::new(); + js_sys::Reflect::set(&obj, &"type".into(), &"integer".into()).unwrap(); + js_sys::Reflect::set( + &obj, + &"integer".into(), + &js_sys::BigInt::new(&i.to_string().into()).unwrap().into(), + ) + .unwrap(); + obj.into() + } Term::Rational(r) => { - JsPrologRational::new(&r.numerator().to_string(), &r.denominator().to_string()) - .into() + let obj = js_sys::Object::new(); + js_sys::Reflect::set(&obj, &"type".into(), &"rational".into()).unwrap(); + js_sys::Reflect::set( + &obj, + &"numerator".into(), + &js_sys::BigInt::new(&r.numerator().to_string().into()) + .unwrap() + .into(), + ) + .unwrap(); + js_sys::Reflect::set( + &obj, + &"denominator".into(), + &js_sys::BigInt::new(&r.denominator().to_string().into()) + .unwrap() + .into(), + ) + .unwrap(); + obj.into() + } + Term::Float(f) => { + let obj = js_sys::Object::new(); + js_sys::Reflect::set(&obj, &"type".into(), &"float".into()).unwrap(); + js_sys::Reflect::set(&obj, &"float".into(), &f.into()).unwrap(); + obj.into() + } + Term::Atom(a) => { + let obj = js_sys::Object::new(); + js_sys::Reflect::set(&obj, &"type".into(), &"atom".into()).unwrap(); + js_sys::Reflect::set(&obj, &"atom".into(), &a.into()).unwrap(); + obj.into() + } + Term::String(s) => { + let obj = js_sys::Object::new(); + js_sys::Reflect::set(&obj, &"type".into(), &"string".into()).unwrap(); + js_sys::Reflect::set(&obj, &"string".into(), &s.into()).unwrap(); + obj.into() + } + Term::List(l) => { + let list = js_sys::Array::new(); + for term in l { + list.push(&term.into()); + } + + let obj = js_sys::Object::new(); + js_sys::Reflect::set(&obj, &"type".into(), &"list".into()).unwrap(); + js_sys::Reflect::set(&obj, &"list".into(), &list.into()).unwrap(); + obj.into() } - Term::Float(f) => JsPrologFloat::new(f).into(), - Term::Atom(a) => JsPrologAtom::new(&a).into(), - Term::String(s) => JsPrologString::new(&s).into(), - Term::List(l) => JsPrologList::new(l.into_iter().map(JsValue::from).collect()).into(), Term::Compound(functor, args) => { - JsPrologCompound::new(&functor, args.into_iter().map(JsValue::from).collect()) - .into() + let args_list = js_sys::Array::new(); + for term in args { + args_list.push(&term.into()); + } + + let obj = js_sys::Object::new(); + js_sys::Reflect::set(&obj, &"type".into(), &"compound".into()).unwrap(); + js_sys::Reflect::set(&obj, &"functor".into(), &functor.into()).unwrap(); + js_sys::Reflect::set(&obj, &"args".into(), &args_list.into()).unwrap(); + obj.into() + } + Term::Var(v) => { + let obj = js_sys::Object::new(); + js_sys::Reflect::set(&obj, &"type".into(), &"variable".into()).unwrap(); + js_sys::Reflect::set(&obj, &"variable".into(), &v.into()).unwrap(); + obj.into() } - Term::Var(v) => JsPrologVariable::new(&v).into(), } } } From 3b89c1124d454dba73623f15517058d2e30de6dd Mon Sep 17 00:00:00 2001 From: bakaq Date: Thu, 20 Feb 2025 00:33:11 -0300 Subject: [PATCH 5/6] Consult modules --- src/wasm.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/wasm.rs b/src/wasm.rs index cdfa6e91..08383318 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -38,8 +38,7 @@ pub struct WasmMachine { #[wasm_bindgen(js_class = Machine)] impl WasmMachine { - #[wasm_bindgen(js_name = runQuery)] - pub fn run_query(&mut self, query: String) -> Result { + fn ensure_machine_ownership(&mut self) -> Result<(), JsValue> { if self.inner.is_err() { // We have a receiver, try to get the Machine let machine = self @@ -50,7 +49,12 @@ impl WasmMachine { .map_err(|_| js_sys::Error::new("Another query is still active"))?; let _ = mem::replace(&mut self.inner, Ok(machine)); } + Ok(()) + } + #[wasm_bindgen(js_name = runQuery)] + pub fn run_query(&mut self, query: String) -> Result { + self.ensure_machine_ownership()?; assert!(self.inner.is_ok()); // Installs a receiver and gets the machine @@ -71,6 +75,21 @@ impl WasmMachine { Ok(query_state) } + + #[wasm_bindgen(js_name = consultModuleString)] + pub fn consult_module_string( + &mut self, + module: String, + program: String, + ) -> Result<(), JsValue> { + self.ensure_machine_ownership()?; + assert!(self.inner.is_ok()); + + let inner = self.inner.as_mut().unwrap(); + inner.consult_module_string(&module, program); + + Ok(()) + } } #[self_referencing] From 080a4fed038478980e05f87b8944896a40ed67a2 Mon Sep 17 00:00:00 2001 From: bakaq Date: Thu, 20 Feb 2025 00:48:51 -0300 Subject: [PATCH 6/6] Documentation --- src/wasm.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/wasm.rs b/src/wasm.rs index 08383318..10430677 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -1,7 +1,5 @@ //! Wasm interface -#![allow(missing_docs)] - use std::mem; use std::sync::mpsc; use std::sync::mpsc::{Receiver, Sender}; @@ -11,6 +9,7 @@ use wasm_bindgen::prelude::*; use crate::*; +/// A builder for a `Machine`. #[wasm_bindgen(js_name = MachineBuilder)] #[derive(Default)] pub struct WasmMachineBuilder { @@ -19,11 +18,13 @@ pub struct WasmMachineBuilder { #[wasm_bindgen(js_class = MachineBuilder)] impl WasmMachineBuilder { + /// Creates a new `MachineBuilder` with the default configuration. #[wasm_bindgen(constructor)] pub fn new() -> Self { Default::default() } + /// Creates a new `Machine`. pub fn build(&mut self) -> WasmMachine { WasmMachine { inner: Ok(std::mem::take(&mut self.inner).build()), @@ -31,6 +32,7 @@ impl WasmMachineBuilder { } } +/// The Scryer Prolog `Machine`. #[wasm_bindgen(js_name = Machine)] pub struct WasmMachine { inner: Result>, @@ -52,6 +54,10 @@ impl WasmMachine { Ok(()) } + /// Runs a query. + /// + /// You can only have one query at a time. If you try to do anything with this machine while + /// doing a query an error will be thrown. #[wasm_bindgen(js_name = runQuery)] pub fn run_query(&mut self, query: String) -> Result { self.ensure_machine_ownership()?; @@ -76,6 +82,7 @@ impl WasmMachine { Ok(query_state) } + /// Consults a module. #[wasm_bindgen(js_name = consultModuleString)] pub fn consult_module_string( &mut self, @@ -101,6 +108,7 @@ struct WasmQueryStateInner { query_state: QueryState<'this>, } +/// The state of a running query. #[wasm_bindgen(js_name = QueryState)] pub struct WasmQueryState { inner: Option, @@ -108,6 +116,12 @@ pub struct WasmQueryState { #[wasm_bindgen(js_class = QueryState)] impl WasmQueryState { + /// Gets the next leaf answer. + /// + /// This follows the Javascript iterator protocol, so it returns an object that + /// contains a `done` field and a `value` field. If `done` is `false`, then the query ended + /// and control of the `Machine` will be given back to the `Machine` that created this query. + /// Any call after that will result in an error. #[wasm_bindgen(js_name = next)] pub fn next_answer(&mut self) -> Result { let ret = js_sys::Object::new(); @@ -146,6 +160,10 @@ impl WasmQueryState { Ok(ret.into()) } + /// Drops the query. + /// + /// This is useful to end a query early. Like finishing a query, control will be given back + /// to the `Machine` and any call to `next` after that will result in an error. #[wasm_bindgen(js_name = drop)] pub fn drop_inner(&mut self) { let ouroboros_impl_wasm_query_state_inner::Heads {