]> Repositorios git - scryer-prolog.git/commitdiff
Rework Wasm interface
authorbakaq <[email protected]>
Fri, 31 Jan 2025 12:24:39 +0000 (09:24 -0300)
committerbakaq <[email protected]>
Thu, 20 Feb 2025 04:04:38 +0000 (01:04 -0300)
Cargo.lock
Cargo.toml
src/bin/scryer-prolog.rs
src/lib.rs
src/wasm.rs [new file with mode: 0644]

index 623d5cf782d8548a08262e11189c770b6522587e..8be9620b943ee1384a6d836cabb541c5123634ee 100644 (file)
@@ -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"
index 0eb6b7a1f2efe8b959263ea9f5ebe28703526553..4d89a01615a7d925e89d955fba0a49be43c74e97 100644 (file)
@@ -116,6 +116,7 @@ web-sys = { version = "0.3", features = [
     "Performance",
 ] }
 js-sys = "0.3"
+ouroboros = "0.18"
 
 [dev-dependencies]
 maplit = "1.0.2"
index 763314ec9709ac31b7435cf9a7060d54435d25dc..4a9dd2cd7cc389a43bd600aab8bf0e2ad666875d 100644 (file)
@@ -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();
 }
index 85ee726ac4dd1ab7a66d86a36037f5e35c5185ad..fcb65ff0cc33d040e3459875c08d79e6c499dcb9 100644 (file)
@@ -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 (file)
index 0000000..d08e186
--- /dev/null
@@ -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<Machine, Receiver<Machine>>,
+}
+
+#[wasm_bindgen(js_class = Machine)]
+impl WasmMachine {
+    #[wasm_bindgen(js_name = runQuery)]
+    pub fn run_query(&mut self, query: String) -> Result<JsValue, JsValue> {
+        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<Machine>,
+    #[covariant]
+    #[borrows(mut machine)]
+    query_state: QueryState<'this>,
+}
+
+#[wasm_bindgen(js_name = QueryState)]
+pub struct WasmQueryState {
+    inner: Option<WasmQueryStateInner>,
+}
+
+#[wasm_bindgen(js_class = QueryState)]
+impl WasmQueryState {
+    #[wasm_bindgen(js_name = next)]
+    pub fn next_answer(&mut self) -> Result<JsValue, JsValue> {
+        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<LeafAnswer> 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<Term> 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(),
+        }
+    }
+}