use core::fmt; use nom::{ IResult, branch::alt, bytes::complete::{is_a, tag, take_while1}, character::{is_digit, complete::char}, combinator::{map, value}, sequence::preceded, multi::fold_many1, error::ErrorKind, }; use lexical_core as lexical; #[derive(Clone, Debug)] pub enum Error { Parser(ErrorKind), Incomplete, UnexpectedInput(u8), ParseNumber(lexical::Error) } impl<'t> From> for Error { fn from(e: nom::Err<(&'t [u8], ErrorKind)>) -> Self { match e { nom::Err::Incomplete(_) => Error::Incomplete, nom::Err::Error((_, e)) => Error::Parser(e), nom::Err::Failure((_, e)) => Error::Parser(e), } } } impl From for Error { fn from(e: lexical::Error) -> Self { Error::ParseNumber(e) } } impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { match self { Error::Incomplete => "incomplete input".fmt(fmt), Error::UnexpectedInput(c) => { "unexpected input: ".fmt(fmt)?; c.fmt(fmt) } Error::Parser(e) => { "parser: ".fmt(fmt)?; (e as &dyn core::fmt::Debug).fmt(fmt) } Error::ParseNumber(e) => { "parsing number: ".fmt(fmt)?; (e as &dyn core::fmt::Debug).fmt(fmt) } } } } #[derive(Debug, Clone)] pub enum ShowCommand { Input, Reporting, Pwm, Pid, } #[derive(Debug, Clone)] pub enum PidParameter { Target, KP, KI, KD, OutputMin, OutputMax, IntegralMin, IntegralMax, } #[derive(Debug, Clone)] pub enum PwmMode { Manual { width: u32, total: u32, }, Pid, } #[derive(Debug, Clone)] pub enum Command { Quit, Show(ShowCommand), Reporting(bool), Pwm(PwmMode), Pid { parameter: PidParameter, value: f32, }, } fn whitespace(input: &[u8]) -> IResult<&[u8], ()> { fold_many1(char(' '), (), |(), _| ())(input) } fn unsigned(input: &[u8]) -> IResult<&[u8], Result> { take_while1(is_digit)(input) .map(|(input, digits)| (input, lexical::parse(digits))) } fn float(input: &[u8]) -> IResult<&[u8], Result> { let (input, sign) = is_a("-")(input)?; let negative = sign.len() > 0; let (input, digits) = take_while1(|c| is_digit(c) || c == '.' as u8)(input)?; let result = lexical::parse(digits) .map(|result: f32| if negative { -result } else { result }); Ok((input, result)) } fn off_on(input: &[u8]) -> IResult<&[u8], bool> { alt((value(false, tag("off")), value(true, tag("on")) ))(input) } fn report(input: &[u8]) -> IResult<&[u8], Command> { preceded( tag("report"), alt(( preceded( whitespace, preceded( tag("mode"), alt(( preceded( whitespace, // `report mode ` - Switch repoting mode map(off_on, |reporting| Command::Reporting(reporting)) ), // `report mode` - Show current reporting state |input| Ok((input, Command::Show(ShowCommand::Reporting))) )) )), // `report` - Report once |input| Ok((input, Command::Show(ShowCommand::Input))) )) )(input) } /// `pwm ` - Set pwm duty cycle fn pwm_manual(input: &[u8]) -> IResult<&[u8], Result> { let (input, width) = unsigned(input)?; let width = match width { Ok(width) => width, Err(e) => return Ok((input, Err(e.into()))), }; let (input, _) = whitespace(input)?; let (input, total) = unsigned(input)?; let total = match total { Ok(total) => total, Err(e) => return Ok((input, Err(e.into()))), }; Ok((input, Ok(Command::Pwm(PwmMode::Manual { width, total })))) } /// `pwm pid` - Set PWM to be controlled by PID fn pwm_pid(input: &[u8]) -> IResult<&[u8], Result> { value(Ok(Command::Pwm(PwmMode::Pid)), tag("pid"))(input) } fn pwm(input: &[u8]) -> IResult<&[u8], Result> { let (input, _) = tag("pwm")(input)?; alt(( preceded( whitespace, alt(( pwm_pid, pwm_manual, ))), |input| Ok((input, Ok(Command::Show(ShowCommand::Pwm)))) ))(input) } fn pid_parameter(input: &[u8]) -> IResult<&[u8], Result> { let (input, parameter) = alt((value(PidParameter::Target, tag("target")), value(PidParameter::KP, tag("kp")), value(PidParameter::KI, tag("ki")), value(PidParameter::KD, tag("kd")), value(PidParameter::OutputMin, tag("output_min")), value(PidParameter::OutputMax, tag("output_max")), value(PidParameter::IntegralMin, tag("integral_min")), value(PidParameter::IntegralMax, tag("integral_max")) ))(input)?; let (input, _) = whitespace(input)?; let (input, value) = float(input)?; let result = value .map(|value| Command::Pid { parameter, value }) .map_err(|e| e.into()); Ok((input, result)) } /// `pid ` fn pid(input: &[u8]) -> IResult<&[u8], Result> { let (input, _) = tag("pid")(input)?; let (input, _) = whitespace(input)?; alt(( preceded( whitespace, pid_parameter ), |input| Ok((input, Ok(Command::Show(ShowCommand::Pid)))) ))(input) } fn command(input: &[u8]) -> IResult<&[u8], Result> { alt((value(Ok(Command::Quit), tag("quit")), |input| report(input).map(|(input, command)| { (input, Ok(command)) }), pwm, pid, ))(input) } impl Command { pub fn parse(input: &[u8]) -> Result { match command(input) { Ok((b"", result)) => result, Ok((input_remain, _)) => Err(Error::UnexpectedInput(input_remain[0])), Err(e) => Err(e.into()), } } }