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
This commit is contained in:
Emily Toop 2017-05-22 13:13:03 +01:00
parent a8bb996e4f
commit e0548e9be2
3 changed files with 73 additions and 45 deletions

View file

@ -9,10 +9,9 @@
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
use combine::{ use combine::{
any,
eof, eof,
many,
many1, many1,
parser,
satisfy, satisfy,
sep_end_by, sep_end_by,
token, token,
@ -28,6 +27,8 @@ use combine::combinator::{
try try
}; };
use combine::primitives::Consumed;
use errors as cli; use errors as cli;
use edn; use edn;
@ -35,8 +36,10 @@ use edn;
pub static HELP_COMMAND: &'static str = &"help"; pub static HELP_COMMAND: &'static str = &"help";
pub static OPEN_COMMAND: &'static str = &"open"; pub static OPEN_COMMAND: &'static str = &"open";
pub static CLOSE_COMMAND: &'static str = &"close"; pub static CLOSE_COMMAND: &'static str = &"close";
pub static QUERY_COMMAND: &'static str = &"q"; pub static QUERY_COMMAND: &'static str = &"query";
pub static TRANSACT_COMMAND: &'static str = &"t"; 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)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum Command { pub enum Command {
@ -90,6 +93,8 @@ pub fn command(s: &str) -> Result<Command, cli::Error> {
let close_parser = string(CLOSE_COMMAND) let close_parser = string(CLOSE_COMMAND)
.with(arguments()) .with(arguments())
.skip(spaces())
.skip(eof())
.map(|args| { .map(|args| {
if args.len() > 0 { if args.len() > 0 {
return Err(cli::ErrorKind::CommandParse(format!("Unrecognized argument {:?}", args[0])).into()); return Err(cli::ErrorKind::CommandParse(format!("Unrecognized argument {:?}", args[0])).into());
@ -97,27 +102,24 @@ pub fn command(s: &str) -> Result<Command, cli::Error> {
Ok(Command::Close) Ok(Command::Close)
}); });
let opening_brace_parser = try(string("[")) let edn_arg_parser = || spaces()
.or(try(string("{"))); .with(try(string("["))
.or(try(string("{")))
let edn_arg_parser = spaces() .then(|d| parser(move |input| {
.and(opening_brace_parser.clone() let _: &str = input;
.and(many::<Vec<_>, _>(try(any())))); Ok((d.to_string() + input, Consumed::Empty(input)))
})));
let query_parser = string(QUERY_COMMAND) let query_parser = try(string(QUERY_COMMAND)).or(try(string(ALT_QUERY_COMMAND)))
.and(edn_arg_parser.clone()) .with(edn_arg_parser())
.map( |x| { .map(|x| {
let args = (x.1).1; Ok(Command::Query(x))
let content: String = args.1.iter().collect(); });
Ok(Command::Query(format!("{}{}", args.0, content)))
});
let transact_parser = string(TRANSACT_COMMAND) let transact_parser = try(string(TRANSACT_COMMAND)).or(try(string(ALT_TRANSACT_COMMAND)))
.and(edn_arg_parser.clone()) .with(edn_arg_parser())
.map( |x| { .map( |x| {
let args = (x.1).1; Ok(Command::Transact(x))
let content: String = args.1.iter().collect();
Ok(Command::Transact(format!("{}{}", args.0, content)))
}); });
spaces() spaces()
@ -128,11 +130,8 @@ pub fn command(s: &str) -> Result<Command, cli::Error> {
&mut try(close_parser), &mut try(close_parser),
&mut try(query_parser), &mut try(query_parser),
&mut try(transact_parser)])) &mut try(transact_parser)]))
.skip(spaces())
.skip(eof())
.parse(s) .parse(s)
.map(|x| x.0) .unwrap_or((Err(cli::ErrorKind::CommandParse(format!("Invalid command {:?}", s)).into()), "")).0
.unwrap_or(Err(cli::ErrorKind::CommandParse(format!("Invalid command {:?}", s)).into()))
} }
#[cfg(test)] #[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] #[test]
fn test_query_parser_incomplete_edn() { fn test_query_parser_incomplete_edn() {
let input = ".q [:find ?x\r\n"; let input = ".q [:find ?x\r\n";
@ -319,10 +328,20 @@ mod tests {
#[test] #[test]
fn test_transact_parser_complete_edn() { 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"); let cmd = command(&input).expect("Expected transact command");
match cmd { 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) _ => assert!(false)
} }
} }

View file

@ -35,8 +35,8 @@ pub enum InputResult {
MetaCommand(Command), MetaCommand(Command),
/// An empty line /// An empty line
Empty, Empty,
/// Needs more input; i.e. there is an unclosed delimiter /// Needs more input
More(Command), More,
/// End of file reached /// End of file reached
Eof, Eof,
} }
@ -45,6 +45,7 @@ pub enum InputResult {
pub struct InputReader { pub struct InputReader {
buffer: String, buffer: String,
reader: Option<Reader<DefaultTerminal>>, reader: Option<Reader<DefaultTerminal>>,
in_process_cmd: Option<Command>,
} }
impl InputReader { impl InputReader {
@ -61,6 +62,7 @@ impl InputReader {
InputReader{ InputReader{
buffer: String::new(), buffer: String::new(),
reader: r, reader: r,
in_process_cmd: None,
} }
} }
@ -72,8 +74,9 @@ impl InputReader {
/// Reads a single command, item, or statement from `stdin`. /// Reads a single command, item, or statement from `stdin`.
/// Returns `More` if further input is required for a complete result. /// Returns `More` if further input is required for a complete result.
/// In this case, the input received so far is buffered internally. /// In this case, the input received so far is buffered internally.
pub fn read_input(&mut self, in_process_cmd: Option<Command>) -> Result<InputResult, cli::Error> { pub fn read_input(&mut self) -> Result<InputResult, cli::Error> {
let line = match self.read_line(if in_process_cmd.is_some() { MORE_PROMPT } else { DEFAULT_PROMPT }) { let prompt = if self.in_process_cmd.is_some() { MORE_PROMPT } else { DEFAULT_PROMPT };
let line = match self.read_line(prompt) {
Some(s) => s, Some(s) => s,
None => return Ok(Eof), None => return Ok(Eof),
}; };
@ -86,27 +89,32 @@ impl InputReader {
self.add_history(&line); self.add_history(&line);
let cmd = match in_process_cmd { // if we have a command in process (i.e. in incomplete query or transaction),
Some(Command::Query(args)) => { // then we already know which type of command it is and so we don't need to parse the
Command::Query(args + " " + &line) // 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)) => { &Some(Command::Transact(ref args)) => {
Command::Transact(args + " " + &line) Command::Transact(args.clone() + " " + &line)
}, },
_ => { _ => {
try!(command(&self.buffer)) try!(command(&self.buffer))
} }
}; };
println!("processing {:?}", cmd);
match cmd { match cmd {
Command::Query(_) | Command::Query(_) |
Command::Transact(_) if !cmd.is_complete() => { Command::Transact(_) if !cmd.is_complete() => {
Ok(More(cmd)) self.in_process_cmd = Some(cmd);
Ok(More)
}, },
_ => { _ => {
self.buffer.clear(); self.buffer.clear();
self.in_process_cmd = None;
Ok(InputResult::MetaCommand(cmd)) Ok(InputResult::MetaCommand(cmd))
} }
} }

View file

@ -52,21 +52,22 @@ impl Repl {
/// Runs the REPL interactively. /// Runs the REPL interactively.
pub fn run(&mut self) { pub fn run(&mut self) {
let mut more: Option<Command> = None; let mut more = false;
let mut input = InputReader::new(); let mut input = InputReader::new();
loop { loop {
let res = input.read_input(more.clone()); let res = input.read_input();
match res { match res {
Ok(MetaCommand(cmd)) => { Ok(MetaCommand(cmd)) => {
debug!("read command: {:?}", cmd); debug!("read command: {:?}", cmd);
more = None; more = false;
self.handle_command(cmd); self.handle_command(cmd);
}, },
Ok(Empty) => (), Ok(Empty) => more = false,
Ok(More(cmd)) => { more = Some(cmd); }, Ok(More) => { more = true; },
Ok(Eof) => { Ok(Eof) => {
more = false;
if input.is_tty() { if input.is_tty() {
println!(""); println!("");
} }