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:
parent
a8bb996e4f
commit
e0548e9be2
3 changed files with 73 additions and 45 deletions
|
@ -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("{")))
|
||||||
|
.then(|d| parser(move |input| {
|
||||||
|
let _: &str = input;
|
||||||
|
Ok((d.to_string() + input, Consumed::Empty(input)))
|
||||||
|
})));
|
||||||
|
|
||||||
let edn_arg_parser = spaces()
|
let query_parser = try(string(QUERY_COMMAND)).or(try(string(ALT_QUERY_COMMAND)))
|
||||||
.and(opening_brace_parser.clone()
|
.with(edn_arg_parser())
|
||||||
.and(many::<Vec<_>, _>(try(any()))));
|
.map(|x| {
|
||||||
|
Ok(Command::Query(x))
|
||||||
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 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!("");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue