use std::collections::{HashMap, HashSet};
use crate::command::Command;
use crate::error::ParseError;
use crate::error::ParseError::{EscapeError, NameError, PrefixError};
#[derive(Debug, Copy, Clone)]
enum ParseState {
Prefix,
Name,
Default,
Argument,
LongArgument,
EscapeLongArg,
Option,
ParamConnector,
ParamVal,
ParamLongVal,
EscapeLongParamVal,
}
#[derive(Debug)]
pub struct Parser {
pub prefix: char,
pub option_prefix: char,
}
impl Parser {
pub fn new(prefix: char, option_prefix: char) -> Parser {
Parser {
prefix,
option_prefix,
}
}
pub fn parse<'a>(&'_ self, raw: &'a str) -> Result<Command, ParseError> {
let mut name = String::new();
let mut arguments: Vec<String> = vec![];
let mut options: HashSet<String> = HashSet::new();
let mut parameters: HashMap<String, String> = HashMap::new();
let mut state = ParseState::Prefix;
let mut buffer = String::new();
let mut key_buffer = String::new();
for (cursor, c) in raw.chars().enumerate() {
match state {
ParseState::Prefix => {
match c {
x if x == self.prefix => {
state = ParseState::Name;
}
_ => {return Err(PrefixError(cursor, c))}
}
}
ParseState::Name => {
match c {
' ' => {
if cursor == 1 {
return Err(NameError(cursor, c));
} else {
state = ParseState::Default;
}
}
_ => { name.push(c); }
}
}
ParseState::Argument => {
match c {
' ' => {
arguments.push(buffer);
buffer = String::new();
state = ParseState::Default;
}
_ => {
buffer.push(c);
}
}
}
ParseState::LongArgument => {
match c {
'"' => {
arguments.push(buffer);
buffer = String::new();
state = ParseState::Default;
}
'\\' => {
state = ParseState::EscapeLongArg;
}
_ => {
buffer.push(c);
}
}
}
ParseState::EscapeLongArg => {
match c {
'"' | '\\' => {
state = ParseState::LongArgument;
buffer.push(c);
}
_ => {
return Err(EscapeError(cursor, c));
}
}
}
ParseState::Option => {
match c {
' ' => {
options.insert(buffer);
buffer = String::new();
state = ParseState::Default;
}
':' => {
key_buffer = buffer;
buffer = String::new();
state = ParseState::ParamConnector;
}
_ => {
buffer.push(c);
}
}
}
ParseState::ParamConnector => {
match c {
'"' => {
state = ParseState::ParamLongVal;
}
' ' => {
parameters.insert(key_buffer, buffer);
key_buffer = String::new();
buffer = String::new();
state = ParseState::Default;
}
_ => {
state = ParseState::ParamVal;
buffer.push(c);
}
}
}
ParseState::ParamVal => {
match c {
' ' => {
parameters.insert(key_buffer, buffer);
key_buffer = String::new();
buffer = String::new();
state = ParseState::Default;
}
_ => {
buffer.push(c);
}
}
}
ParseState::ParamLongVal => {
match c {
'"' => {
parameters.insert(key_buffer, buffer);
key_buffer = String::new();
buffer = String::new();
state = ParseState::Default;
}
'\\' => {
state = ParseState::EscapeLongParamVal;
}
_ => {
buffer.push(c);
}
}
}
ParseState::EscapeLongParamVal => {
match c {
'"' | '\\' => {
state = ParseState::ParamLongVal;
buffer.push(c);
}
_ => {
return Err(EscapeError(cursor, c));
}
}
}
ParseState::Default => {
match c {
' ' => {}
'"' => {state = ParseState::LongArgument;}
x if x == self.option_prefix => {
state = ParseState::Option;
}
_ => {
state = ParseState::Argument;
buffer.push(c);
}
}
}
}
}
Ok(Command {
prefix: self.prefix,
option_prefix: self.option_prefix,
name, arguments, options, parameters
})
}
}
#[cfg(test)]
pub mod tests {
use std::time::{Duration, Instant};
use super::*;
#[test]
fn parse_test() {
let p = Parser::new('!', '-');
let command_string = r##"!foo arg1 "long arg 2" -opt -opt -key1:val1 -key2:"long val2""##;
let command = Command {
prefix: '!',
option_prefix: '-',
name: "foo".to_string(),
arguments: vec!["arg1".to_string(), "long arg 2".to_string()],
options: HashSet::from(["opt".to_string()]),
parameters: HashMap::from([
("key1".to_string(), "val1".to_string()),
("key2".to_string(), "long val2".to_string())
])
};
assert_eq!(p.parse(command_string).unwrap(), command);
}
#[test]
fn time_test() {
let p = Parser::new('!', '-');
let command_string = r##"!foo arg1 "long arg 2" -opt -opt -key1:val1 -key2:"long val2""##;
let now = Instant::now();
for _ in 0..100000 {
let _ = p.parse(command_string);
}
println!("{}", now.elapsed().as_micros());
let p = Parser::new('!', '-');
let command_string = r##"just a normal sentence"##;
let now = Instant::now();
for _ in 0..100000 {
let _ = p.parse(command_string);
}
println!("{}", now.elapsed().as_micros());
}
}