From 9382563dfc528a729dc878a05abaa531624124bd Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Sun, 26 May 2019 09:32:56 -0500 Subject: [PATCH] DEV: Adds a Repl for ucg. Uses a StatementAccumulator to handle the accumulation of lines until a line terminated with a statement is encountered. Fixes #41 --- Cargo.lock | 72 +++++++++++++++++++++++++++++++++++++------- Cargo.toml | 1 + src/build/mod.rs | 2 +- src/io/mod.rs | 58 +++++++++++++++++++++++++++++++++++ src/io/test.rs | 40 +++++++++++++++++++++++++ src/lib.rs | 1 + src/main.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 241 insertions(+), 11 deletions(-) create mode 100644 src/io/mod.rs create mode 100644 src/io/test.rs diff --git a/Cargo.lock b/Cargo.lock index 4013103..65ade5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,7 +40,7 @@ name = "atty" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -52,7 +52,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -63,7 +63,7 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cc 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -138,7 +138,7 @@ name = "dirs" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", "redox_users 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -203,7 +203,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.46" +version = "0.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -211,16 +211,36 @@ name = "linked-hash-map" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "log" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "memchr" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "nix" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.22 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nodrop" version = "0.1.13" @@ -248,7 +268,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -316,6 +336,22 @@ name = "rustc-demangle" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "rustyline" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ryu" version = "0.2.5" @@ -397,7 +433,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -406,7 +442,7 @@ name = "termion" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -451,6 +487,7 @@ dependencies = [ "clap 2.26.2 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rustyline 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", "serde_yaml 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "simple-error 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -480,6 +517,11 @@ name = "utf8-ranges" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "utf8parse" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "vec_map" version = "0.8.1" @@ -490,6 +532,11 @@ name = "version_check" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "walkdir" version = "2.2.7" @@ -578,9 +625,11 @@ dependencies = [ "checksum itoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5adb58558dcd1d786b5f0bd15f3226ee23486e24b7b58304b60f64dc68e62606" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" -"checksum libc 0.2.46 (registry+https://github.com/rust-lang/crates.io-index)" = "023a4cd09b2ff695f9734c1934145a315594b7986398496841c7031a5a1bbdbd" +"checksum libc 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "42914d39aad277d9e176efbdad68acb1d5443ab65afe0e0e4f0d49352a950880" "checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e" +"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "db4c41318937f6e76648f42826b1d9ade5c09cafb5aef7e351240a70f39206e9" +"checksum nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46f0f3210768d796e8fa79ec70ee6af172dacbe7147f5e69be5240a47778302b" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09" "checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c" @@ -593,6 +642,7 @@ dependencies = [ "checksum regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37e7cbbd370869ce2e8dff25c7018702d10b21a20ef7135316f8daecd6c25b7f" "checksum regex-syntax 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4e47a2ed29da7a9e1960e1639e7a982e6edc6d49be308a3b02daf511504a16d1" "checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395" +"checksum rustyline 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0f47ea1ceb347d2deae482d655dc8eef4bd82363d3329baffa3818bd76fea48b" "checksum ryu 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e7c066b8e2923f05d4718a06d2622f189ff362bc642bfade6c6629f0440f3827" "checksum same-file 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20c4be53a8a1ff4c1f1b2bd14570d2f634628709752f0702ecdd2b3f9a5267" "checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" @@ -613,8 +663,10 @@ dependencies = [ "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" +"checksum utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d9d7ed3431229a144296213105a390676cc49c9b6a72bd19f3176c98e129fa1" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd" diff --git a/Cargo.toml b/Cargo.toml index db5ddee..4bcaf69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ base64 = "0.10.0" regex = "1" dirs = "1.0.4" unicode-segmentation = "1.2.1" +rustyline = "4.1.0" [build-dependencies] walkdir = "2.2.7" diff --git a/src/build/mod.rs b/src/build/mod.rs index e68309d..c2a6cc7 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -302,7 +302,7 @@ impl<'a> FileBuilder<'a> { Ok(()) } - fn eval_input(&mut self, input: OffsetStrIter) -> Result, Box> { + pub fn eval_input(&mut self, input: OffsetStrIter) -> Result, Box> { match parse(input.clone(), None) { Ok(stmts) => { //panic!("Successfully parsed {}", input); diff --git a/src/io/mod.rs b/src/io/mod.rs new file mode 100644 index 0000000..377ddeb --- /dev/null +++ b/src/io/mod.rs @@ -0,0 +1,58 @@ +// Copyright 2019 Jeremy Wall +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use std::convert::Into; + +/// Accumulates lines and tells you when a ucg Statement has been read. +pub struct StatementAccumulator { + acc: Vec, +} + +impl StatementAccumulator { + /// Constructs a StatementAccumulator ready for use. + pub fn new() -> Self { + Self { acc: Vec::new() } + } + + /// Tells you if the latest line ends in the statement terminator. + /// + /// Returns None if it wasn't a terminated statement and leaves the + /// accumulated lines alone. + /// + /// Returns Some(String) with the terminated statement if it was a + /// terminated statement and drains the accumulated statements out. + pub fn get_statement(&mut self) -> Option { + if let Some(l) = self.acc.last() { + if l.trim_end().ends_with(";") { + let mut stmt = self.acc.drain(0..).fold(String::new(), |mut acc, s| { + acc.push_str(&s); + acc.push_str("\n"); + acc + }); + stmt.shrink_to_fit(); + return Some(stmt); + } + } + None + } + + /// Pushes a line into the Statement Accumulator. Assumes that the + /// new line has already been trimmed. get_statement will reintroduce + /// the new lines. + pub fn push>(&mut self, line: S) { + self.acc.push(line.into()); + } +} + +#[cfg(test)] +mod test; diff --git a/src/io/test.rs b/src/io/test.rs new file mode 100644 index 0000000..dbca071 --- /dev/null +++ b/src/io/test.rs @@ -0,0 +1,40 @@ +// Copyright 2019 Jeremy Wall +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::io::*; + +#[test] +fn test_line_accumulator_single_line_stmt() { + let test_line = "1 + 1;"; + let mut expected = String::new(); + expected.push_str(test_line); + expected.push_str("\n"); + let mut acc = StatementAccumulator::new(); + acc.push(test_line); + assert_eq!(acc.get_statement().unwrap(), expected); +} + +#[test] +fn test_line_accumulator_multi_line_stmt() { + let test_line1 = "1 "; + let test_line2 = "+ 1;"; + let mut expected = String::new(); + expected.push_str(test_line1); + expected.push_str("\n"); + expected.push_str(test_line2); + expected.push_str("\n"); + let mut acc = StatementAccumulator::new(); + acc.push(test_line1); + acc.push(test_line2); + assert_eq!(acc.get_statement().unwrap(), expected); +} diff --git a/src/lib.rs b/src/lib.rs index 3bccebd..5af7206 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,7 @@ pub mod tokenizer; pub mod build; pub mod convert; pub mod error; +pub mod io; pub mod iter; pub mod parse; diff --git a/src/main.rs b/src/main.rs index 27fadd6..e48219e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ #[macro_use] extern crate clap; extern crate dirs; +extern crate rustyline; extern crate ucglib; use std::cell::RefCell; @@ -47,6 +48,9 @@ fn do_flags<'a, 'b>() -> clap::App<'a, 'b> { (@arg target: --format +takes_value "Output type. (flags, json, env, exec) defaults to json.") (@arg INPUT: "ucg file to use as context for the expression.") ) + (@subcommand repl => + (about: "Start the ucg repl for interactive evaluation.") + ) (@subcommand build => (about: "Build a list of ucg files.") (@arg recurse: -r "Whether we should recurse in directories or not.") @@ -509,6 +513,78 @@ fn env_help() { ); } +fn do_repl( + import_paths: &Vec, + cache: Rc>, +) -> std::result::Result<(), Box> { + let config = rustyline::Config::builder(); + let mut editor = rustyline::Editor::<()>::with_config( + config + .history_ignore_space(true) + .history_ignore_dups(false) + .build(), + ); + let path_home = dirs::home_dir().unwrap_or(std::env::temp_dir()); + let config_home = std::env::var("XDG_CACHE_HOME") + .unwrap_or_else(|_| format!("{}/.cache", path_home.to_string_lossy())); + let mut config_home = PathBuf::from(config_home); + config_home.push("ucg"); + config_home.push("line_hist"); + if editor.load_history(&config_home).is_err() { + eprintln!( + "No history file {} Continuing without history.", + config_home.to_string_lossy() + ); + // introduce a scope so the file will get automatically closed after + { + let base_dir = config_home.parent().unwrap(); + if !base_dir.exists() { + if let Err(e) = std::fs::create_dir_all(base_dir) { + eprintln!("{}", e); + } + } + if let Err(e) = std::fs::File::create(&config_home) { + eprintln!("{}", e); + } + } + } + let mut builder = build::FileBuilder::new(std::env::current_dir()?, import_paths, cache); + // loop + let mut lines = ucglib::io::StatementAccumulator::new(); + loop { + // print prompt + lines.push(editor.readline("ucg> ")?); + // check to see if that line is a statement + loop { + // read a statement + if let Some(stmt) = lines.get_statement() { + // if it is then + // eval statement + match builder.eval_string(&stmt) { + // print the result + Err(e) => eprintln!("{}", e), + Ok(v) => { + println!("{}", v); + editor.history_mut().add(stmt); + editor.save_history(&config_home)?; + } + } + // start loop over at prompt. + break; + } + // if not then keep accumulating lines without a prompt + lines.push(editor.readline(">> ")?); + } + } +} + +fn repl(import_paths: &Vec, cache: Rc>) { + if let Err(e) = do_repl(import_paths, cache) { + eprintln!("{}", e); + process::exit(1); + } +} + fn main() { let mut app = do_flags(); let app_matches = app.clone().get_matches(); @@ -546,6 +622,8 @@ fn main() { importers_command(®istry) } else if let Some(_) = app_matches.subcommand_matches("env") { env_help() + } else if let Some(_) = app_matches.subcommand_matches("repl") { + repl(&import_paths, cache) } else if let Some(matches) = app_matches.subcommand_matches("fmt") { if let Err(e) = fmt_command(matches) { eprintln!("{}", e);