//! 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(), } } }