Rework Wasm interface
This commit is contained in:
58
Cargo.lock
generated
58
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -116,6 +116,7 @@ web-sys = { version = "0.3", features = [
|
||||
"Performance",
|
||||
] }
|
||||
js-sys = "0.3"
|
||||
ouroboros = "0.18"
|
||||
|
||||
[dev-dependencies]
|
||||
maplit = "1.0.2"
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
16
src/lib.rs
16
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;
|
||||
|
||||
320
src/wasm.rs
Normal file
320
src/wasm.rs
Normal file
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user