From 9f0f47abaa934b66da5b302236bfc89f95a7f329 Mon Sep 17 00:00:00 2001 From: Justin Wernick Date: Mon, 27 Feb 2023 23:12:32 +0200 Subject: Revamp parsing to support more complex commands --- src/main.rs | 37 +++++++++++++++++++++--------------- src/parser.rs | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 15 deletions(-) create mode 100644 src/parser.rs (limited to 'src') diff --git a/src/main.rs b/src/main.rs index 93880de..0df2754 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,38 +1,45 @@ -use std::error::Error; use std::{io, io::Write}; +use thiserror::Error; -fn prompt() -> Result<(), Box> { +mod parser; + +use parser::Command; + +fn prompt() -> Result<(), ShackleError> { print!("> "); io::stdout().flush()?; Ok(()) } -fn read_stdin() -> Result> { +fn read_stdin() -> Result { let mut buffer = String::new(); io::stdin().read_line(&mut buffer)?; Ok(buffer) } -fn main() -> Result<(), Box> { +fn main() -> Result<(), ShackleError> { loop { prompt()?; let user_input = read_stdin()?; - if user_input.len() == 0 { - // control-d or end of input. Needs to be specially handled before - // the match because this is identical to whitespace after the trim. - break; - } - - match user_input.trim() { - "" => {} - "exit" => { + match user_input.parse::() { + Err(unknown_input) => { + println!("Unknown input \"{}\"", unknown_input); + } + Ok(Command::Whitespace) => {} + Ok(Command::Exit) => { break; } - other_input => { - println!("Unknown input {}", other_input); + Ok(Command::GitInit(repo_name)) => { + println!("Successfully created {}.git", repo_name); } } } Ok(()) } + +#[derive(Error, Debug)] +enum ShackleError { + #[error(transparent)] + IoError(#[from] io::Error), +} diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..2f65180 --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,61 @@ +use nom::{ + branch::alt, + bytes::complete::{is_not, tag}, + character::complete::{multispace0, multispace1}, + combinator::{eof, map, value}, + error::ParseError, + sequence::delimited, + sequence::tuple, + Finish, IResult, +}; +use std::str::FromStr; + +#[derive(Clone)] +pub enum Command { + Whitespace, + Exit, + GitInit(String), +} + +impl FromStr for Command { + // the error must be owned as well + type Err = String; + + fn from_str(s: &str) -> Result { + match command_parser(s).finish() { + Ok((remaining, command)) => { + if remaining.trim().is_empty() { + Ok(command) + } else { + Err(s.trim().to_owned()) + } + } + Err(_) => Err(s.trim().to_owned()), + } + } +} + +fn command_parser(input: &str) -> IResult<&str, Command> { + alt(( + value(Command::Exit, eof), + value(Command::Whitespace, tuple((multispace1, eof))), + value(Command::Exit, ws(tag("exit"))), + map( + ws(tuple((tag("git-init"), multispace1, not_space))), + |(_, _, repo_name)| Command::GitInit(repo_name.to_owned()), + ), + ))(input) +} + +/// A combinator that takes a parser `inner` and produces a parser that also consumes both leading and +/// trailing whitespace, returning the output of `inner`. +fn ws<'a, F, O, E: ParseError<&'a str>>(inner: F) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> +where + F: FnMut(&'a str) -> IResult<&'a str, O, E>, +{ + delimited(multispace0, inner, multispace0) +} + +fn not_space(s: &str) -> IResult<&str, &str> { + is_not(" \t\r\n")(s) +} -- cgit v1.2.3