diff --git a/Cargo.toml b/Cargo.toml
index 16f51100..c1e8ae2e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,14 +12,18 @@ categories = ["command-line-utilities"]
build = "build/main.rs"
rust-version = "1.70"
+[lib]
+crate-type = ["cdylib", "rlib"]
+
[features]
-default = ["ffi", "repl", "hostname", "tls", "http"]
+default = ["ffi", "repl", "hostname", "tls", "http", "crypto-full"]
ffi = ["dep:libffi"]
repl = ["dep:crossterm", "dep:ctrlc", "dep:rustyline"]
hostname = ["dep:hostname"]
tls = ["dep:native-tls"]
http = ["dep:hyper", "dep:reqwest"]
rust_beta_channel = []
+crypto-full = []
[build-dependencies]
indexmap = "1.0.2"
@@ -80,7 +84,19 @@ tokio = { version = "1.28.2", features = ["full"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2.10", features = ["js"] }
-tokio = { version = "1.28.2", features = ["sync", "macros", "io-util", "rt", "time"] }
+tokio = { version = "1.28.2", features = ["sync", "macros", "io-util", "rt"] }
+
+[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",
+]}
[target.'cfg(target_os = "wasi")'.dependencies]
ring-wasi = { version = "0.16.25" }
diff --git a/README.md b/README.md
index bd55c050..49934052 100644
--- a/README.md
+++ b/README.md
@@ -152,6 +152,77 @@ It will generate a very basic MSI file which installs the main executable and a
Scryer Prolog must be built with **Rust 1.63 and up**.
+### Building WebAssembly
+
+Scryer Prolog has basic WebAssembly support. You can follow `wasm-pack`'s [official instructions](https://rustwasm.github.io/docs/wasm-pack/quickstart.html) to install `wasm-pack` and build it in any way you like.
+
+However, none of the [default features](https://doc.rust-lang.org/cargo/reference/features.html#the-default-feature) are currently supported. The preferred way of disabling them is passing [extra options](https://rustwasm.github.io/wasm-pack/book/commands/build.html#extra-options) to `wasm-pack`.
+
+For example, if you want a minimal working package without using any bundler like `webpack`, you can do this:
+```
+wasm-pack build --target web -- --no-default-features
+```
+Then a `pkg` directory will be created, containing everything you need for a webapp. You can test whether the package is successfully built by creating an html file, adapted from `wasm-bindgen`'s [official example](https://rustwasm.github.io/wasm-bindgen/examples/without-a-bundler.html) like this:
+
+```html
+
+
+
+
+ Scryer Prolog - Sudoku Solver Example
+
+
+
+
+```
+
+Then you can serve it with your favorite http server like `python -m http.server` or `npx serve`, and access the page with your browser.
+
### Docker Install
First, install [Docker](https://docs.docker.com/get-docker/) on Linux,
diff --git a/build/instructions_template.rs b/build/instructions_template.rs
index f626d781..4dc9c893 100644
--- a/build/instructions_template.rs
+++ b/build/instructions_template.rs
@@ -496,22 +496,28 @@ enum SystemClauseType {
CryptoDataHKDF,
#[strum_discriminants(strum(props(Arity = "4", Name = "$crypto_password_hash")))]
CryptoPasswordHash,
- #[strum_discriminants(strum(props(Arity = "7", Name = "$crypto_data_encrypt")))]
- CryptoDataEncrypt,
- #[strum_discriminants(strum(props(Arity = "6", Name = "$crypto_data_decrypt")))]
- CryptoDataDecrypt,
#[strum_discriminants(strum(props(Arity = "4", Name = "$crypto_curve_scalar_mult")))]
CryptoCurveScalarMult,
- #[strum_discriminants(strum(props(Arity = "4", Name = "$ed25519_sign")))]
- Ed25519Sign,
- #[strum_discriminants(strum(props(Arity = "4", Name = "$ed25519_verify")))]
- Ed25519Verify,
- #[strum_discriminants(strum(props(Arity = "1", Name = "$ed25519_new_keypair")))]
- Ed25519NewKeyPair,
- #[strum_discriminants(strum(props(Arity = "2", Name = "$ed25519_keypair_public_key")))]
- Ed25519KeyPairPublicKey,
#[strum_discriminants(strum(props(Arity = "3", Name = "$curve25519_scalar_mult")))]
Curve25519ScalarMult,
+ #[cfg(feature = "crypto-full")]
+ #[strum_discriminants(strum(props(Arity = "7", Name = "$crypto_data_encrypt")))]
+ CryptoDataEncrypt,
+ #[cfg(feature = "crypto-full")]
+ #[strum_discriminants(strum(props(Arity = "6", Name = "$crypto_data_decrypt")))]
+ CryptoDataDecrypt,
+ #[cfg(feature = "crypto-full")]
+ #[strum_discriminants(strum(props(Arity = "4", Name = "$ed25519_sign")))]
+ Ed25519Sign,
+ #[cfg(feature = "crypto-full")]
+ #[strum_discriminants(strum(props(Arity = "4", Name = "$ed25519_verify")))]
+ Ed25519Verify,
+ #[cfg(feature = "crypto-full")]
+ #[strum_discriminants(strum(props(Arity = "1", Name = "$ed25519_new_keypair")))]
+ Ed25519NewKeyPair,
+ #[cfg(feature = "crypto-full")]
+ #[strum_discriminants(strum(props(Arity = "2", Name = "$ed25519_keypair_public_key")))]
+ Ed25519KeyPairPublicKey,
#[strum_discriminants(strum(props(Arity = "2", Name = "$first_non_octet")))]
FirstNonOctet,
#[strum_discriminants(strum(props(Arity = "3", Name = "$load_html")))]
@@ -1842,13 +1848,7 @@ fn generate_instruction_preface() -> TokenStream {
&Instruction::CallCryptoDataHash |
&Instruction::CallCryptoDataHKDF |
&Instruction::CallCryptoPasswordHash |
- &Instruction::CallCryptoDataEncrypt |
- &Instruction::CallCryptoDataDecrypt |
&Instruction::CallCryptoCurveScalarMult |
- &Instruction::CallEd25519Sign |
- &Instruction::CallEd25519Verify |
- &Instruction::CallEd25519NewKeyPair |
- &Instruction::CallEd25519KeyPairPublicKey |
&Instruction::CallCurve25519ScalarMult |
&Instruction::CallFirstNonOctet |
&Instruction::CallLoadHTML |
@@ -1905,6 +1905,17 @@ fn generate_instruction_preface() -> TokenStream {
functor!(atom!("call"), [atom(name), fixnum(arity)])
}
//
+ #[cfg(feature = "crypto-full")]
+ &Instruction::CallCryptoDataEncrypt |
+ &Instruction::CallCryptoDataDecrypt |
+ &Instruction::CallEd25519Sign |
+ &Instruction::CallEd25519Verify |
+ &Instruction::CallEd25519NewKeyPair |
+ &Instruction::CallEd25519KeyPairPublicKey => {
+ let (name, arity) = self.to_name_and_arity();
+ functor!(atom!("call"), [atom(name), fixnum(arity)])
+ }
+ //
&Instruction::ExecuteAtomChars |
&Instruction::ExecuteAtomCodes |
&Instruction::ExecuteAtomLength |
@@ -2070,13 +2081,7 @@ fn generate_instruction_preface() -> TokenStream {
&Instruction::ExecuteCryptoDataHash |
&Instruction::ExecuteCryptoDataHKDF |
&Instruction::ExecuteCryptoPasswordHash |
- &Instruction::ExecuteCryptoDataEncrypt |
- &Instruction::ExecuteCryptoDataDecrypt |
&Instruction::ExecuteCryptoCurveScalarMult |
- &Instruction::ExecuteEd25519Sign |
- &Instruction::ExecuteEd25519Verify |
- &Instruction::ExecuteEd25519NewKeyPair |
- &Instruction::ExecuteEd25519KeyPairPublicKey |
&Instruction::ExecuteCurve25519ScalarMult |
&Instruction::ExecuteFirstNonOctet |
&Instruction::ExecuteLoadHTML |
@@ -2133,6 +2138,17 @@ fn generate_instruction_preface() -> TokenStream {
functor!(atom!("execute"), [atom(name), fixnum(arity)])
}
//
+ #[cfg(feature = "crypto-full")]
+ &Instruction::ExecuteCryptoDataEncrypt |
+ &Instruction::ExecuteCryptoDataDecrypt |
+ &Instruction::ExecuteEd25519Sign |
+ &Instruction::ExecuteEd25519Verify |
+ &Instruction::ExecuteEd25519NewKeyPair |
+ &Instruction::ExecuteEd25519KeyPairPublicKey => {
+ let (name, arity) = self.to_name_and_arity();
+ functor!(atom!("execute"), [atom(name), fixnum(arity)])
+ }
+ //
&Instruction::Deallocate => {
functor!(atom!("deallocate"))
}
diff --git a/src/atom_table.rs b/src/atom_table.rs
index ba5a80b4..80ba5411 100644
--- a/src/atom_table.rs
+++ b/src/atom_table.rs
@@ -159,6 +159,7 @@ impl std::ops::Deref for AtomString<'_> {
}
}
+#[cfg(feature = "repl")]
impl rustyline::completion::Candidate for AtomString<'_> {
fn display(&self) -> &str {
self.deref()
diff --git a/src/lib.rs b/src/lib.rs
index 63c818e9..8817fa5f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -40,3 +40,17 @@ pub mod types;
use instructions::instr;
mod rcu;
+
+#[cfg(target_arch = "wasm32")]
+use wasm_bindgen::prelude::*;
+
+#[cfg(target_arch = "wasm32")]
+#[wasm_bindgen]
+pub fn eval_code(s: &str) -> String {
+ use web_sys::console;
+ use machine::mock_wam::*;
+
+ let mut wam = Machine::with_test_streams();
+ let bytes = wam.test_load_string(s);
+ String::from_utf8_lossy(&bytes).to_string()
+}
diff --git a/src/machine/dispatch.rs b/src/machine/dispatch.rs
index 35a73f58..89174091 100644
--- a/src/machine/dispatch.rs
+++ b/src/machine/dispatch.rs
@@ -4742,18 +4742,22 @@ impl Machine {
self.crypto_password_hash();
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
}
+ #[cfg(feature = "crypto-full")]
&Instruction::CallCryptoDataEncrypt => {
self.crypto_data_encrypt();
step_or_fail!(self, self.machine_st.p += 1);
}
+ #[cfg(feature = "crypto-full")]
&Instruction::ExecuteCryptoDataEncrypt => {
self.crypto_data_encrypt();
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
}
+ #[cfg(feature = "crypto-full")]
&Instruction::CallCryptoDataDecrypt => {
self.crypto_data_decrypt();
step_or_fail!(self, self.machine_st.p += 1);
}
+ #[cfg(feature = "crypto-full")]
&Instruction::ExecuteCryptoDataDecrypt => {
self.crypto_data_decrypt();
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
@@ -4766,34 +4770,42 @@ impl Machine {
self.crypto_curve_scalar_mult();
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
}
+ #[cfg(feature = "crypto-full")]
&Instruction::CallEd25519Sign => {
self.ed25519_sign();
step_or_fail!(self, self.machine_st.p += 1);
}
+ #[cfg(feature = "crypto-full")]
&Instruction::ExecuteEd25519Sign => {
self.ed25519_sign();
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
}
+ #[cfg(feature = "crypto-full")]
&Instruction::CallEd25519Verify => {
self.ed25519_verify();
step_or_fail!(self, self.machine_st.p += 1);
}
+ #[cfg(feature = "crypto-full")]
&Instruction::ExecuteEd25519Verify => {
self.ed25519_verify();
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
}
+ #[cfg(feature = "crypto-full")]
&Instruction::CallEd25519NewKeyPair => {
self.ed25519_new_key_pair();
step_or_fail!(self, self.machine_st.p += 1);
}
+ #[cfg(feature = "crypto-full")]
&Instruction::ExecuteEd25519NewKeyPair => {
self.ed25519_new_key_pair();
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
}
+ #[cfg(feature = "crypto-full")]
&Instruction::CallEd25519KeyPairPublicKey => {
self.ed25519_key_pair_public_key();
step_or_fail!(self, self.machine_st.p += 1);
}
+ #[cfg(feature = "crypto-full")]
&Instruction::ExecuteEd25519KeyPairPublicKey => {
self.ed25519_key_pair_public_key();
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
diff --git a/src/machine/mock_wam.rs b/src/machine/mock_wam.rs
index 7be95fbd..cd8f7836 100644
--- a/src/machine/mock_wam.rs
+++ b/src/machine/mock_wam.rs
@@ -332,6 +332,19 @@ impl Machine {
self.load_file(file.into(), stream);
self.user_output.bytes().map(|b| b.unwrap()).collect()
}
+
+ pub fn test_load_string(&mut self, code: &str) -> Vec {
+ use std::io::Read;
+
+ let stream = Stream::from_owned_string(
+ code.to_owned(),
+ &mut self.machine_st.arena,
+ );
+
+ self.load_file("".into(), stream);
+ self.user_output.bytes().map(|b| b.unwrap()).collect()
+ }
+
}
#[cfg(test)]
diff --git a/src/machine/mod.rs b/src/machine/mod.rs
index a7d80e9f..7e456d4e 100644
--- a/src/machine/mod.rs
+++ b/src/machine/mod.rs
@@ -453,9 +453,9 @@ impl Machine {
let user_output = Stream::stdout(&mut machine_st.arena);
let user_error = Stream::stderr(&mut machine_st.arena);
- #[cfg(not(target_os = "wasi"))]
+ #[cfg(not(target_arch = "wasm32"))]
let runtime = tokio::runtime::Runtime::new().unwrap();
- #[cfg(target_os = "wasi")]
+ #[cfg(target_arch = "wasm32")]
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs
index 7b017c17..3e6a7090 100644
--- a/src/machine/system_calls.rs
+++ b/src/machine/system_calls.rs
@@ -60,7 +60,7 @@ use std::process;
use std::str::FromStr;
use chrono::{offset::Local, DateTime};
-#[cfg(not(target_os = "wasi"))]
+#[cfg(not(target_arch = "wasm32"))]
use cpu_time::ProcessTime;
use std::time::{Duration, SystemTime};
@@ -71,8 +71,11 @@ use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use blake2::{Blake2b, Blake2s};
use ring::rand::{SecureRandom, SystemRandom};
+use ring::{digest, hkdf, pbkdf2};
+
+#[cfg(feature = "crypto-full")]
use ring::{
- aead, digest, hkdf, pbkdf2,
+ aead,
signature::{self, KeyPair},
};
use ripemd160::{Digest, Ripemd160};
@@ -4290,7 +4293,7 @@ impl Machine {
self.machine_st.fail = result;
}
- #[cfg(not(target_os = "wasi"))]
+ #[cfg(not(target_arch = "wasm32"))]
#[inline(always)]
pub(crate) fn cpu_now(&mut self) {
let secs = ProcessTime::now().as_duration().as_secs_f64();
@@ -4300,7 +4303,7 @@ impl Machine {
.unify_f64(secs, self.machine_st.registers[1]);
}
- #[cfg(target_os = "wasi")]
+ #[cfg(target_arch = "wasm32")]
#[inline(always)]
pub(crate) fn cpu_now(&mut self) {
// TODO
@@ -7382,6 +7385,7 @@ impl Machine {
unify!(self.machine_st, self.machine_st.registers[4], ints_list);
}
+ #[cfg(feature = "crypto-full")]
#[inline(always)]
pub(crate) fn crypto_data_encrypt(&mut self) {
let encoding = cell_as_atom!(self.deref_register(3));
@@ -7430,6 +7434,7 @@ impl Machine {
);
}
+ #[cfg(feature = "crypto-full")]
#[inline(always)]
pub(crate) fn crypto_data_decrypt(&mut self) {
let data = self.string_encoding_bytes(self.machine_st.registers[1], atom!("octet"));
@@ -7508,6 +7513,7 @@ impl Machine {
unify!(self.machine_st, self.machine_st.registers[4], uncompressed);
}
+ #[cfg(feature = "crypto-full")]
#[inline(always)]
pub(crate) fn ed25519_new_key_pair(&mut self) {
let pkcs8_bytes = signature::Ed25519KeyPair::generate_pkcs8(rng()).unwrap();
@@ -7520,6 +7526,7 @@ impl Machine {
)
}
+ #[cfg(feature = "crypto-full")]
#[inline(always)]
pub(crate) fn ed25519_key_pair_public_key(&mut self) {
let bytes = self.string_encoding_bytes(self.machine_st.registers[1], atom!("octet"));
@@ -7541,6 +7548,7 @@ impl Machine {
);
}
+ #[cfg(feature = "crypto-full")]
#[inline(always)]
pub(crate) fn ed25519_sign(&mut self) {
let key = self.string_encoding_bytes(self.machine_st.registers[1], atom!("octet"));
@@ -7567,6 +7575,7 @@ impl Machine {
unify!(self.machine_st, self.machine_st.registers[4], sig_list);
}
+ #[cfg(feature = "crypto-full")]
#[inline(always)]
pub(crate) fn ed25519_verify(&mut self) {
let key = self.string_encoding_bytes(self.machine_st.registers[1], atom!("octet"));