diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1a8c77a..84c9ccc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,11 @@
## Version
- Restructure of repository
+- Libraries can now be cached and not dropped once evaluation is finished.
+- Removed ability to redirect output of evaluation.
+ - This functionality is broken and requires
+ a. More testing
+ b. Better use case
## 0.12.0
- Handle `Item::Use` when parsing input.
diff --git a/Cargo.lock b/Cargo.lock
index 7c91191..30f5efc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -114,6 +114,14 @@ name = "byteorder"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+[[package]]
+name = "c2-chacha"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
[[package]]
name = "cast"
version = "0.2.3"
@@ -629,7 +637,6 @@ dependencies = [
"colored 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"criterion 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossterm 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -637,10 +644,10 @@ dependencies = [
"libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"racer 2.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
- "shh 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"term_cursor 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -667,6 +674,11 @@ dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
+[[package]]
+name = "ppv-lite86"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
[[package]]
name = "proc-macro2"
version = "0.4.30"
@@ -740,6 +752,27 @@ dependencies = [
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
]
+[[package]]
+name = "rand"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
[[package]]
name = "rand_core"
version = "0.3.1"
@@ -761,6 +794,14 @@ dependencies = [
"getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
[[package]]
name = "rand_os"
version = "0.1.3"
@@ -1124,15 +1165,6 @@ dependencies = [
"serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
]
-[[package]]
-name = "shh"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
[[package]]
name = "signal-hook"
version = "0.1.12"
@@ -1281,6 +1313,14 @@ name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+[[package]]
+name = "uuid"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
[[package]]
name = "vec_map"
version = "0.8.1"
@@ -1373,6 +1413,7 @@ dependencies = [
"checksum blake2b_simd 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b83b7baab1e671718d78204225800d6b170e648188ac7dc992e9d6bddf87d0c0"
"checksum bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8d6c2c5b58ab920a4f5aeaaca34b4488074e8cc7596af94e6f8c6ff247c60245"
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
+"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb"
"checksum cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0"
"checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
@@ -1432,6 +1473,7 @@ dependencies = [
"checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72"
"checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252"
"checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b"
+"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27"
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
@@ -1440,9 +1482,12 @@ dependencies = [
"checksum racer 2.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6d7ffceb4da3e0a29c18986f0469c209f4db3ab9f2ffe286eaa1104a3e5028"
"checksum racer-cargo-metadata 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2b60cd72291a641dbaa649e9e328df552186dda1fea834c55cf28594a25b7c6f"
"checksum racer-interner 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "206a244afd319767bdf97cf4e94c0d5d3b1de9cb23fd25434e7992cca4d4fa4c"
+"checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412"
+"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853"
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
"checksum rand_os 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a788ae3edb696cfcba1c19bfd388cc4b8c21f8a408432b199c072825084da58a"
"checksum rand_xoshiro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0e18c91676f670f6f0312764c759405f13afb98d5d73819840cf72a518487bff"
@@ -1481,7 +1526,6 @@ dependencies = [
"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
"checksum serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)" = "48c575e0cc52bdd09b47f330f646cf59afc586e9c4e3ccd6fc1f625b8ea1dad7"
-"checksum shh 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9cff8b151d3d290604d9c93f0400bd4d98e2837a5461532bc6c95d0129ea0964"
"checksum signal-hook 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "7a9c17dd3ba2d36023a5c9472ecddeda07e27fd0b05436e8c1e0c8f178185652"
"checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41"
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
@@ -1501,6 +1545,7 @@ dependencies = [
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
+"checksum uuid 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e"
"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d"
diff --git a/Cargo.toml b/Cargo.toml
index f19bcd7..a22b43e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,13 +19,11 @@ codecov = { repository = "kurtlawrence/papyrus" }
# My crates
cmdtree = { version = "0.10", default-features = false }
kserd = { version = "0.1", default-features = false, optional = false, features = [ "format" ] }
-shh = { version = "1", default-features = false }
# crates.io
backtrace = { version = "0.3", default-features = false }
colored = { version = "1", default-features = false }
crossbeam-channel = { version = "0.4", default-features = false }
-crossbeam-utils = { version = "0.7", default-features = false }
crossterm = { version = "0.14", default-features = false, optional = true }
dirs = { version = "2", default-features = false }
fxhash = { version = "0.2", default-features = false }
@@ -33,6 +31,7 @@ libloading = { version = "0.5", default-features = false }
log = { version = "0.4", default-features = false }
racer = { version = "2.1", default-features = false, optional = true, features = [ "metadata" ] }
syn = { version = "1", default-features = false, optional = false, features = [ "full", "printing", "parsing" ] }
+uuid = { version = "0.8", default-features = false, optional = false, features = [ "v4" ] }
[dev-dependencies]
criterion = "0.3"
diff --git a/docs/modocs/repl.md b/docs/modocs/repl.md
deleted file mode 100644
index 39c8b0d..0000000
Binary files a/docs/modocs/repl.md and /dev/null differ
diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md
index 0c876dd..151e912 100644
--- a/docs/src/SUMMARY.md
+++ b/docs/src/SUMMARY.md
@@ -5,5 +5,4 @@
- [Commands](api.cmds.md)
- [Output](api.output.md)
- [Linking](api.linking.md)
- - [REPL](api.repl.md)
diff --git a/docs/src/api.repl.md b/docs/src/api.repl.md
deleted file mode 100644
index 7f44770..0000000
--- a/docs/src/api.repl.md
+++ /dev/null
@@ -1,4 +0,0 @@
-# REPL
-
-{{#include ../modocs/repl.md}}
-
diff --git a/docs/src/chapter_1.md b/docs/src/chapter_1.md
deleted file mode 100644
index b743fda..0000000
--- a/docs/src/chapter_1.md
+++ /dev/null
@@ -1 +0,0 @@
-# Chapter 1
diff --git a/modoc.config b/modoc.config
index c3f1f6f..70c6f77 100644
--- a/modoc.config
+++ b/modoc.config
@@ -2,5 +2,4 @@
"docs/modocs/cmds.md" = [ "src/cmds/mod.rs" ]
"docs/modocs/code.md" = [ "src/code.rs" ]
"docs/modocs/linking.md" = [ "src/linking.rs" ]
-"docs/modocs/repl.md" = [ "src/repl/mod.rs" ]
"docs/modocs/output.md" = [ "src/output/mod.rs" ]
diff --git a/src/compile/build.rs b/src/compile/build.rs
index 606d5b7..878d259 100644
--- a/src/compile/build.rs
+++ b/src/compile/build.rs
@@ -1,107 +1,145 @@
-use super::LIBRARY_NAME;
-use std::io::{self, BufRead, BufReader};
-use std::path::{Path, PathBuf};
-use std::process::{Command, Stdio};
-use std::{error, fmt};
-
-/// Run `rustc` in the given compilation directory.
-pub fn compile
(
- compile_dir: P,
- linking_config: &crate::linking::LinkingConfiguration,
- mut stderr_line_cb: F,
-) -> Result
-where
- P: AsRef,
- F: FnMut(&str),
-{
- let compile_dir = compile_dir.as_ref();
- let lib_file = compile_dir.join("target/debug/");
- let lib_file = if cfg!(windows) {
- lib_file.join(format!("{}.dll", LIBRARY_NAME))
- } else {
- lib_file.join(format!("lib{}.so", LIBRARY_NAME))
- };
-
- let mut args = vec!["rustc".to_owned(), "--".to_owned(), "-Awarnings".to_owned()];
-
- for external in linking_config.external_libs.iter() {
- args.push("-L".to_owned());
- args.push(format!("dependency={}", external.deps_path().display()));
- args.push("--extern".to_owned());
- args.push(format!(
- "{}={}",
- external.lib_name(),
- external.lib_path().display()
- ));
- }
-
- let mut child = Command::new("cargo")
- .current_dir(compile_dir)
- .args(&args)
- .stdout(Stdio::piped())
- .stderr(Stdio::piped())
- .spawn()
- .map_err(|_| CompilationError::NoBuildCommand)?;
-
- let stderr = {
- let rdr = BufReader::new(child.stderr.as_mut().expect("stderr should be piped"));
- let mut s = String::new();
- for line in rdr.lines() {
- let line = line.unwrap();
- stderr_line_cb(&line);
- s.push_str(&line);
- s.push('\n');
- }
- s
- };
-
- match child.wait() {
- Ok(ex) => {
- if ex.success() {
- Ok(lib_file)
- } else {
- Err(CompilationError::CompileError(stderr))
- }
- }
- Err(e) => Err(CompilationError::IOError(e)),
- }
-}
-
-/// Error type for compilation.
-#[derive(Debug)]
-pub enum CompilationError {
- /// Failed to initialise `cargo build`. Usually because `cargo` is not in your `PATH` or Rust is not installed.
- NoBuildCommand,
- /// A compiling error occured, with the contents of the stderr.
- CompileError(String),
- /// Generic IO errors.
- IOError(io::Error),
-}
-
-impl error::Error for CompilationError {}
-
-impl fmt::Display for CompilationError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- CompilationError::NoBuildCommand => {
- write!(f, "cargo build command failed to start, is rust installed?")
- }
- CompilationError::CompileError(e) => write!(f, "{}", e),
- CompilationError::IOError(e) => write!(f, "io error occurred: {}", e),
- }
- }
-}
-
-#[test]
-fn compilation_error_fmt_test() {
- let e = CompilationError::NoBuildCommand;
- assert_eq!(
- &e.to_string(),
- "cargo build command failed to start, is rust installed?"
- );
- let e = CompilationError::CompileError("compile err".to_string());
- assert_eq!(&e.to_string(), "compile err");
- let ioe = io::Error::new(io::ErrorKind::Other, "test");
- let e = CompilationError::IOError(ioe);
- assert_eq!(&e.to_string(), "io error occurred: test");
-}
+use super::LIBRARY_NAME;
+use std::io::{self, BufRead, BufReader};
+use std::path::{Path, PathBuf};
+use std::process::{Command, Stdio};
+use std::{error, fmt};
+
+/// Run `rustc` in the given compilation directory.
+pub fn compile(
+ compile_dir: P,
+ linking_config: &crate::linking::LinkingConfiguration,
+ mut stderr_line_cb: F,
+) -> Result
+where
+ P: AsRef,
+ F: FnMut(&str),
+{
+ let compile_dir = compile_dir.as_ref();
+ let lib_file = compile_dir.join("target/debug/");
+ let lib_file = if cfg!(windows) {
+ lib_file.join(format!("{}.dll", LIBRARY_NAME))
+ } else {
+ lib_file.join(format!("lib{}.so", LIBRARY_NAME))
+ };
+
+ let mut args = vec!["rustc".to_owned(), "--".to_owned(), "-Awarnings".to_owned()];
+
+ for external in linking_config.external_libs.iter() {
+ args.push("-L".to_owned());
+ args.push(format!("dependency={}", external.deps_path().display()));
+ args.push("--extern".to_owned());
+ args.push(format!(
+ "{}={}",
+ external.lib_name(),
+ external.lib_path().display()
+ ));
+ }
+
+ let mut child = Command::new("cargo")
+ .current_dir(compile_dir)
+ .args(&args)
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
+ .spawn()
+ .map_err(|_| CompilationError::NoBuildCommand)?;
+
+ let stderr = {
+ let rdr = BufReader::new(child.stderr.as_mut().expect("stderr should be piped"));
+ let mut s = String::new();
+ for line in rdr.lines() {
+ let line = line.unwrap();
+ stderr_line_cb(&line);
+ s.push_str(&line);
+ s.push('\n');
+ }
+ s
+ };
+
+ match child.wait() {
+ Ok(ex) => {
+ if ex.success() {
+ Ok(lib_file)
+ } else {
+ Err(CompilationError::CompileError(stderr))
+ }
+ }
+ Err(e) => Err(CompilationError::IOError(e)),
+ }
+}
+
+/// Function to rename the output library file and remove the associated dependency.
+///
+/// In relation to [#44](https://github.com/kurtlawrence/papyrus/issues/44), loading a library will
+/// effectively lock a library file. This is especially pervasive in windows where the `.dll` locks
+/// in both the target directory and the inner `deps` folder. The locking makes subsequent
+/// compilations fail with io errors.
+///
+/// To separate the locking from the loading, the compiled library is renamed _then_ loaded. This
+/// function renames the specified library with a random UUID. It also deletes the similarly name
+/// library in the `deps` folder. _If there is no `deps` folder, or no library inside the folder,
+/// the deletion silently fails_. This step is required for Windows but the behaviour is kept
+/// standard across platforms.
+pub fn unshackle_library_file>(libpath: P) -> PathBuf {
+ let libpath = libpath.as_ref();
+ let lib = libpath.file_name().expect("there should be a file name");
+ let depsfile = libpath
+ .parent()
+ .expect("there will be parent")
+ .join("deps")
+ .join(lib);
+ if depsfile.exists() {
+ std::fs::remove_file(depsfile).ok(); // allow deps files removal to fail
+ }
+ rename_lib_file(libpath).unwrap_or_else(|_| libpath.to_owned())
+}
+
+fn rename_lib_file>(compiled_lib: P) -> io::Result {
+ let no_parent = PathBuf::new();
+ let parent = compiled_lib.as_ref().parent().unwrap_or(&no_parent);
+ let name = || format!("papyrus.{}.lib", uuid::Uuid::new_v4().to_hyphenated());
+ let mut lib_path = parent.join(&name());
+ while lib_path.exists() {
+ lib_path = parent.join(&name());
+ }
+ std::fs::rename(&compiled_lib, &lib_path)?;
+ Ok(lib_path)
+}
+
+/// Error type for compilation.
+#[derive(Debug)]
+pub enum CompilationError {
+ /// Failed to initialise `cargo build`. Usually because `cargo` is not in your `PATH` or Rust is not installed.
+ NoBuildCommand,
+ /// A compiling error occured, with the contents of the stderr.
+ CompileError(String),
+ /// Generic IO errors.
+ IOError(io::Error),
+}
+
+impl error::Error for CompilationError {}
+
+impl fmt::Display for CompilationError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ CompilationError::NoBuildCommand => {
+ write!(f, "cargo build command failed to start, is rust installed?")
+ }
+ CompilationError::CompileError(e) => write!(f, "{}", e),
+ CompilationError::IOError(e) => write!(f, "io error occurred: {}", e),
+ }
+ }
+}
+
+#[test]
+fn compilation_error_fmt_test() {
+ let e = CompilationError::NoBuildCommand;
+ assert_eq!(
+ &e.to_string(),
+ "cargo build command failed to start, is rust installed?"
+ );
+ let e = CompilationError::CompileError("compile err".to_string());
+ assert_eq!(&e.to_string(), "compile err");
+ let ioe = io::Error::new(io::ErrorKind::Other, "test");
+ let e = CompilationError::IOError(ioe);
+ assert_eq!(&e.to_string(), "io error occurred: test");
+}
diff --git a/src/compile/execute.rs b/src/compile/execute.rs
index 58d294f..20b5a42 100644
--- a/src/compile/execute.rs
+++ b/src/compile/execute.rs
@@ -1,135 +1,56 @@
-use ::kserd::Kserd;
-use libloading::{Library, Symbol};
-use std::io::{self, Write};
-use std::path::Path;
-
-/// We don't type anything here. You must be **VERY** careful to pass through the correct borrow to match the
-/// function signature!
-type DataFunc = unsafe fn(D) -> Kserd<'static>;
-
-type ExecResult = Result, &'static str>;
-
-pub(crate) fn exec, D, W: Write + Send>(
- library_file: P,
- function_name: &str,
- app_data: D,
- wtr: Option<&mut W>,
-) -> ExecResult {
- if let Some(wtr) = wtr {
- exec_and_redirect(library_file, function_name, app_data, wtr)
- } else {
- exec_no_redirect(library_file, function_name, app_data)
- }
-}
-
-fn exec_no_redirect, Data>(
- library_file: P,
- function_name: &str,
- app_data: Data,
-) -> ExecResult {
- let lib = get_lib(library_file)?;
- let func = get_func(&lib, function_name)?;
-
- let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe { func(app_data) }));
-
- match res {
- Ok(kserd) => Ok(kserd),
- Err(_) => Err("a panic occured with evaluation"),
- }
-}
-
-fn exec_and_redirect, Data, W: Write + Send>(
- library_file: P,
- function_name: &str,
- app_data: Data,
- output_wtr: &mut W,
-) -> ExecResult {
- let lib = get_lib(library_file)?;
- let func = get_func(&lib, function_name)?;
-
- let (tx, rx) = crossbeam_channel::bounded(0);
-
- let (stdout_gag, stderr_gag) =
- get_gags().map_err(|_| "failed to apply redirect gags on stdout and stderr")?;
-
- let res = crossbeam_utils::thread::scope(|scope| {
- let jh = if cfg!(debug_assertions) {
- // don't redirect on debug builds, such that dbg!() can print through to terminal for debugging.
- drop(stderr_gag);
- scope.spawn(|_| redirect_output_loop(output_wtr, rx, stdout_gag, std::io::empty()))
- } else {
- scope.spawn(|_| redirect_output_loop(output_wtr, rx, stdout_gag, stderr_gag))
- };
-
- let res =
- std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe { func(app_data) }));
-
- tx.send(()).expect("sending signal to stop gagging failed");
- jh.join()
- .expect("joining gagging thread failed")
- .expect("failed to redirect output loop");
- res
- });
-
- let res = res.map_err(|_| "crossbeam scoping failed")?;
-
- match res {
- Ok(kserd) => Ok(kserd),
- Err(_) => Err("a panic occured with evaluation"),
- }
-}
-
-fn get_lib>(path: P) -> Result {
- // If segfaults are occurring maybe use this, SIGSEV?
- // This is shown in https://github.com/nagisa/rust_libloading/issues/41
- // let lib: Library =
- // libloading::os::unix::Library::open(Some(library_file.as_ref()), 0x2 | 0x1000)
- // .unwrap()
- // .into();
- Library::new(path.as_ref()).map_err(|e| {
- error!("failed to load library file: {}", e);
- "failed to load library file"
- })
-}
-
-fn get_func<'l, Data>(
- lib: &'l Library,
- name: &str,
-) -> Result>, &'static str> {
- unsafe {
- lib.get(name.as_bytes())
- .map_err(|_| "failed to find function in library")
- }
-}
-
-fn get_gags() -> io::Result<(shh::ShhStdout, shh::ShhStderr)> {
- Ok((shh::stdout()?, shh::stderr()?))
-}
-
-fn redirect_output_loop(
- wtr: &mut W,
- rx: crossbeam_channel::Receiver<()>,
- mut stdout_gag: R1,
- mut stderr_gag: R2,
-) -> io::Result<()> {
- loop {
- std::thread::sleep(std::time::Duration::from_millis(2)); // add in some delay as reading occurs to avoid smashing cpu.
-
- let mut buf = Vec::new();
-
- // read/write stderr first
- stderr_gag.read_to_end(&mut buf)?;
-
- stdout_gag.read_to_end(&mut buf)?;
-
- wtr.write_all(&buf)?;
-
- match rx.try_recv() {
- Ok(_) => break, // stop signal sent
- Err(crossbeam_channel::TryRecvError::Disconnected) => break, // tx dropped
- _ => (),
- };
- }
-
- Ok(())
-}
+use ::kserd::Kserd;
+use libloading::{Library, Symbol};
+use std::path::Path;
+
+/// We don't type anything here. You must be **VERY** careful to pass through the correct borrow to match the
+/// function signature!
+type DataFunc = unsafe fn(D) -> Kserd<'static>;
+
+type ExecResult = Result<(Kserd<'static>, Library), &'static str>;
+
+pub(crate) fn exec, D>(
+ library_file: P,
+ function_name: &str,
+ app_data: D,
+) -> ExecResult {
+ exec_no_redirect(library_file, function_name, app_data)
+}
+
+fn exec_no_redirect, Data>(
+ library_file: P,
+ function_name: &str,
+ app_data: Data,
+) -> ExecResult {
+ let lib = get_lib(library_file)?;
+ let func = get_func(&lib, function_name)?;
+
+ let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe { func(app_data) }));
+
+ match res {
+ Ok(kserd) => Ok((kserd, lib)),
+ Err(_) => Err("a panic occured with evaluation"),
+ }
+}
+
+fn get_lib>(path: P) -> Result {
+ // If segfaults are occurring maybe use this, SIGSEV?
+ // This is shown in https://github.com/nagisa/rust_libloading/issues/41
+ // let lib: Library =
+ // libloading::os::unix::Library::open(Some(library_file.as_ref()), 0x2 | 0x1000)
+ // .unwrap()
+ // .into();
+ Library::new(path.as_ref()).map_err(|e| {
+ error!("failed to load library file: {}", e);
+ "failed to load library file"
+ })
+}
+
+fn get_func<'l, Data>(
+ lib: &'l Library,
+ name: &str,
+) -> Result>, &'static str> {
+ unsafe {
+ lib.get(name.as_bytes())
+ .map_err(|_| "failed to find function in library")
+ }
+}
diff --git a/src/compile/mod.rs b/src/compile/mod.rs
index 6da7058..a0183e9 100644
--- a/src/compile/mod.rs
+++ b/src/compile/mod.rs
@@ -4,7 +4,7 @@ mod build;
mod construct;
mod execute;
-pub use self::build::{compile, CompilationError};
+pub use self::build::{compile, unshackle_library_file, CompilationError};
pub use self::construct::build_compile_dir;
pub(crate) use self::execute::exec;
@@ -36,9 +36,9 @@ mod tests {
let path = compile(&compile_dir, &linking_config, |_| ()).unwrap();
// eval
- let r = exec::<_, _, std::io::Sink>(path, "_lib_intern_eval", &(), None).unwrap(); // execute library fn
+ let r = exec::<_, _>(path, "_lib_intern_eval", &()).unwrap(); // execute library fn
- assert_eq!(r, Kserd::new_num(4));
+ assert_eq!(r.0, Kserd::new_num(4));
}
#[test]
@@ -61,9 +61,9 @@ mod tests {
let path = compile(&compile_dir, &linking_config, |_| ()).unwrap();
// eval
- let r = exec::<_, _, std::io::Sink>(path, "_lib_intern_eval", &(), None).unwrap(); // execute library fn
+ let r = exec::<_, _>(path, "_lib_intern_eval", &()).unwrap(); // execute library fn
- assert_eq!(r, Kserd::new_num(4));
+ assert_eq!(r.0, Kserd::new_num(4));
}
#[test]
@@ -92,9 +92,9 @@ mod tests {
let path = compile(&compile_dir, &linking_config, |_| ()).unwrap();
// eval
- let r = exec::<_, _, std::io::Sink>(path, "_lib_intern_eval", &(), None).unwrap(); // execute library fn
+ let r = exec::<_, _>(path, "_lib_intern_eval", &()).unwrap(); // execute library fn
- assert_eq!(r, Kserd::new_num(4));
+ assert_eq!(r.0, Kserd::new_num(4));
}
#[test]
@@ -123,9 +123,9 @@ mod tests {
let path = compile(&compile_dir, &linking_config, |_| ()).unwrap();
// eval
- let r = exec(path, "_lib_intern_eval", &(), Some(&mut std::io::sink())).unwrap(); // execute library fn
+ let r = exec(path, "_lib_intern_eval", &()).unwrap(); // execute library fn
- assert_eq!(r, Kserd::new_num(4));
+ assert_eq!(r.0, Kserd::new_num(4));
}
#[test]
diff --git a/src/linking.rs b/src/linking.rs
index ae25595..9ef478e 100644
--- a/src/linking.rs
+++ b/src/linking.rs
@@ -147,7 +147,7 @@
//! Papyrus uses `AssertUnwindSafe` wrappers to make this work, however it makes `app_data` vulnerable to breaking
//! invariant states if a panic is triggered.
//!
-//! The developer should keep this in mind when implementing a linked REPL.
+//! The developer should keep this in mind when implementing a linked REPL.
//! Some guidelines:
//!
//! 1. Keep the app_data that is being transfered simple.
diff --git a/src/repl/data.rs b/src/repl/data.rs
index dc7685e..0773dc4 100644
--- a/src/repl/data.rs
+++ b/src/repl/data.rs
@@ -17,9 +17,10 @@ impl Default for ReplData {
out_colour: Color::BrightGreen,
compilation_dir: default_compile_dir(),
linking: LinkingConfiguration::default(),
- redirect_on_execution: true,
editing: None,
editing_src: None,
+ loadedlibs: VecDeque::new(),
+ loaded_libs_size_limit: 0,
};
r.with_cmdtree_builder(Builder::new("papyrus"))
@@ -78,6 +79,16 @@ impl ReplData {
&self.linking
}
+ /// Clears the cached loaded libraries.
+ ///
+ /// This can be used to clear resources. Loaded libraries are stored up to the
+ /// [`loaded_libs_size_limit`] but can be cleared earlier if need be.
+ ///
+ /// [`loaded_libs_size_limit`]: ReplData
+ pub fn clear_loaded_libs(&mut self) {
+ self.loadedlibs.clear()
+ }
+
/// Not meant to used by developer. Use the macros instead.
/// [See _linking_ module](../pfh/linking.html)
pub unsafe fn set_data_type(mut self, data_type: &str) -> Self {
diff --git a/src/repl/eval.rs b/src/repl/eval.rs
index 55c61d3..f519fcd 100644
--- a/src/repl/eval.rs
+++ b/src/repl/eval.rs
@@ -1,435 +1,435 @@
-use super::*;
-use crate::{
- cmds::{self, CommandResult},
- code::{self, Input, SourceCode, StmtGrp},
- compile,
-};
-use std::borrow::{Borrow, BorrowMut};
-use std::ops::{Deref, DerefMut};
-use std::path::Path;
-use std::sync::{Arc, Mutex};
-
-impl Repl {
- /// Evaluates the read input, compiling and executing the code and printing all line prints until
- /// a result is found. This result gets passed back as a print ready repl.
- pub fn eval(self, app_data: &mut D) -> EvalResult {
- use std::cell::Cell;
- use std::rc::Rc;
-
- let ptr = Rc::into_raw(Rc::new(app_data));
-
- // as I am playing around with pointers here, I am going to do assertions in the rebuilding
- // if from_raw is called more than once, it is memory unsafe, so count the calls and assert it is only 1
- let rebuilds: Rc> = Rc::new(Cell::new(0));
-
- let func = || {
- let b = Rc::clone(&rebuilds);
-
- let n = b.get();
-
- assert_eq!(n, 0, "unsafe memory operation, can only rebuild Rc once.");
-
- b.set(n + 1);
-
- let c = unsafe { Rc::from_raw(ptr) };
-
- Rc::try_unwrap(c)
- .map_err(|_| "there should only be one strong reference")
- .unwrap()
- };
-
- map_variants(self, func, func)
- }
-
- /// Begin listening to line change events on the output.
- pub fn output_listen(&mut self) -> output::Receiver {
- self.state.output.listen()
- }
-
- /// Close the sender side of the output channel.
- pub fn close_channel(&mut self) {
- self.state.output.close()
- }
-
- /// The current output.
- ///
- /// The output contains colouring ANSI escape codes, the prompt, and all input.
- pub fn output(&self) -> &str {
- self.state.output.buffer()
- }
-}
-
-impl Repl {
- /// Same as `eval` but will evaluate on another thread, not blocking this one.
- ///
- /// An `Arc::clone` will be taken of `app_data`.
- pub fn eval_async(self, app_data: &Arc>) -> Evaluating {
- let (tx, rx) = crossbeam_channel::bounded(1);
-
- let clone = Arc::clone(app_data);
-
- std::thread::spawn(move || {
- let eval = map_variants(
- self,
- || clone.lock().expect("failed getting lock of data"),
- || clone.lock().expect("failed getting lock of data"),
- );
-
- tx.send(eval).unwrap();
- });
-
- Evaluating { jh: rx }
- }
-}
-
-impl Evaluating {
- /// Evaluating has finished.
- pub fn completed(&self) -> bool {
- !self.jh.is_empty()
- }
-
- /// Waits for the evaluating to finish before return the result.
- /// If evaluating is `completed` this will return immediately.
- pub fn wait(self) -> EvalResult {
- self.jh
- .recv()
- .expect("receiving eval result from async thread failed")
- }
-}
-
-fn map_variants(
- repl: Repl,
- obtain_mut_data: Fmut,
- obtain_brw_data: Fbrw,
-) -> EvalResult
-where
- Fmut: FnOnce() -> Rmut,
- Rmut: DerefMut,
- Fbrw: FnOnce() -> Rbrw,
- Rbrw: Deref,
-{
- let Repl {
- state,
- mut data,
- more,
- data_mrker,
- } = repl;
-
- let Evaluate { mut output, result } = state;
-
- let mut keep_mutating = false; // default to stop mutating phase
- // can't cancel before as handle program requires it for decisions
-
- // map variants into Result
- let mapped = match result {
- InputResult::Command(cmds) => {
- let r = data.handle_command(&cmds, &mut output, obtain_mut_data);
- keep_mutating = data.linking.mutable; // a command can alter the mutating state, needs to persist
- r.map(|x| EvalOutput::Print(x))
- }
- InputResult::Program(input) => {
- Ok(data.handle_program(input, &mut output, obtain_mut_data, obtain_brw_data))
- }
- InputResult::InputError(err) => Ok(EvalOutput::Print(Cow::Owned(err))),
- InputResult::Eof => Err(Signal::Exit),
- _ => Ok(EvalOutput::Print(Cow::Borrowed(""))),
- };
-
- let (eval_output, sig) = match mapped {
- Ok(hir) => (hir, Signal::None),
- Err(sig) => (EvalOutput::Print(Cow::Borrowed("")), sig),
- };
-
- data.linking.mutable = keep_mutating; // always cancel a mutating block on evaluation??
- // the alternative would be to keep alive on compilation failures, might not for now though.
- // this would have to be individually handled in each match arm and it, rather let the user
- // have to reinstate mutability if they fuck up input.
-
- EvalResult {
- signal: sig,
- repl: Repl {
- state: Print {
- output,
- data: eval_output,
- },
- data,
- more,
- data_mrker,
- },
- }
-}
-
-impl ReplData {
- fn handle_command(
- &mut self,
- cmds: &str,
- writer: &mut W,
- obtain_mut_app_data: F,
- ) -> Result, Signal>
- where
- F: FnOnce() -> R,
- R: DerefMut,
- W: io::Write,
- {
- use cmdtree::LineResult as lr;
-
- let tuple = match self.cmdtree.parse_line(cmds, true, writer) {
- lr::Exit => return Err(Signal::Exit),
- lr::Cancel => {
- self.linking.mutable = false; // reset the mutating on cancel
- self.editing = None; // reset the editing on cancel
- Cow::Borrowed("cancelled input and returned to root")
- }
- lr::Action(res) => match res {
- CommandResult::BeginMutBlock => {
- self.linking.mutable = true;
- Cow::Borrowed("beginning mut block")
- }
- CommandResult::EditAlter(ei) => Cow::Borrowed(cmds::edit_alter(self, ei)),
- CommandResult::EditReplace(ei, val) => {
- let r = Cow::Borrowed(cmds::edit_alter(self, ei));
-
- if r.is_empty() {
- return Err(Signal::ReEvaluate(val));
- } else {
- r
- }
- }
- CommandResult::SwitchModule(path) => {
- Cow::Borrowed(crate::cmds::switch_module(self, &path))
- }
-
- CommandResult::ActionOnReplData(action) => Cow::Owned(action(self, writer)),
- CommandResult::ActionOnAppData(action) => {
- let mut r = obtain_mut_app_data();
- let app_data: &mut D = r.borrow_mut();
- let s = action(app_data, writer);
- Cow::Owned(s)
- }
- CommandResult::Empty => Cow::Borrowed(""),
- },
- _ => Cow::Borrowed(""),
- };
-
- Ok(tuple)
- }
-
- fn handle_program(
- &mut self,
- mut input: Input,
- writer: &mut Output,
- obtain_mut_data: Fmut,
- obtain_brw_data: Fbrw,
- ) -> EvalOutput
- where
- Fmut: FnOnce() -> Rmut,
- Rmut: DerefMut,
- Fbrw: FnOnce() -> Rbrw,
- Rbrw: Deref,
- {
- let (nitems, ncrates) = (input.items.len(), input.crates.len());
-
- let has_stmts = input.stmts.len() > 0;
-
- let (lstmts, litem, lcrates) = {
- let src = self.current_src();
- (src.stmts.len(), src.items.len(), src.crates.len())
- };
-
- let mut undo = true;
-
- let (stmt_idx, item_idx, crate_idx) = if let Some(ei) = self.editing.take() {
- let src = self.get_current_file_mut(); // remove at the index
- // then insert, so
- // acts like replace
-
- undo = false;
-
- match ei.editing {
- // we clear the edits if the indices fall outside the bounds
- Editing::Stmt => {
- if ei.index >= lstmts {
- input.stmts.clear();
- } else {
- src.stmts.remove(ei.index);
- }
-
- (ei.index, litem, lcrates)
- }
- Editing::Item => {
- if ei.index >= litem {
- input.items.clear();
- } else {
- src.items.remove(ei.index);
- }
-
- (lstmts, ei.index, lcrates)
- }
- Editing::Crate => {
- if ei.index >= lcrates {
- input.crates.clear();
- } else {
- src.crates.remove(ei.index);
- }
-
- (lstmts, litem, ei.index)
- }
- }
- } else {
- (lstmts, litem, lcrates)
- };
-
- self.insert_input(input, stmt_idx, item_idx, crate_idx);
-
- let maybe_pop_input = |repl_data: &mut ReplData| {
- if undo {
- let src = repl_data.get_current_file_mut();
-
- if has_stmts {
- src.stmts.remove(stmt_idx);
- }
-
- for _ in 0..nitems {
- src.items.remove(item_idx);
- }
-
- for _ in 0..ncrates {
- src.crates.remove(crate_idx);
- }
- }
- };
-
- // build directory
- let res = compile::build_compile_dir(&self.compilation_dir, &self.mods_map, &self.linking);
- if let Err(e) = res {
- maybe_pop_input(self); // failed so don't save
- return EvalOutput::Print(Cow::Owned(format!(
- "failed to build compile directory: {}",
- e
- )));
- }
-
- // compile
- let lib_file = compile::compile(&self.compilation_dir, &self.linking, |line| {
- writer.erase_last_line();
- writer.write_str(line);
- });
-
- writer.erase_last_line();
-
- let lib_file = match lib_file {
- Ok(f) => f,
- Err(e) => {
- maybe_pop_input(self); // failed so don't save
- return EvalOutput::Print(Cow::Owned(format!("{}", e)));
- }
- };
-
- if has_stmts {
- // execute
- let exec_res = {
- // Has to be done to make linux builds work
- // see:
- // https://github.com/nagisa/rust_libloading/issues/5
- // https://github.com/nagisa/rust_libloading/issues/41
- // https://github.com/nagisa/rust_libloading/issues/49
- //
- // Basically the api function `dlopen` will keep loaded libraries in memory to avoid
- // continuously allocating memory. It only does not release the library when thread_local data
- // is hanging around, and it seems `println!()` is something that does this.
- // Hence to avoid not having the library not updated with a new `new()` call, a different lib
- // name is passed to the function.
- // This is very annoying as it has needless fs interactions and a growing fs footprint but
- // what can you do ¯\_(ツ)_/¯
- let lib_file = rename_lib_file(lib_file).expect("failed renaming library file");
-
- // FIXME If removing the true this won't work through terminals
- // Need to trial it in linux though as I remember it breaking a lot...
- // If it is all good, I should be able to just redirect _all_ output
- let redirect_wtr = if self.redirect_on_execution {
- Some(writer)
- } else {
- None
- };
-
- let mut fn_name = String::new();
- code::eval_fn_name(&code::into_mod_path_vec(self.current_mod()), &mut fn_name);
-
- if self.linking.mutable {
- let mut r = obtain_mut_data();
- let app_data: &mut D = r.borrow_mut();
- compile::exec(&lib_file, &fn_name, app_data, redirect_wtr)
- } else {
- let r = obtain_brw_data();
- let app_data: &D = r.borrow();
- compile::exec(&lib_file, &fn_name, app_data, redirect_wtr)
- }
- };
- match exec_res {
- Ok(s) => {
- if self.linking.mutable {
- maybe_pop_input(self); // don't save mutating inputs
- EvalOutput::Print(Cow::Owned(format!("finished mutating block: {}", s)))
- // don't print as `out#`
- } else {
- EvalOutput::Data(s)
- }
- }
- Err(e) => {
- maybe_pop_input(self); // failed so don't save
- EvalOutput::Print(Cow::Borrowed(e))
- }
- }
- } else {
- // this will keep inputs, might not be preferrable to do so in mutating state?
- EvalOutput::Print(Cow::Borrowed("")) // do not execute if no extra statements have been added
- }
- }
-
- fn insert_input(&mut self, input: Input, stmt_idx: usize, item_idx: usize, crate_idx: usize) {
- let Input {
- items,
- crates,
- stmts,
- } = input;
-
- let src = self.get_current_file_mut();
-
- if !stmts.is_empty() {
- src.stmts.insert(stmt_idx, StmtGrp(stmts));
- }
-
- for item in items.into_iter().rev() {
- src.items.insert(item_idx, item);
- }
-
- for cr in crates.into_iter().rev() {
- src.crates.insert(crate_idx, cr);
- }
- }
-
- fn get_current_file_mut(&mut self) -> &mut SourceCode {
- self.mods_map.get_mut(&self.current_mod).expect(&format!(
- "file map does not have key: {}",
- self.current_mod.display()
- ))
- }
-}
-
-/// Renames the library into a distinct file name by incrementing a counter.
-/// Could fail if the number of libs grows enormous, greater than `u64`. This would mean, with
-/// `u64 = 18,446,744,073,709,551,615`, even with 1KB files (prolly not) this would be
-/// 18,446,744,073 TB. User will probably know something is up.
-fn rename_lib_file>(compiled_lib: P) -> io::Result {
- let no_parent = PathBuf::new();
- let mut idx: u64 = 0;
- let parent = compiled_lib.as_ref().parent().unwrap_or(&no_parent);
- let name = |i| format!("papyrus.mem-code.lib.{}", i);
- let mut lib_path = parent.join(&name(idx));
- while lib_path.exists() {
- idx += 1;
- lib_path = parent.join(&name(idx));
- }
- std::fs::rename(&compiled_lib, &lib_path)?;
- Ok(lib_path)
-}
+use super::*;
+use crate::{
+ cmds::{self, CommandResult},
+ code::{self, Input, SourceCode, StmtGrp},
+ compile,
+};
+use std::borrow::{Borrow, BorrowMut};
+use std::ops::{Deref, DerefMut};
+use std::sync::{Arc, Mutex};
+
+impl Repl {
+ /// Evaluates the read input, compiling and executing the code and printing all line prints until
+ /// a result is found. This result gets passed back as a print ready repl.
+ pub fn eval(self, app_data: &mut D) -> EvalResult {
+ use std::cell::Cell;
+ use std::rc::Rc;
+
+ let ptr = Rc::into_raw(Rc::new(app_data));
+
+ // as I am playing around with pointers here, I am going to do assertions in the rebuilding
+ // if from_raw is called more than once, it is memory unsafe, so count the calls and assert it is only 1
+ let rebuilds: Rc> = Rc::new(Cell::new(0));
+
+ let func = || {
+ let b = Rc::clone(&rebuilds);
+
+ let n = b.get();
+
+ assert_eq!(n, 0, "unsafe memory operation, can only rebuild Rc once.");
+
+ b.set(n + 1);
+
+ let c = unsafe { Rc::from_raw(ptr) };
+
+ Rc::try_unwrap(c)
+ .map_err(|_| "there should only be one strong reference")
+ .unwrap()
+ };
+
+ map_variants(self, func, func)
+ }
+
+ /// Begin listening to line change events on the output.
+ pub fn output_listen(&mut self) -> output::Receiver {
+ self.state.output.listen()
+ }
+
+ /// Close the sender side of the output channel.
+ pub fn close_channel(&mut self) {
+ self.state.output.close()
+ }
+
+ /// The current output.
+ ///
+ /// The output contains colouring ANSI escape codes, the prompt, and all input.
+ pub fn output(&self) -> &str {
+ self.state.output.buffer()
+ }
+}
+
+impl Repl {
+ /// Same as `eval` but will evaluate on another thread, not blocking this one.
+ ///
+ /// An `Arc::clone` will be taken of `app_data`.
+ pub fn eval_async(self, app_data: &Arc>) -> Evaluating {
+ let (tx, rx) = crossbeam_channel::bounded(1);
+
+ let clone = Arc::clone(app_data);
+
+ std::thread::spawn(move || {
+ let eval = map_variants(
+ self,
+ || clone.lock().expect("failed getting lock of data"),
+ || clone.lock().expect("failed getting lock of data"),
+ );
+
+ tx.send(eval).unwrap();
+ });
+
+ Evaluating { jh: rx }
+ }
+}
+
+impl Evaluating {
+ /// Evaluating has finished.
+ pub fn completed(&self) -> bool {
+ !self.jh.is_empty()
+ }
+
+ /// Waits for the evaluating to finish before return the result.
+ /// If evaluating is `completed` this will return immediately.
+ pub fn wait(self) -> EvalResult {
+ self.jh
+ .recv()
+ .expect("receiving eval result from async thread failed")
+ }
+}
+
+fn map_variants(
+ repl: Repl,
+ obtain_mut_data: Fmut,
+ obtain_brw_data: Fbrw,
+) -> EvalResult
+where
+ Fmut: FnOnce() -> Rmut,
+ Rmut: DerefMut,
+ Fbrw: FnOnce() -> Rbrw,
+ Rbrw: Deref,
+{
+ let Repl {
+ state,
+ mut data,
+ more,
+ data_mrker,
+ } = repl;
+
+ let Evaluate { mut output, result } = state;
+
+ let mut keep_mutating = false; // default to stop mutating phase
+ // can't cancel before as handle program requires it for decisions
+
+ // map variants into Result
+ let mapped = match result {
+ InputResult::Command(cmds) => {
+ let r = data.handle_command(&cmds, &mut output, obtain_mut_data);
+ keep_mutating = data.linking.mutable; // a command can alter the mutating state, needs to persist
+ r.map(|x| EvalOutput::Print(x))
+ }
+ InputResult::Program(input) => {
+ Ok(data.handle_program(input, &mut output, obtain_mut_data, obtain_brw_data))
+ }
+ InputResult::InputError(err) => Ok(EvalOutput::Print(Cow::Owned(err))),
+ InputResult::Eof => Err(Signal::Exit),
+ _ => Ok(EvalOutput::Print(Cow::Borrowed(""))),
+ };
+
+ let (eval_output, sig) = match mapped {
+ Ok(hir) => (hir, Signal::None),
+ Err(sig) => (EvalOutput::Print(Cow::Borrowed("")), sig),
+ };
+
+ data.linking.mutable = keep_mutating; // always cancel a mutating block on evaluation??
+ // the alternative would be to keep alive on compilation failures, might not for now though.
+ // this would have to be individually handled in each match arm and it, rather let the user
+ // have to reinstate mutability if they fuck up input.
+
+ EvalResult {
+ signal: sig,
+ repl: Repl {
+ state: Print {
+ output,
+ data: eval_output,
+ },
+ data,
+ more,
+ data_mrker,
+ },
+ }
+}
+
+impl ReplData {
+ fn handle_command(
+ &mut self,
+ cmds: &str,
+ writer: &mut W,
+ obtain_mut_app_data: F,
+ ) -> Result, Signal>
+ where
+ F: FnOnce() -> R,
+ R: DerefMut,
+ W: io::Write,
+ {
+ use cmdtree::LineResult as lr;
+
+ let tuple = match self.cmdtree.parse_line(cmds, true, writer) {
+ lr::Exit => return Err(Signal::Exit),
+ lr::Cancel => {
+ self.linking.mutable = false; // reset the mutating on cancel
+ self.editing = None; // reset the editing on cancel
+ Cow::Borrowed("cancelled input and returned to root")
+ }
+ lr::Action(res) => match res {
+ CommandResult::BeginMutBlock => {
+ self.linking.mutable = true;
+ Cow::Borrowed("beginning mut block")
+ }
+ CommandResult::EditAlter(ei) => Cow::Borrowed(cmds::edit_alter(self, ei)),
+ CommandResult::EditReplace(ei, val) => {
+ let r = Cow::Borrowed(cmds::edit_alter(self, ei));
+
+ if r.is_empty() {
+ return Err(Signal::ReEvaluate(val));
+ } else {
+ r
+ }
+ }
+ CommandResult::SwitchModule(path) => {
+ Cow::Borrowed(crate::cmds::switch_module(self, &path))
+ }
+
+ CommandResult::ActionOnReplData(action) => Cow::Owned(action(self, writer)),
+ CommandResult::ActionOnAppData(action) => {
+ let mut r = obtain_mut_app_data();
+ let app_data: &mut D = r.borrow_mut();
+ let s = action(app_data, writer);
+ Cow::Owned(s)
+ }
+ CommandResult::Empty => Cow::Borrowed(""),
+ },
+ _ => Cow::Borrowed(""),
+ };
+
+ Ok(tuple)
+ }
+
+ fn handle_program(
+ &mut self,
+ mut input: Input,
+ writer: &mut Output,
+ obtain_mut_data: Fmut,
+ obtain_brw_data: Fbrw,
+ ) -> EvalOutput
+ where
+ Fmut: FnOnce() -> Rmut,
+ Rmut: DerefMut,
+ Fbrw: FnOnce() -> Rbrw,
+ Rbrw: Deref,
+ {
+ let (nitems, ncrates) = (input.items.len(), input.crates.len());
+
+ let has_stmts = input.stmts.len() > 0;
+
+ let (lstmts, litem, lcrates) = {
+ let src = self.current_src();
+ (src.stmts.len(), src.items.len(), src.crates.len())
+ };
+
+ let mut undo = true;
+
+ let (stmt_idx, item_idx, crate_idx) = if let Some(ei) = self.editing.take() {
+ let src = self.get_current_file_mut(); // remove at the index
+ // then insert, so
+ // acts like replace
+
+ undo = false;
+
+ match ei.editing {
+ // we clear the edits if the indices fall outside the bounds
+ Editing::Stmt => {
+ if ei.index >= lstmts {
+ input.stmts.clear();
+ } else {
+ src.stmts.remove(ei.index);
+ }
+
+ (ei.index, litem, lcrates)
+ }
+ Editing::Item => {
+ if ei.index >= litem {
+ input.items.clear();
+ } else {
+ src.items.remove(ei.index);
+ }
+
+ (lstmts, ei.index, lcrates)
+ }
+ Editing::Crate => {
+ if ei.index >= lcrates {
+ input.crates.clear();
+ } else {
+ src.crates.remove(ei.index);
+ }
+
+ (lstmts, litem, ei.index)
+ }
+ }
+ } else {
+ (lstmts, litem, lcrates)
+ };
+
+ self.insert_input(input, stmt_idx, item_idx, crate_idx);
+
+ let maybe_pop_input = |repl_data: &mut ReplData| {
+ if undo {
+ let src = repl_data.get_current_file_mut();
+
+ if has_stmts {
+ src.stmts.remove(stmt_idx);
+ }
+
+ for _ in 0..nitems {
+ src.items.remove(item_idx);
+ }
+
+ for _ in 0..ncrates {
+ src.crates.remove(crate_idx);
+ }
+ }
+ };
+
+ // build directory
+ let res = compile::build_compile_dir(&self.compilation_dir, &self.mods_map, &self.linking);
+ if let Err(e) = res {
+ maybe_pop_input(self); // failed so don't save
+ return EvalOutput::Print(Cow::Owned(format!(
+ "failed to build compile directory: {}",
+ e
+ )));
+ }
+
+ // compile
+ let lib_file = compile::compile(&self.compilation_dir, &self.linking, |line| {
+ writer.erase_last_line();
+ writer.write_str(line);
+ });
+
+ writer.erase_last_line();
+
+ let lib_file = match lib_file {
+ Ok(f) => f,
+ Err(e) => {
+ maybe_pop_input(self); // failed so don't save
+ return EvalOutput::Print(Cow::Owned(format!("{}", e)));
+ }
+ };
+
+ if has_stmts {
+ // execute
+ let exec_res = {
+ // once compilation succeeds and we are going to evaluate it (which libloads) we
+ // first rename the files to avoid locking for the next compilation that might
+ // happen
+ let lib_file = compile::unshackle_library_file(lib_file);
+
+ let mut fn_name = String::new();
+ code::eval_fn_name(&code::into_mod_path_vec(self.current_mod()), &mut fn_name);
+
+ if self.linking.mutable {
+ let mut r = obtain_mut_data();
+ let app_data: &mut D = r.borrow_mut();
+ compile::exec(&lib_file, &fn_name, app_data)
+ } else {
+ let r = obtain_brw_data();
+ let app_data: &D = r.borrow();
+ compile::exec(&lib_file, &fn_name, app_data)
+ }
+ };
+ match exec_res {
+ Ok((kserd, lib)) => {
+ // store vec, maybe
+ add_to_limit_vec(&mut self.loadedlibs, lib, self.loaded_libs_size_limit);
+
+ if self.linking.mutable {
+ maybe_pop_input(self); // don't save mutating inputs
+ EvalOutput::Print(Cow::Owned(format!("finished mutating block: {}", kserd)))
+ // don't print as `out#`
+ } else {
+ EvalOutput::Data(kserd)
+ }
+ }
+ Err(e) => {
+ maybe_pop_input(self); // failed so don't save
+ EvalOutput::Print(Cow::Borrowed(e))
+ }
+ }
+ } else {
+ // this will keep inputs, might not be preferrable to do so in mutating state?
+ EvalOutput::Print(Cow::Borrowed("")) // do not execute if no extra statements have been added
+ }
+ }
+
+ fn insert_input(&mut self, input: Input, stmt_idx: usize, item_idx: usize, crate_idx: usize) {
+ let Input {
+ items,
+ crates,
+ stmts,
+ } = input;
+
+ let src = self.get_current_file_mut();
+
+ if !stmts.is_empty() {
+ src.stmts.insert(stmt_idx, StmtGrp(stmts));
+ }
+
+ for item in items.into_iter().rev() {
+ src.items.insert(item_idx, item);
+ }
+
+ for cr in crates.into_iter().rev() {
+ src.crates.insert(crate_idx, cr);
+ }
+ }
+
+ fn get_current_file_mut(&mut self) -> &mut SourceCode {
+ self.mods_map.get_mut(&self.current_mod).expect(&format!(
+ "file map does not have key: {}",
+ self.current_mod.display()
+ ))
+ }
+}
+
+fn add_to_limit_vec(store: &mut VecDeque, item: T, limit: usize) {
+ match (limit, store.len()) {
+ (0, 0) => (), // do nothing, lib will drop after this
+ (0, _x) => store.clear(), // zero limit and store has something, clear them
+ (limit, _) => {
+ let limit = limit - 1; // limit will be gt zero
+ store.truncate(limit); // truncate to limit - 1 length, as we will add new lib in
+ store.push_front(item); // we keep the newest versions at front of queue
+ }
+ }
+}
+
+#[test]
+fn vec_limited_testing() {
+ let mut vec: VecDeque = VecDeque::new();
+ vec.push_front(-3);
+ vec.push_front(-2);
+
+ add_to_limit_vec(&mut vec, 0, 3);
+ assert_eq!(&vec, &[0, -2, -3]);
+
+ add_to_limit_vec(&mut vec, 3, 3);
+ assert_eq!(&vec, &[3, 0, -2]);
+
+ add_to_limit_vec(&mut vec, -1, 0);
+ assert!(vec.is_empty());
+ add_to_limit_vec(&mut vec, -1, 0);
+ assert!(vec.is_empty());
+
+ add_to_limit_vec(&mut vec, 0, 1);
+ add_to_limit_vec(&mut vec, 1, 1);
+ add_to_limit_vec(&mut vec, 2, 1);
+ assert_eq!(&vec, &[2]);
+}
diff --git a/src/repl/mod.rs b/src/repl/mod.rs
index 39be96b..c2c69f4 100644
Binary files a/src/repl/mod.rs and b/src/repl/mod.rs differ
diff --git a/src/repl/read.rs b/src/repl/read.rs
index 4cef821..8b443c2 100644
--- a/src/repl/read.rs
+++ b/src/repl/read.rs
@@ -2,9 +2,7 @@ use super::*;
impl Default for Repl {
fn default() -> Self {
- let mut data = ReplData::default();
-
- data.redirect_on_execution = false;
+ let data = ReplData::default();
let mut r = Repl {
state: Read {
diff --git a/src/run/interface.rs b/src/run/interface.rs
index 38dea79..29038f9 100644
--- a/src/run/interface.rs
+++ b/src/run/interface.rs
@@ -78,11 +78,6 @@ impl InputBuffer {
self.buf.len()
}
- pub fn clear(&mut self) {
- self.buf.clear();
- self.pos = 0;
- }
-
pub fn insert(&mut self, ch: char) {
self.buf.insert(self.pos, ch);
self.pos += 1;
| |