From e0548e9be220a420026086be4dc77a63a6e4c257 Mon Sep 17 00:00:00 2001 From: Emily Toop Date: Mon, 22 May 2017 13:13:03 +0100 Subject: [PATCH] Improve query parser. Am still not happy with it though. There must be some way that I can retain the eof() after the `then` that means I don't have to move the skip on spaces and eof Make in process command storing clearer. Add comments around in process commands. Add alternative commands for transact/t and query/q --- tools/cli/src/mentat_cli/command_parser.rs | 75 ++++++++++++++-------- tools/cli/src/mentat_cli/input.rs | 32 +++++---- tools/cli/src/mentat_cli/repl.rs | 11 ++-- 3 files changed, 73 insertions(+), 45 deletions(-) diff --git a/tools/cli/src/mentat_cli/command_parser.rs b/tools/cli/src/mentat_cli/command_parser.rs index d20b85bb..d925a907 100644 --- a/tools/cli/src/mentat_cli/command_parser.rs +++ b/tools/cli/src/mentat_cli/command_parser.rs @@ -9,10 +9,9 @@ // specific language governing permissions and limitations under the License. use combine::{ - any, eof, - many, many1, + parser, satisfy, sep_end_by, token, @@ -28,6 +27,8 @@ use combine::combinator::{ try }; +use combine::primitives::Consumed; + use errors as cli; use edn; @@ -35,8 +36,10 @@ use edn; pub static HELP_COMMAND: &'static str = &"help"; pub static OPEN_COMMAND: &'static str = &"open"; pub static CLOSE_COMMAND: &'static str = &"close"; -pub static QUERY_COMMAND: &'static str = &"q"; -pub static TRANSACT_COMMAND: &'static str = &"t"; +pub static QUERY_COMMAND: &'static str = &"query"; +pub static ALT_QUERY_COMMAND: &'static str = &"q"; +pub static TRANSACT_COMMAND: &'static str = &"transact"; +pub static ALT_TRANSACT_COMMAND: &'static str = &"t"; #[derive(Clone, Debug, Eq, PartialEq)] pub enum Command { @@ -90,6 +93,8 @@ pub fn command(s: &str) -> Result { let close_parser = string(CLOSE_COMMAND) .with(arguments()) + .skip(spaces()) + .skip(eof()) .map(|args| { if args.len() > 0 { return Err(cli::ErrorKind::CommandParse(format!("Unrecognized argument {:?}", args[0])).into()); @@ -97,27 +102,24 @@ pub fn command(s: &str) -> Result { Ok(Command::Close) }); - let opening_brace_parser = try(string("[")) - .or(try(string("{"))); - - let edn_arg_parser = spaces() - .and(opening_brace_parser.clone() - .and(many::, _>(try(any())))); + let edn_arg_parser = || spaces() + .with(try(string("[")) + .or(try(string("{"))) + .then(|d| parser(move |input| { + let _: &str = input; + Ok((d.to_string() + input, Consumed::Empty(input))) + }))); - let query_parser = string(QUERY_COMMAND) - .and(edn_arg_parser.clone()) - .map( |x| { - let args = (x.1).1; - let content: String = args.1.iter().collect(); - Ok(Command::Query(format!("{}{}", args.0, content))) - }); + let query_parser = try(string(QUERY_COMMAND)).or(try(string(ALT_QUERY_COMMAND))) + .with(edn_arg_parser()) + .map(|x| { + Ok(Command::Query(x)) + }); - let transact_parser = string(TRANSACT_COMMAND) - .and(edn_arg_parser.clone()) + let transact_parser = try(string(TRANSACT_COMMAND)).or(try(string(ALT_TRANSACT_COMMAND))) + .with(edn_arg_parser()) .map( |x| { - let args = (x.1).1; - let content: String = args.1.iter().collect(); - Ok(Command::Transact(format!("{}{}", args.0, content))) + Ok(Command::Transact(x)) }); spaces() @@ -128,11 +130,8 @@ pub fn command(s: &str) -> Result { &mut try(close_parser), &mut try(query_parser), &mut try(transact_parser)])) - .skip(spaces()) - .skip(eof()) .parse(s) - .map(|x| x.0) - .unwrap_or(Err(cli::ErrorKind::CommandParse(format!("Invalid command {:?}", s)).into())) + .unwrap_or((Err(cli::ErrorKind::CommandParse(format!("Invalid command {:?}", s)).into()), "")).0 } #[cfg(test)] @@ -283,6 +282,16 @@ mod tests { } } + #[test] + fn test_query_parser_alt_query_command() { + let input = ".query [:find ?x :where [?x foo/bar ?y]]"; + let cmd = command(&input).expect("Expected query command"); + match cmd { + Command::Query(edn) => assert_eq!(edn, "[:find ?x :where [?x foo/bar ?y]]"), + _ => assert!(false) + } + } + #[test] fn test_query_parser_incomplete_edn() { let input = ".q [:find ?x\r\n"; @@ -319,10 +328,20 @@ mod tests { #[test] fn test_transact_parser_complete_edn() { - let input = ".t [:db/add \"s\" :db/ident :foo/uuid]"; + let input = ".t [[:db/add \"s\" :db/ident :foo/uuid] [:db/add \"r\" :db/ident :bar/uuid]]"; let cmd = command(&input).expect("Expected transact command"); match cmd { - Command::Transact(edn) => assert_eq!(edn, "[:db/add \"s\" :db/ident :foo/uuid]"), + Command::Transact(edn) => assert_eq!(edn, "[[:db/add \"s\" :db/ident :foo/uuid] [:db/add \"r\" :db/ident :bar/uuid]]"), + _ => assert!(false) + } + } + + #[test] + fn test_transact_parser_alt_command() { + let input = ".transact [[:db/add \"s\" :db/ident :foo/uuid] [:db/add \"r\" :db/ident :bar/uuid]]"; + let cmd = command(&input).expect("Expected transact command"); + match cmd { + Command::Transact(edn) => assert_eq!(edn, "[[:db/add \"s\" :db/ident :foo/uuid] [:db/add \"r\" :db/ident :bar/uuid]]"), _ => assert!(false) } } diff --git a/tools/cli/src/mentat_cli/input.rs b/tools/cli/src/mentat_cli/input.rs index 9e580d02..6839dc61 100644 --- a/tools/cli/src/mentat_cli/input.rs +++ b/tools/cli/src/mentat_cli/input.rs @@ -35,8 +35,8 @@ pub enum InputResult { MetaCommand(Command), /// An empty line Empty, - /// Needs more input; i.e. there is an unclosed delimiter - More(Command), + /// Needs more input + More, /// End of file reached Eof, } @@ -45,6 +45,7 @@ pub enum InputResult { pub struct InputReader { buffer: String, reader: Option>, + in_process_cmd: Option, } impl InputReader { @@ -61,6 +62,7 @@ impl InputReader { InputReader{ buffer: String::new(), reader: r, + in_process_cmd: None, } } @@ -72,8 +74,9 @@ impl InputReader { /// Reads a single command, item, or statement from `stdin`. /// Returns `More` if further input is required for a complete result. /// In this case, the input received so far is buffered internally. - pub fn read_input(&mut self, in_process_cmd: Option) -> Result { - let line = match self.read_line(if in_process_cmd.is_some() { MORE_PROMPT } else { DEFAULT_PROMPT }) { + pub fn read_input(&mut self) -> Result { + let prompt = if self.in_process_cmd.is_some() { MORE_PROMPT } else { DEFAULT_PROMPT }; + let line = match self.read_line(prompt) { Some(s) => s, None => return Ok(Eof), }; @@ -86,27 +89,32 @@ impl InputReader { self.add_history(&line); - let cmd = match in_process_cmd { - Some(Command::Query(args)) => { - Command::Query(args + " " + &line) + // if we have a command in process (i.e. in incomplete query or transaction), + // then we already know which type of command it is and so we don't need to parse the + // command again, only the content, which we do later. + // Therefore, we add the newly read in line to the existing command args. + // If there is no in process command, we parse the read in line as a new command. + let cmd = match &self.in_process_cmd { + &Some(Command::Query(ref args)) => { + Command::Query(args.clone() + " " + &line) }, - Some(Command::Transact(args)) => { - Command::Transact(args + " " + &line) + &Some(Command::Transact(ref args)) => { + Command::Transact(args.clone() + " " + &line) }, _ => { try!(command(&self.buffer)) } }; - println!("processing {:?}", cmd); - match cmd { Command::Query(_) | Command::Transact(_) if !cmd.is_complete() => { - Ok(More(cmd)) + self.in_process_cmd = Some(cmd); + Ok(More) }, _ => { self.buffer.clear(); + self.in_process_cmd = None; Ok(InputResult::MetaCommand(cmd)) } } diff --git a/tools/cli/src/mentat_cli/repl.rs b/tools/cli/src/mentat_cli/repl.rs index c9da67a3..6c002525 100644 --- a/tools/cli/src/mentat_cli/repl.rs +++ b/tools/cli/src/mentat_cli/repl.rs @@ -52,21 +52,22 @@ impl Repl { /// Runs the REPL interactively. pub fn run(&mut self) { - let mut more: Option = None; + let mut more = false; let mut input = InputReader::new(); loop { - let res = input.read_input(more.clone()); + let res = input.read_input(); match res { Ok(MetaCommand(cmd)) => { debug!("read command: {:?}", cmd); - more = None; + more = false; self.handle_command(cmd); }, - Ok(Empty) => (), - Ok(More(cmd)) => { more = Some(cmd); }, + Ok(Empty) => more = false, + Ok(More) => { more = true; }, Ok(Eof) => { + more = false; if input.is_tty() { println!(""); }