From 584eb4aff05c39b478d1d4545f3fee95aa192e64 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 5 May 2024 14:39:41 +0200 Subject: [PATCH] Parser accepts complete lines including \n Allow comments in parser --- src/rotctl.rs | 163 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 107 insertions(+), 56 deletions(-) diff --git a/src/rotctl.rs b/src/rotctl.rs index d895e5d..1bc36f3 100644 --- a/src/rotctl.rs +++ b/src/rotctl.rs @@ -1,12 +1,15 @@ use nom::{ branch::alt, bytes::complete::tag, - character::complete::{alphanumeric1, i8, multispace1, space1}, - combinator::{all_consuming, map, rest}, + character::complete::{ + alphanumeric1, i8, multispace0, multispace1, newline, none_of, not_line_ending, space1, + }, + combinator::{all_consuming, map, opt, recognize, rest}, error::{context, convert_error, VerboseError}, + multi::{many0, many1}, number::complete::float, sequence::{preceded, separated_pair, terminated}, - IResult, Parser, + Err as NomErr, IResult, Parser, }; #[derive(PartialEq, Debug)] @@ -34,7 +37,7 @@ pub enum Direction { } 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>> { @@ -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>> { context( "set_pos", - all_consuming(map( + map( preceded( terminated(alt((tag("set_pos"), tag("P"))), multispace1), float_pair, ), |pair: (f32, f32)| Command::SetPos(pair.0, pair.1), - )), + ), ) .parse(input) } 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>> { @@ -83,26 +86,27 @@ fn move_parameters(input: &str) -> IResult<&str, (Direction, i8), VerboseError<& fn move_command(input: &str) -> IResult<&str, Command, VerboseError<&str>> { context( "move", - all_consuming(map( + map( preceded( terminated(alt((tag("move"), tag("M"))), multispace1), move_parameters, ), |pair: (Direction, i8)| Command::Move(pair.0, pair.1), - )), + ), ) .parse(input) } 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>> { - 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>> { + // TODO: find out if alphanumeric1 is enough. Might need -_ and others context( "string_pair", 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>> { context( "set_comf", - all_consuming(map( + map( preceded( terminated(alt((tag("set_conf"), tag("C"))), multispace1), string_pair, ), |pair: (&str, &str)| Command::SetConf(pair.0.to_owned(), pair.1.to_owned()), - )), + ), ) .parse(input) } 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>> { - 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>> { - 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>> { - all_consuming(map(alt((tag("dump_caps"), tag("1"))), |_| { - Command::DumpCaps - })) - .parse(input) + map(alt((tag("dump_caps"), tag("1"))), |_| Command::DumpCaps).parse(input) } fn send_cmd(input: &str) -> IResult<&str, Command, VerboseError<&str>> { context( - "set_comf", - all_consuming(map( - preceded(terminated(alt((tag("send_cmd"), tag("w"))), space1), rest), - |rest: &str| Command::SendCmd(rest.to_owned()), - )), + "send_cmd", + map( + preceded( + terminated(alt((tag("send_cmd"), tag("w"))), space1), + many1(none_of("#\n")), + ), + |rest: Vec| Command::SendCmd(rest.iter().collect()), + ), ) .parse(input) } @@ -160,10 +164,10 @@ fn command(input: &str) -> IResult<&str, Command, VerboseError<&str>> { alt(( exit, set_pos, + park, get_pos, move_command, stop, - park, set_conf, reset, get_info, @@ -175,17 +179,46 @@ fn command(input: &str) -> IResult<&str, Command, VerboseError<&str>> { .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 { + 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)] mod tests { use super::*; use nom::Err as NomErr; fn assert_command(input: &str, expected: Command) { - let result = command(input); + let result = line(input); match result { Ok(("", cmd)) => assert_eq!(cmd, expected), Err(err) => match err { - NomErr::Incomplete(_) => panic!("Command was ncomplete"), + NomErr::Incomplete(_) => panic!("Command was incomplete"), NomErr::Error(err) | NomErr::Failure(err) => { panic!("{}", convert_error(input, err)) } @@ -196,83 +229,101 @@ mod tests { #[test] fn test_exit() { - assert_command("Q", Command::Exit); - assert_command("q", Command::Exit); + assert_command("Q\n", Command::Exit); + assert_command("q\n", Command::Exit); } #[test] fn test_set_pos() { - assert_command("set_pos 180.0 10.0", Command::SetPos(180.0, 10.0)); - assert_command("P 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\n", Command::SetPos(180.0, 10.0)); } #[test] fn test_get_pos() { - assert_command("get_pos", Command::GetPos); - assert_command("p", Command::GetPos); + assert_command("get_pos\n", Command::GetPos); + assert_command("p\n", Command::GetPos); } #[test] fn test_move() { - assert_command("M UP 100", Command::Move(Direction::UP, 100)); - assert_command("M DOWN -1", Command::Move(Direction::DOWN, -1)); - assert_command("M CCW 42", Command::Move(Direction::CCW, 42)); - assert_command("M LEFT 42", Command::Move(Direction::CCW, 42)); - assert_command("M CW 42", Command::Move(Direction::CW, 42)); - assert_command("M RIGHT 42", Command::Move(Direction::CW, 42)); - assert_command("move UP 100", Command::Move(Direction::UP, 100)); + assert_command("M UP 100\n", Command::Move(Direction::UP, 100)); + assert_command("M DOWN -1\n", Command::Move(Direction::DOWN, -1)); + assert_command("M CCW 42\n", Command::Move(Direction::CCW, 42)); + assert_command("M LEFT 42\n", Command::Move(Direction::CCW, 42)); + assert_command("M CW 42\n", Command::Move(Direction::CW, 42)); + assert_command("M RIGHT 42\n", Command::Move(Direction::CW, 42)); + assert_command("move UP 100\n", Command::Move(Direction::UP, 100)); } #[test] fn test_stop() { - assert_command("stop", Command::Stop); - assert_command("S", Command::Stop); + assert_command("stop\n", Command::Stop); + assert_command("S\n", Command::Stop); } #[test] fn test_park() { - assert_command("park", Command::Park); - assert_command("K", Command::Park); + assert_command("park\n", Command::Park); + assert_command("K\n", Command::Park); } #[test] fn test_reset() { - assert_command("reset", Command::Reset); - assert_command("R", Command::Reset); + assert_command("reset\n", Command::Reset); + assert_command("R\n", Command::Reset); } #[test] fn test_get_info() { - assert_command("get_info", Command::GetInfo); - assert_command("_", Command::GetInfo); + assert_command("get_info\n", Command::GetInfo); + assert_command("_\n", Command::GetInfo); } #[test] fn test_dump_state() { - assert_command("dump_state", Command::DumpState); + assert_command("dump_state\n", Command::DumpState); } #[test] fn test_dump_caps() { - assert_command("dump_caps", Command::DumpCaps); - assert_command("1", Command::DumpCaps); + assert_command("dump_caps\n", Command::DumpCaps); + assert_command("1\n", Command::DumpCaps); } #[test] fn test_set_conf() { assert_command( - "set_conf foo bar", + "set_conf foo bar\n", Command::SetConf("foo".to_owned(), "bar".to_owned()), ); assert_command( - "C foo bar", + "C foo bar\n", Command::SetConf("foo".to_owned(), "bar".to_owned()), ); } #[test] fn test_send_cmd() { - assert_command("send_cmd foo bar", Command::SendCmd("foo bar".to_owned())); - assert_command("w 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\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()), + ); } }