Parser accepts complete lines including \n

Allow comments in parser
This commit is contained in:
Sebastian 2024-05-05 14:39:41 +02:00
parent b38014686b
commit 584eb4aff0

View file

@ -1,12 +1,15 @@
use nom::{ use nom::{
branch::alt, branch::alt,
bytes::complete::tag, bytes::complete::tag,
character::complete::{alphanumeric1, i8, multispace1, space1}, character::complete::{
combinator::{all_consuming, map, rest}, alphanumeric1, i8, multispace0, multispace1, newline, none_of, not_line_ending, space1,
},
combinator::{all_consuming, map, opt, recognize, rest},
error::{context, convert_error, VerboseError}, error::{context, convert_error, VerboseError},
multi::{many0, many1},
number::complete::float, number::complete::float,
sequence::{preceded, separated_pair, terminated}, sequence::{preceded, separated_pair, terminated},
IResult, Parser, Err as NomErr, IResult, Parser,
}; };
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
@ -34,7 +37,7 @@ pub enum Direction {
} }
fn exit(input: &str) -> IResult<&str, Command, VerboseError<&str>> { fn exit(input: &str) -> IResult<&str, Command, VerboseError<&str>> {
all_consuming(map(alt((tag("q"), tag("Q"))), |_| Command::Exit)).parse(input) map(alt((tag("q"), tag("Q"))), |_| Command::Exit).parse(input)
} }
fn float_pair(input: &str) -> IResult<&str, (f32, f32), VerboseError<&str>> { fn float_pair(input: &str) -> IResult<&str, (f32, f32), VerboseError<&str>> {
@ -44,19 +47,19 @@ fn float_pair(input: &str) -> IResult<&str, (f32, f32), VerboseError<&str>> {
fn set_pos(input: &str) -> IResult<&str, Command, VerboseError<&str>> { fn set_pos(input: &str) -> IResult<&str, Command, VerboseError<&str>> {
context( context(
"set_pos", "set_pos",
all_consuming(map( map(
preceded( preceded(
terminated(alt((tag("set_pos"), tag("P"))), multispace1), terminated(alt((tag("set_pos"), tag("P"))), multispace1),
float_pair, float_pair,
), ),
|pair: (f32, f32)| Command::SetPos(pair.0, pair.1), |pair: (f32, f32)| Command::SetPos(pair.0, pair.1),
)), ),
) )
.parse(input) .parse(input)
} }
fn get_pos(input: &str) -> IResult<&str, Command, VerboseError<&str>> { fn get_pos(input: &str) -> IResult<&str, Command, VerboseError<&str>> {
all_consuming(map(alt((tag("get_pos"), tag("p"))), |_| Command::GetPos)).parse(input) map(alt((tag("get_pos"), tag("p"))), |_| Command::GetPos).parse(input)
} }
fn direction(input: &str) -> IResult<&str, Direction, VerboseError<&str>> { fn direction(input: &str) -> IResult<&str, Direction, VerboseError<&str>> {
@ -83,26 +86,27 @@ fn move_parameters(input: &str) -> IResult<&str, (Direction, i8), VerboseError<&
fn move_command(input: &str) -> IResult<&str, Command, VerboseError<&str>> { fn move_command(input: &str) -> IResult<&str, Command, VerboseError<&str>> {
context( context(
"move", "move",
all_consuming(map( map(
preceded( preceded(
terminated(alt((tag("move"), tag("M"))), multispace1), terminated(alt((tag("move"), tag("M"))), multispace1),
move_parameters, move_parameters,
), ),
|pair: (Direction, i8)| Command::Move(pair.0, pair.1), |pair: (Direction, i8)| Command::Move(pair.0, pair.1),
)), ),
) )
.parse(input) .parse(input)
} }
fn stop(input: &str) -> IResult<&str, Command, VerboseError<&str>> { fn stop(input: &str) -> IResult<&str, Command, VerboseError<&str>> {
all_consuming(map(alt((tag("stop"), tag("S"))), |_| Command::Stop)).parse(input) map(alt((tag("stop"), tag("S"))), |_| Command::Stop).parse(input)
} }
fn park(input: &str) -> IResult<&str, Command, VerboseError<&str>> { fn park(input: &str) -> IResult<&str, Command, VerboseError<&str>> {
all_consuming(map(alt((tag("park"), tag("K"))), |_| Command::Park)).parse(input) map(alt((tag("park"), tag("K"))), |_| Command::Park).parse(input)
} }
fn string_pair(input: &str) -> IResult<&str, (&str, &str), VerboseError<&str>> { fn string_pair(input: &str) -> IResult<&str, (&str, &str), VerboseError<&str>> {
// TODO: find out if alphanumeric1 is enough. Might need -_ and others
context( context(
"string_pair", "string_pair",
separated_pair(alphanumeric1, multispace1, alphanumeric1), separated_pair(alphanumeric1, multispace1, alphanumeric1),
@ -113,43 +117,43 @@ fn string_pair(input: &str) -> IResult<&str, (&str, &str), VerboseError<&str>> {
fn set_conf(input: &str) -> IResult<&str, Command, VerboseError<&str>> { fn set_conf(input: &str) -> IResult<&str, Command, VerboseError<&str>> {
context( context(
"set_comf", "set_comf",
all_consuming(map( map(
preceded( preceded(
terminated(alt((tag("set_conf"), tag("C"))), multispace1), terminated(alt((tag("set_conf"), tag("C"))), multispace1),
string_pair, string_pair,
), ),
|pair: (&str, &str)| Command::SetConf(pair.0.to_owned(), pair.1.to_owned()), |pair: (&str, &str)| Command::SetConf(pair.0.to_owned(), pair.1.to_owned()),
)), ),
) )
.parse(input) .parse(input)
} }
fn reset(input: &str) -> IResult<&str, Command, VerboseError<&str>> { fn reset(input: &str) -> IResult<&str, Command, VerboseError<&str>> {
all_consuming(map(alt((tag("reset"), tag("R"))), |_| Command::Reset)).parse(input) map(alt((tag("reset"), tag("R"))), |_| Command::Reset).parse(input)
} }
fn get_info(input: &str) -> IResult<&str, Command, VerboseError<&str>> { fn get_info(input: &str) -> IResult<&str, Command, VerboseError<&str>> {
all_consuming(map(alt((tag("get_info"), tag("_"))), |_| Command::GetInfo)).parse(input) map(alt((tag("get_info"), tag("_"))), |_| Command::GetInfo).parse(input)
} }
fn dump_state(input: &str) -> IResult<&str, Command, VerboseError<&str>> { fn dump_state(input: &str) -> IResult<&str, Command, VerboseError<&str>> {
all_consuming(map(tag("dump_state"), |_| Command::DumpState)).parse(input) map(tag("dump_state"), |_| Command::DumpState).parse(input)
} }
fn dump_caps(input: &str) -> IResult<&str, Command, VerboseError<&str>> { fn dump_caps(input: &str) -> IResult<&str, Command, VerboseError<&str>> {
all_consuming(map(alt((tag("dump_caps"), tag("1"))), |_| { map(alt((tag("dump_caps"), tag("1"))), |_| Command::DumpCaps).parse(input)
Command::DumpCaps
}))
.parse(input)
} }
fn send_cmd(input: &str) -> IResult<&str, Command, VerboseError<&str>> { fn send_cmd(input: &str) -> IResult<&str, Command, VerboseError<&str>> {
context( context(
"set_comf", "send_cmd",
all_consuming(map( map(
preceded(terminated(alt((tag("send_cmd"), tag("w"))), space1), rest), preceded(
|rest: &str| Command::SendCmd(rest.to_owned()), terminated(alt((tag("send_cmd"), tag("w"))), space1),
)), many1(none_of("#\n")),
),
|rest: Vec<char>| Command::SendCmd(rest.iter().collect()),
),
) )
.parse(input) .parse(input)
} }
@ -160,10 +164,10 @@ fn command(input: &str) -> IResult<&str, Command, VerboseError<&str>> {
alt(( alt((
exit, exit,
set_pos, set_pos,
park,
get_pos, get_pos,
move_command, move_command,
stop, stop,
park,
set_conf, set_conf,
reset, reset,
get_info, get_info,
@ -175,17 +179,46 @@ fn command(input: &str) -> IResult<&str, Command, VerboseError<&str>> {
.parse(input) .parse(input)
} }
fn comment(input: &str) -> IResult<&str, &str, VerboseError<&str>> {
context(
"comment",
recognize(preceded(multispace0, preceded(tag("#"), not_line_ending))),
)
.parse(input)
}
fn line(input: &str) -> IResult<&str, Command, VerboseError<&str>> {
context(
"line",
all_consuming(terminated(terminated(command, opt(comment)), newline)),
)
.parse(input)
}
pub fn parse_command(input: &str) -> Result<Command, String> {
let result = line(input);
println!("Input: {} Result: {:?}", input, result);
match result {
Ok(("", cmd)) => Ok(cmd),
Ok((rest, _)) => Err("Unable to parse rest".to_owned()),
Err(err) => match err {
NomErr::Incomplete(_) => Err("Command was incomplete".to_owned()),
NomErr::Error(err) | NomErr::Failure(err) => Err(convert_error(input, err)),
},
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use nom::Err as NomErr; use nom::Err as NomErr;
fn assert_command(input: &str, expected: Command) { fn assert_command(input: &str, expected: Command) {
let result = command(input); let result = line(input);
match result { match result {
Ok(("", cmd)) => assert_eq!(cmd, expected), Ok(("", cmd)) => assert_eq!(cmd, expected),
Err(err) => match err { Err(err) => match err {
NomErr::Incomplete(_) => panic!("Command was ncomplete"), NomErr::Incomplete(_) => panic!("Command was incomplete"),
NomErr::Error(err) | NomErr::Failure(err) => { NomErr::Error(err) | NomErr::Failure(err) => {
panic!("{}", convert_error(input, err)) panic!("{}", convert_error(input, err))
} }
@ -196,83 +229,101 @@ mod tests {
#[test] #[test]
fn test_exit() { fn test_exit() {
assert_command("Q", Command::Exit); assert_command("Q\n", Command::Exit);
assert_command("q", Command::Exit); assert_command("q\n", Command::Exit);
} }
#[test] #[test]
fn test_set_pos() { fn test_set_pos() {
assert_command("set_pos 180.0 10.0", Command::SetPos(180.0, 10.0)); assert_command("set_pos 180.0 10.0\n", Command::SetPos(180.0, 10.0));
assert_command("P 180.0 10.0", Command::SetPos(180.0, 10.0)); assert_command("P 180.0 10.0\n", Command::SetPos(180.0, 10.0));
} }
#[test] #[test]
fn test_get_pos() { fn test_get_pos() {
assert_command("get_pos", Command::GetPos); assert_command("get_pos\n", Command::GetPos);
assert_command("p", Command::GetPos); assert_command("p\n", Command::GetPos);
} }
#[test] #[test]
fn test_move() { fn test_move() {
assert_command("M UP 100", Command::Move(Direction::UP, 100)); assert_command("M UP 100\n", Command::Move(Direction::UP, 100));
assert_command("M DOWN -1", Command::Move(Direction::DOWN, -1)); assert_command("M DOWN -1\n", Command::Move(Direction::DOWN, -1));
assert_command("M CCW 42", Command::Move(Direction::CCW, 42)); assert_command("M CCW 42\n", Command::Move(Direction::CCW, 42));
assert_command("M LEFT 42", Command::Move(Direction::CCW, 42)); assert_command("M LEFT 42\n", Command::Move(Direction::CCW, 42));
assert_command("M CW 42", Command::Move(Direction::CW, 42)); assert_command("M CW 42\n", Command::Move(Direction::CW, 42));
assert_command("M RIGHT 42", Command::Move(Direction::CW, 42)); assert_command("M RIGHT 42\n", Command::Move(Direction::CW, 42));
assert_command("move UP 100", Command::Move(Direction::UP, 100)); assert_command("move UP 100\n", Command::Move(Direction::UP, 100));
} }
#[test] #[test]
fn test_stop() { fn test_stop() {
assert_command("stop", Command::Stop); assert_command("stop\n", Command::Stop);
assert_command("S", Command::Stop); assert_command("S\n", Command::Stop);
} }
#[test] #[test]
fn test_park() { fn test_park() {
assert_command("park", Command::Park); assert_command("park\n", Command::Park);
assert_command("K", Command::Park); assert_command("K\n", Command::Park);
} }
#[test] #[test]
fn test_reset() { fn test_reset() {
assert_command("reset", Command::Reset); assert_command("reset\n", Command::Reset);
assert_command("R", Command::Reset); assert_command("R\n", Command::Reset);
} }
#[test] #[test]
fn test_get_info() { fn test_get_info() {
assert_command("get_info", Command::GetInfo); assert_command("get_info\n", Command::GetInfo);
assert_command("_", Command::GetInfo); assert_command("_\n", Command::GetInfo);
} }
#[test] #[test]
fn test_dump_state() { fn test_dump_state() {
assert_command("dump_state", Command::DumpState); assert_command("dump_state\n", Command::DumpState);
} }
#[test] #[test]
fn test_dump_caps() { fn test_dump_caps() {
assert_command("dump_caps", Command::DumpCaps); assert_command("dump_caps\n", Command::DumpCaps);
assert_command("1", Command::DumpCaps); assert_command("1\n", Command::DumpCaps);
} }
#[test] #[test]
fn test_set_conf() { fn test_set_conf() {
assert_command( assert_command(
"set_conf foo bar", "set_conf foo bar\n",
Command::SetConf("foo".to_owned(), "bar".to_owned()), Command::SetConf("foo".to_owned(), "bar".to_owned()),
); );
assert_command( assert_command(
"C foo bar", "C foo bar\n",
Command::SetConf("foo".to_owned(), "bar".to_owned()), Command::SetConf("foo".to_owned(), "bar".to_owned()),
); );
} }
#[test] #[test]
fn test_send_cmd() { fn test_send_cmd() {
assert_command("send_cmd foo bar", Command::SendCmd("foo bar".to_owned())); assert_command("send_cmd foo bar\n", Command::SendCmd("foo bar".to_owned()));
assert_command("w foo bar", Command::SendCmd("foo bar".to_owned())); assert_command("w foo bar\n", Command::SendCmd("foo bar".to_owned()));
}
#[test]
fn test_comments() {
assert_command(
"set_pos 180.0 10.0 # fooo bar lol test\n",
Command::SetPos(180.0, 10.0),
);
assert_command(
"P 180.0 10.0 # fooo bar lol test\n",
Command::SetPos(180.0, 10.0),
);
// TODO: figure out if the trailing space is a problem
assert_command(
"send_cmd foo bar # this is a comment\n",
Command::SendCmd("foo bar ".to_owned()),
);
} }
} }