ionpak-thermostat/firmware/src/command_parser.rs

183 lines
4.8 KiB
Rust

use logos::Logos;
use super::session::ReportMode;
#[derive(Logos, Debug, PartialEq)]
enum Token {
#[end]
End,
#[error]
Error,
#[token = "Quit"]
Quit,
#[token = "show"]
Show,
#[token = "channel"]
Channel,
#[token = "report"]
Report,
#[token = "mode"]
Mode,
#[token = "off"]
Off,
#[token = "once"]
Once,
#[token = "continuous"]
Continuous,
#[token = "enable"]
Enable,
#[token = "disable"]
Disable,
#[token = "setup"]
Setup,
#[token = "ref+"]
RefPos,
#[token = "ref-"]
RefNeg,
#[token = "ain+"]
AinPos,
#[token = "ain-"]
AinNeg,
#[token = "unipolar"]
Unipolar,
#[token = "burnout"]
Burnout,
#[regex = "[0-9]+"]
Number,
}
#[derive(Debug)]
pub enum Error {
Parser,
UnexpectedEnd,
UnexpectedToken(Token),
NoSuchChannel,
NoSuchSetup,
}
#[derive(Debug)]
pub enum ShowCommand {
ReportMode,
Channel(u8),
}
#[derive(Debug)]
pub enum ChannelCommand {
Enable,
Disable,
Setup(u8),
EnableInput(InputBuffer),
DisableInput(InputBuffer),
}
#[derive(Clone, Copy, Debug)]
pub enum InputBuffer {
RefPos,
RefNeg,
AinPos,
AinNeg,
}
#[derive(Debug)]
pub enum Command {
Quit,
Show(ShowCommand),
Report(ReportMode),
Channel(u8, ChannelCommand),
}
const CHANNEL_IDS: &'static [&'static str] = &[
"0", "1", "2", "3",
];
const SETUP_IDS: &'static [&'static str] = CHANNEL_IDS;
impl Command {
pub fn parse(input: &str) -> Result<Self, Error> {
let mut lexer = Token::lexer(input);
/// Match against a set of expected tokens
macro_rules! choice {
[$($token: tt => $block: stmt,)*] => {
match lexer.token {
$(
Token::$token => {
lexer.advance();
$block
}
)*
Token::End => return Err(Error::UnexpectedEnd),
_ => return Err(Error::UnexpectedToken(lexer.token))
}
};
}
/// Expecting no further tokens
macro_rules! end {
($result: expr) => {
match lexer.token {
Token::End => Ok($result),
_ => return Err(Error::UnexpectedToken(lexer.token)),
}
};
}
macro_rules! channel_input {
($channel: expr, $input: tt) => {
choice![
End =>
Ok(Command::Show(ShowCommand::Channel($channel))),
Enable =>
end![Command::Channel($channel, ChannelCommand::EnableInput(InputBuffer::$input))],
Disable =>
end![Command::Channel($channel, ChannelCommand::DisableInput(InputBuffer::$input))],
]
};
}
// Command grammar
choice![
Quit => Ok(Command::Quit),
Report => choice![
Mode => choice![
End => end!(Command::Show(ShowCommand::ReportMode)),
Off => Ok(Command::Report(ReportMode::Off)),
Once => Ok(Command::Report(ReportMode::Once)),
Continuous => Ok(Command::Report(ReportMode::Continuous)),
],
End => Ok(Command::Report(ReportMode::Once)),
],
Channel => {
let channel = choice![
Number => {
CHANNEL_IDS.iter()
.position(|id| *id == lexer.slice())
.ok_or(Error::NoSuchChannel)
},
]? as u8;
choice![
End =>
Ok(Command::Show(ShowCommand::Channel(channel))),
Enable =>
Ok(Command::Channel(channel, ChannelCommand::Enable)),
Disable =>
Ok(Command::Channel(channel, ChannelCommand::Enable)),
Setup => {
let setup = choice![
Number => {
SETUP_IDS.iter()
.position(|id| *id == lexer.slice())
.ok_or(Error::NoSuchSetup)
},
]? as u8;
end!(Command::Channel(channel, ChannelCommand::Setup(setup)))
},
RefPos => channel_input!(channel, RefPos),
RefNeg => channel_input!(channel, RefNeg),
AinPos => channel_input!(channel, AinPos),
AinNeg => channel_input!(channel, AinNeg),
]
},
]
}
}