diff --git a/Cargo.toml b/Cargo.toml index 7b4ed6d82..244de5b69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ readme = "README.md" name = "ketos" path = "src/bin/repl.rs" doc = false +required-features = ["repl"] [lib] name = "ketos" @@ -25,22 +26,25 @@ path = "src/ketos/lib.rs" [dependencies] byteorder = "1.3" -dirs = "2.0" gumdrop = "0.7" ketos_derive = { version = "0.12", path = "ketos_derive", optional = true } -linefeed = "0.6" num = "0.2" rand = "0.7" serde = { version = "1.0", optional = true } # Used only in `tests/value_derive.rs` serde_derive = { version = "1.0", optional = true } +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +dirs = { version = "2.0", optional = true } +linefeed = { version = "0.6", optional = true } + [dev-dependencies] assert_matches = "1.0" ketos_derive = { version = "0.12", path = "ketos_derive" } [features] -default = [] +default = ["repl"] derive = ["ketos_derive"] +repl = ["dirs", "linefeed"] [workspace] diff --git a/README.md b/README.md index 9baedf243..6451564da 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,16 @@ Build optimized executable: `ketos` can be run as an interpreter to execute Ketos code files (`.ket`) or run as an interactive read-eval-print loop. +## WebAssembly + +This library supports compiling to WebAssembly, but some internal APIs are only +available on the nightly compiler. Include `ketos` in your `Cargo.toml` like so: + +```toml +[dependencies] +ketos = { version = "x.y.z", default-features = false } +``` + ## License Ketos is distributed under the terms of both the MIT license and the diff --git a/src/ketos/interpreter.rs b/src/ketos/interpreter.rs index 5a327f71c..60cba7feb 100644 --- a/src/ketos/interpreter.rs +++ b/src/ketos/interpreter.rs @@ -3,7 +3,9 @@ use std::cell::RefCell; use std::fs::File; use std::io::{stderr, Read, Write}; -use std::path::{Path, PathBuf}; +use std::path::Path; +#[cfg(not(target_arch = "wasm32"))] +use std::path::PathBuf; use std::rc::Rc; use crate::bytecode::Code; @@ -12,7 +14,9 @@ use crate::error::Error; use crate::exec::{call_function, execute, Context, ExecError}; use crate::io::{GlobalIo, IoError, IoMode}; use crate::lexer::{CodeMap, Lexer}; -use crate::module::{BuiltinModuleLoader, FileModuleLoader, ModuleLoader, ModuleRegistry}; +use crate::module::{BuiltinModuleLoader, ModuleLoader, ModuleRegistry}; +#[cfg(not(target_arch = "wasm32"))] +use crate::module::{FileModuleLoader}; use crate::name::{debug_names, display_names, NameStore}; use crate::parser::{ParseError, Parser}; use crate::restrict::RestrictConfig; @@ -48,6 +52,7 @@ pub struct Builder { io: Option>, struct_defs: Option>>, module_loader: Option>, + #[cfg(not(target_arch = "wasm32"))] search_paths: Option>, } @@ -70,6 +75,7 @@ impl Builder { io: None, struct_defs: None, module_loader: None, + #[cfg(not(target_arch = "wasm32"))] search_paths: None, } } @@ -90,6 +96,7 @@ impl Builder { exclude!(self.restrict, "context", "restrict"); exclude!(self.io, "context", "io"); exclude!(self.module_loader, "context", "module_loader"); + #[cfg(not(target_arch = "wasm32"))] exclude!(self.search_paths, "context", "search_paths"); self.context = Some(ctx); @@ -110,6 +117,7 @@ impl Builder { exclude!(self.context, "scope", "context"); exclude!(self.io, "scope", "io"); exclude!(self.module_loader, "scope", "module_loader"); + #[cfg(not(target_arch = "wasm32"))] exclude!(self.search_paths, "scope", "search_paths"); self.scope = Some(scope); @@ -138,6 +146,7 @@ impl Builder { pub fn module_loader(mut self, loader: Box) -> Self { exclude!(self.context, "module_loader", "context"); exclude!(self.scope, "module_loader", "scope"); + #[cfg(not(target_arch = "wasm32"))] exclude!(self.search_paths, "module_loader", "search_paths"); self.module_loader = Some(loader); @@ -145,6 +154,7 @@ impl Builder { } /// Sets the search paths for a new `FileModuleLoader`. + #[cfg(not(target_arch = "wasm32"))] pub fn search_paths(mut self, paths: Vec) -> Self { exclude!(self.context, "search_paths", "context"); exclude!(self.scope, "search_paths", "scope"); @@ -186,6 +196,7 @@ impl Builder { Rc::new(GlobalScope::new(name, names, codemap, modules, io, defs)) } + #[cfg(not(target_arch = "wasm32"))] fn build_loader(&mut self) -> Box { match (self.module_loader.take(), self.search_paths.take()) { (Some(loader), _) => loader, @@ -195,6 +206,14 @@ impl Builder { (None, None) => Box::new(BuiltinModuleLoader) } } + + #[cfg(target_arch = "wasm32")] + fn build_loader(&mut self) -> Box { + match self.module_loader.take() { + Some(loader) => loader, + None => Box::new(BuiltinModuleLoader) + } + } } /// Provides a context in which to compile and execute code. @@ -250,6 +269,7 @@ impl Interpreter { /// Creates a new `Interpreter` that searches for module files in a given /// series of directories. + #[cfg(not(target_arch = "wasm32"))] pub fn with_search_paths(paths: Vec) -> Interpreter { Interpreter::with_loader(Box::new( BuiltinModuleLoader.chain(FileModuleLoader::with_search_paths(paths)))) diff --git a/src/ketos/lib.rs b/src/ketos/lib.rs index a0e37ced9..24cf817b5 100644 --- a/src/ketos/lib.rs +++ b/src/ketos/lib.rs @@ -47,7 +47,9 @@ pub use crate::function::Arity; pub use crate::interpreter::{Builder, Interpreter}; pub use crate::integer::{Integer, Ratio}; pub use crate::io::{File, GlobalIo, IoError, SharedWrite}; -pub use crate::module::{BuiltinModuleLoader, FileModuleLoader, Module, ModuleBuilder, ModuleLoader}; +pub use crate::module::{BuiltinModuleLoader, Module, ModuleBuilder, ModuleLoader}; +#[cfg(not(target_arch = "wasm32"))] +pub use crate::module::{FileModuleLoader}; pub use crate::name::{Name, NameStore}; pub use crate::parser::{ParseError, ParseErrorKind}; pub use crate::restrict::{RestrictConfig, RestrictError}; diff --git a/src/ketos/module.rs b/src/ketos/module.rs index 1bda21d40..d30ae14ae 100644 --- a/src/ketos/module.rs +++ b/src/ketos/module.rs @@ -1,20 +1,29 @@ //! Implements loading named values from code modules. use std::cell::RefCell; +#[cfg(not(target_arch = "wasm32"))] use std::fs::{File, Metadata}; +#[cfg(not(target_arch = "wasm32"))] use std::io::{stderr, Read, Write}; +#[cfg(not(target_arch = "wasm32"))] use std::path::{Path, PathBuf}; use std::rc::Rc; use crate::bytecode::Code; -use crate::compile::{compile, CompileError}; +use crate::compile::CompileError; +#[cfg(not(target_arch = "wasm32"))] +use crate::compile::compile; +#[cfg(not(target_arch = "wasm32"))] use crate::encode::{DecodeError, read_bytecode_file, write_bytecode_file}; use crate::error::Error; use crate::exec::{Context, execute}; use crate::function::{Arity, Function, FunctionImpl, Lambda, SystemFn}; +#[cfg(not(target_arch = "wasm32"))] use crate::io::{IoError, IoMode}; +#[cfg(not(target_arch = "wasm32"))] use crate::lexer::Lexer; use crate::name::{Name, NameMap, NameSetSlice}; +#[cfg(not(target_arch = "wasm32"))] use crate::parser::Parser; use crate::scope::{GlobalScope, ImportSet, Scope}; use crate::value::Value; @@ -341,6 +350,7 @@ fn load_builtin_module(name: Name, scope: &Scope) -> Result { } /// Loads modules from source files and compiled bytecode files. +#[cfg(not(target_arch = "wasm32"))] pub struct FileModuleLoader { /// Tracks import chains to prevent infinite recursion chain: RefCell>, @@ -358,6 +368,7 @@ pub const FILE_EXTENSION: &str = "ket"; /// File extension for `ketos` compiled bytecode files. pub const COMPILED_FILE_EXTENSION: &str = "ketc"; +#[cfg(not(target_arch = "wasm32"))] impl FileModuleLoader { /// Creates a new `FileModuleLoader` that will search the current /// directory for modules. @@ -407,6 +418,7 @@ impl FileModuleLoader { } } +#[cfg(not(target_arch = "wasm32"))] impl ModuleLoader for FileModuleLoader { fn load_module(&self, name: Name, ctx: Context) -> Result { let (src_fname, code_fname) = ctx.scope().with_name(name, |name_str| { @@ -474,12 +486,14 @@ impl ModuleLoader for FileModuleLoader { } #[derive(Copy, Clone)] +#[cfg(not(target_arch = "wasm32"))] enum ModuleFileResult { NotFound, UseCode, UseSource, } +#[cfg(not(target_arch = "wasm32"))] fn find_module_file(src_path: &Path, code_path: &Path) -> Result { match (code_path.exists(), src_path.exists()) { (true, true) if is_younger(code_path, src_path)? => @@ -490,6 +504,7 @@ fn find_module_file(src_path: &Path, code_path: &Path) -> Result ModuleFileResult { if src_path.exists() { ModuleFileResult::UseSource @@ -498,6 +513,7 @@ fn find_source_file(src_path: &Path) -> ModuleFileResult { } } +#[cfg(not(target_arch = "wasm32"))] fn is_younger(a: &Path, b: &Path) -> Result { let ma = a.metadata() .map_err(|e| IoError::new(IoMode::Stat, a, e))?; @@ -519,6 +535,7 @@ fn is_younger_impl(ma: &Metadata, mb: &Metadata) -> bool { ma.last_write_time() > mb.last_write_time() } +#[cfg(not(target_arch = "wasm32"))] fn load_module_from_file(ctx: Context, name: Name, src_path: &Path, code_path: Option<&Path>) -> Result { let mut file = File::open(src_path) @@ -611,6 +628,7 @@ fn process_imports(ctx: &Context, imports: &[ImportSet]) -> Result<(), Error> { Ok(()) } +#[cfg(not(target_arch = "wasm32"))] fn check_exports(scope: &Scope, mod_name: Name) -> Result<(), CompileError> { scope.with_exports(|exports| { for name in exports { diff --git a/tests/encode.rs b/tests/encode.rs index 788daf9fe..1bfa5aaa0 100644 --- a/tests/encode.rs +++ b/tests/encode.rs @@ -1,3 +1,5 @@ +#![cfg(not(target_arch = "wasm32"))] + #[macro_use] extern crate assert_matches; extern crate ketos; diff --git a/tests/run.rs b/tests/run.rs index db1623e03..74b207bde 100644 --- a/tests/run.rs +++ b/tests/run.rs @@ -1,3 +1,5 @@ +#![cfg(not(target_arch = "wasm32"))] + extern crate ketos; use ketos::{ diff --git a/tests/value_encode.rs b/tests/value_encode.rs index 41b5991fd..c13c11002 100644 --- a/tests/value_encode.rs +++ b/tests/value_encode.rs @@ -1,4 +1,4 @@ -#![cfg(all(feature = "serde", feature = "serde_derive"))] +#![cfg(all(feature = "serde", feature = "serde_derive", not(target_arch = "wasm32")))] extern crate ketos; extern crate serde;