use crate::tuple_concat::TupleConcat;
#[must_use]
pub struct CommandParser<'a, D> {
buffer: &'a [u8],
buffer_index: usize,
data_valid: bool,
data: D,
}
impl<'a> CommandParser<'a, ()> {
pub fn parse(buffer: &'a [u8]) -> CommandParser<'a, ()> {
CommandParser {
buffer,
buffer_index: 0,
data_valid: true,
data: (),
}
}
}
impl<'a, D> CommandParser<'a, D> {
pub fn expect_identifier(mut self, identifier: &[u8]) -> Self {
if !self.data_valid {
return self;
}
if self.buffer[self.buffer_index..].len() < identifier.len() {
self.data_valid = false;
return self;
}
self.data_valid = self.buffer[self.buffer_index..]
.iter()
.zip(identifier)
.all(|(buffer, id)| *buffer == *id);
self.buffer_index += identifier.len();
self.trim_space()
}
pub fn expect_optional_identifier(mut self, identifier: &[u8]) -> Self {
if !self.data_valid {
return self;
}
if self.buffer[self.buffer_index..].is_empty() {
return self;
}
if self.buffer[self.buffer_index..].len() < identifier.len() {
self.data_valid = false;
return self;
}
let found_optional = self.buffer[self.buffer_index..]
.iter()
.zip(identifier)
.all(|(buffer, id)| *buffer == *id);
if found_optional {
self.buffer_index += identifier.len();
self.trim_space()
} else {
self
}
}
fn trim_space(mut self) -> Self {
if !self.data_valid {
return self;
}
while let Some(c) = self.buffer.get(self.buffer_index) {
if *c == b' ' {
self.buffer_index += 1;
} else {
break;
}
}
self
}
pub fn trim_whitespace(mut self) -> Self {
if !self.data_valid {
return self;
}
let white_spaces = self.buffer[self.buffer_index..]
.iter()
.take_while(|c| c.is_ascii_whitespace())
.count();
self.buffer_index += white_spaces;
self
}
fn find_end_of_int_parameter(&self) -> usize {
self.buffer_index
+ self
.buffer
.get(self.buffer_index..)
.map(|buffer| {
buffer
.iter()
.take_while(|byte| {
byte.is_ascii_digit() || **byte == b'-' || **byte == b'+'
})
.count()
})
.unwrap_or(self.buffer.len())
}
fn find_end_of_string_parameter(&self) -> usize {
let mut counted_quotes = 0;
self.buffer_index
+ self
.buffer
.get(self.buffer_index..)
.map(|buffer| {
buffer
.iter()
.take_while(|byte| {
counted_quotes += (**byte == b'"') as u8;
counted_quotes < 2
})
.count()
+ 1
})
.unwrap_or(self.buffer.len())
}
fn find_end_of_raw_string(&self) -> usize {
self.buffer_index
+ self
.buffer
.get(self.buffer_index..)
.map(|buffer| {
buffer
.iter()
.take_while(|byte| !(**byte as char).is_ascii_control())
.count()
+ 1
})
.unwrap_or(self.buffer.len())
}
fn find_end_of_raw_string_parameter(&self) -> usize {
self.buffer_index
+ self
.buffer
.get(self.buffer_index..)
.map(|buffer| buffer.iter().take_while(|byte| **byte != b',').count())
.unwrap_or(self.buffer.len())
}
fn parse_int_parameter(&self) -> (usize, bool, Option<i32>) {
let mut new_buffer_index = self.buffer_index;
let parameter_end = self.find_end_of_int_parameter();
let int_slice = match self.buffer.get(self.buffer_index..parameter_end) {
None => {
return (new_buffer_index, false, None);
}
Some(int_slice) => int_slice,
};
if int_slice.is_empty() {
new_buffer_index =
parameter_end + (self.buffer.get(parameter_end) == Some(&b',')) as usize;
return (new_buffer_index, true, None);
}
let int_slice = if int_slice[0] == b'+' {
&int_slice[1..]
} else {
int_slice
};
let parsed_int = crate::formatter::parse_int(int_slice);
new_buffer_index = parameter_end + (self.buffer.get(parameter_end) == Some(&b',')) as usize;
if let Some(parameter_value) = parsed_int {
(new_buffer_index, true, Some(parameter_value))
} else {
(new_buffer_index, false, None)
}
}
fn parse_string_parameter(&self) -> (usize, bool, Option<&'a str>) {
let mut new_buffer_index = self.buffer_index;
let parameter_end = self.find_end_of_string_parameter();
if parameter_end > self.buffer.len() {
return (new_buffer_index, true, None);
}
let string_slice = &self.buffer[(new_buffer_index + 1)..(parameter_end - 1)];
let has_comma_after_parameter = if let Some(next_char) = self.buffer.get(parameter_end) {
*next_char == b','
} else {
false
};
new_buffer_index = parameter_end + has_comma_after_parameter as usize;
if let Ok(parameter_value) = core::str::from_utf8(string_slice) {
(new_buffer_index, true, Some(parameter_value))
} else {
(new_buffer_index, false, None)
}
}
fn parse_raw_string(&self) -> (usize, bool, Option<&'a str>) {
let mut new_buffer_index = self.buffer_index;
let end = self.find_end_of_raw_string();
let string_slice = &self.buffer[new_buffer_index..(end - 1)];
new_buffer_index = end - 1usize;
if let Ok(parameter_value) = core::str::from_utf8(string_slice) {
(new_buffer_index, true, Some(parameter_value))
} else {
(new_buffer_index, false, None)
}
}
fn parse_raw_string_parameter(&self) -> (usize, bool, Option<&'a str>) {
let mut new_buffer_index = self.buffer_index;
let parameter_end = self.find_end_of_raw_string_parameter();
let string_slice = &self.buffer[new_buffer_index..parameter_end];
if string_slice.is_empty() {
new_buffer_index =
parameter_end + (self.buffer.get(parameter_end) == Some(&b',')) as usize;
return (new_buffer_index, true, None);
}
let has_comma_after_parameter = if let Some(next_char) = self.buffer.get(parameter_end) {
*next_char == b','
} else {
false
};
new_buffer_index = parameter_end + has_comma_after_parameter as usize;
if let Ok(parameter_value) = core::str::from_utf8(string_slice) {
(new_buffer_index, true, Some(parameter_value))
} else {
(new_buffer_index, false, None)
}
}
pub fn finish(self) -> Result<D, ParseError> {
if self.data_valid {
Ok(self.data)
} else {
Err(ParseError(self.buffer_index))
}
}
}
impl<'a, D: TupleConcat<i32>> CommandParser<'a, D> {
pub fn expect_int_parameter(self) -> CommandParser<'a, D::Out> {
if !self.data_valid {
return CommandParser {
buffer: self.buffer,
buffer_index: self.buffer_index,
data_valid: self.data_valid,
data: self.data.tup_cat(0),
};
}
let (buffer_index, data_valid, data) = self.parse_int_parameter();
if let Some(parameter_value) = data {
CommandParser {
buffer: self.buffer,
buffer_index,
data_valid,
data: self.data.tup_cat(parameter_value),
}
.trim_space()
} else {
CommandParser {
buffer: self.buffer,
buffer_index,
data_valid: false,
data: self.data.tup_cat(0),
}
.trim_space()
}
}
}
impl<'a, D: TupleConcat<&'a str>> CommandParser<'a, D> {
pub fn expect_string_parameter(self) -> CommandParser<'a, D::Out> {
if !self.data_valid {
return CommandParser {
buffer: self.buffer,
buffer_index: self.buffer_index,
data_valid: self.data_valid,
data: self.data.tup_cat(""),
};
}
let (buffer_index, data_valid, data) = self.parse_string_parameter();
if let Some(parameter_value) = data {
CommandParser {
buffer: self.buffer,
buffer_index,
data_valid,
data: self.data.tup_cat(parameter_value),
}
.trim_space()
} else {
CommandParser {
buffer: self.buffer,
buffer_index,
data_valid: false,
data: self.data.tup_cat(""),
}
.trim_space()
}
}
pub fn expect_raw_string(self) -> CommandParser<'a, D::Out> {
if !self.data_valid {
return CommandParser {
buffer: self.buffer,
buffer_index: self.buffer_index,
data_valid: self.data_valid,
data: self.data.tup_cat(""),
};
}
let (buffer_index, data_valid, data) = self.parse_raw_string();
if let Some(parameter_value) = data {
CommandParser {
buffer: self.buffer,
buffer_index,
data_valid,
data: self.data.tup_cat(parameter_value),
}
.trim_space()
} else {
CommandParser {
buffer: self.buffer,
buffer_index,
data_valid: false,
data: self.data.tup_cat(""),
}
.trim_space()
}
}
pub fn expect_raw_string_parameter(self) -> CommandParser<'a, D::Out> {
if !self.data_valid {
return CommandParser {
buffer: self.buffer,
buffer_index: self.buffer_index,
data_valid: self.data_valid,
data: self.data.tup_cat(""),
};
}
let (buffer_index, data_valid, data) = self.parse_raw_string_parameter();
if let Some(parameter_value) = data {
CommandParser {
buffer: self.buffer,
buffer_index,
data_valid,
data: self.data.tup_cat(parameter_value),
}
.trim_space()
} else {
CommandParser {
buffer: self.buffer,
buffer_index,
data_valid: false,
data: self.data.tup_cat(""),
}
.trim_space()
}
}
}
impl<'a, D: TupleConcat<Option<i32>>> CommandParser<'a, D> {
pub fn expect_optional_int_parameter(self) -> CommandParser<'a, D::Out> {
if !self.data_valid {
return CommandParser {
buffer: self.buffer,
buffer_index: self.buffer_index,
data_valid: self.data_valid,
data: self.data.tup_cat(None),
};
}
let (buffer_index, data_valid, data) = self.parse_int_parameter();
CommandParser {
buffer: self.buffer,
buffer_index,
data_valid,
data: self.data.tup_cat(data),
}
.trim_space()
}
}
impl<'a, D: TupleConcat<Option<&'a str>>> CommandParser<'a, D> {
pub fn expect_optional_string_parameter(self) -> CommandParser<'a, D::Out> {
if !self.data_valid {
return CommandParser {
buffer: self.buffer,
buffer_index: self.buffer_index,
data_valid: self.data_valid,
data: self.data.tup_cat(None),
};
}
let (buffer_index, data_valid, data) = self.parse_string_parameter();
CommandParser {
buffer: self.buffer,
buffer_index,
data_valid,
data: self.data.tup_cat(data),
}
.trim_space()
}
pub fn expect_optional_raw_string(self) -> CommandParser<'a, D::Out> {
if !self.data_valid {
return CommandParser {
buffer: self.buffer,
buffer_index: self.buffer_index,
data_valid: self.data_valid,
data: self.data.tup_cat(None),
};
}
let (buffer_index, data_valid, data) = self.parse_raw_string();
CommandParser {
buffer: self.buffer,
buffer_index,
data_valid,
data: self.data.tup_cat(data),
}
.trim_space()
}
pub fn expect_optional_raw_string_parameter(self) -> CommandParser<'a, D::Out> {
if !self.data_valid {
return CommandParser {
buffer: self.buffer,
buffer_index: self.buffer_index,
data_valid: self.data_valid,
data: self.data.tup_cat(None),
};
}
let (buffer_index, data_valid, data) = self.parse_raw_string_parameter();
CommandParser {
buffer: self.buffer,
buffer_index,
data_valid,
data: self.data.tup_cat(data),
}
.trim_space()
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ParseError(usize);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ok() {
let (x, y, z) = CommandParser::parse(b"+SYSGPIOREAD:654,\"true\",-65154\r\nOK\r\n")
.expect_identifier(b"+SYSGPIOREAD:")
.expect_int_parameter()
.expect_string_parameter()
.expect_int_parameter()
.expect_identifier(b"\r\nOK\r\n")
.finish()
.unwrap();
assert_eq!(x, 654);
assert_eq!(y, "true");
assert_eq!(z, -65154);
}
#[test]
fn test_positive_int_param() {
let (x,) = CommandParser::parse(b"OK+RP:+20dBm\r\n")
.expect_identifier(b"OK+RP:")
.expect_int_parameter()
.expect_identifier(b"dBm\r\n")
.finish()
.unwrap();
assert_eq!(x, 20);
}
#[test]
fn test_whitespace() {
let (x, y, z) = CommandParser::parse(b"+SYSGPIOREAD: 654, \"true\", -65154 \r\nOK\r\n")
.expect_identifier(b"+SYSGPIOREAD:")
.expect_int_parameter()
.expect_string_parameter()
.expect_int_parameter()
.expect_identifier(b"\r\nOK\r\n")
.finish()
.unwrap();
assert_eq!(x, 654);
assert_eq!(y, "true");
assert_eq!(z, -65154);
}
#[test]
fn string_param_at_end() {
let (x, y) = CommandParser::parse(br#"+SYSGPIOREAD: 42, "param at end""#)
.expect_identifier(b"+SYSGPIOREAD:")
.expect_int_parameter()
.expect_string_parameter()
.finish()
.unwrap();
assert_eq!(x, 42);
assert_eq!(y, "param at end");
}
#[test]
fn test_optional_int_parameter_all_present() {
let (x, y, z) = CommandParser::parse(b"+SYSGPIOREAD:654,\"true\",-65154\r\nOK\r\n")
.expect_identifier(b"+SYSGPIOREAD:")
.expect_optional_int_parameter()
.expect_optional_string_parameter()
.expect_optional_int_parameter()
.expect_identifier(b"\r\nOK\r\n")
.finish()
.unwrap();
assert_eq!(x, Some(654));
assert_eq!(y, Some("true"));
assert_eq!(z, Some(-65154));
}
#[test]
fn test_optional_int_parameter_middle_not_present() {
let (x, y, z) = CommandParser::parse(b"+SYSGPIOREAD:,\"true\"\r\nOK\r\n")
.expect_identifier(b"+SYSGPIOREAD:")
.expect_optional_int_parameter()
.expect_optional_string_parameter()
.expect_optional_int_parameter()
.expect_identifier(b"\r\nOK\r\n")
.finish()
.unwrap();
assert_eq!(x, None);
assert_eq!(y, Some("true"));
assert_eq!(z, None);
}
#[test]
fn test_optional_int_parameter_end_not_present() {
let (x, y, z) = CommandParser::parse(b"+SYSGPIOREAD:654,\"true\",\r\nOK\r\n")
.expect_identifier(b"+SYSGPIOREAD:")
.expect_optional_int_parameter()
.expect_optional_string_parameter()
.expect_optional_int_parameter()
.expect_optional_identifier(b"\r\nOK\r\n")
.finish()
.unwrap();
assert_eq!(x, Some(654));
assert_eq!(y, Some("true"));
assert_eq!(z, None);
}
#[test]
fn test_optional_identifier() {
let r = CommandParser::parse(b"+SYSGPIOREAD:,\"true\"\r\nK\r\n")
.expect_identifier(b"+SYSGPIOREAD:")
.expect_optional_int_parameter()
.expect_optional_string_parameter()
.expect_optional_int_parameter()
.expect_optional_identifier(b"\r\nOK\r\n")
.finish();
assert_eq!(r, Err(ParseError(20)));
let (x, y, z) = CommandParser::parse(b"+SYSGPIOREAD:,\"true\"\r\nOK\r\n")
.expect_identifier(b"+SYSGPIOREAD:")
.expect_optional_int_parameter()
.expect_optional_string_parameter()
.expect_optional_int_parameter()
.expect_optional_identifier(b"\r\nOK\r\n")
.finish()
.unwrap();
assert_eq!(x, None);
assert_eq!(y, Some("true"));
assert_eq!(z, None);
let (x, y, z) = CommandParser::parse(b"+SYSGPIOREAD:,\"true\"")
.expect_identifier(b"+SYSGPIOREAD:")
.expect_optional_int_parameter()
.expect_optional_string_parameter()
.expect_optional_int_parameter()
.expect_optional_identifier(b"\r\nOK\r\n")
.finish()
.unwrap();
assert_eq!(x, None);
assert_eq!(y, Some("true"));
assert_eq!(z, None);
}
#[test]
fn test_optional_identifier_multiple_cases() {
const OK_1: &str = "\r\nOK";
const OK_2: &str = "\r\nOK\r\n";
const OK_3: &str = "OK\r\n";
const OK_4: &str = "OK";
static TEST_CASES: [&str; 4] = [OK_1, OK_2, OK_3, OK_4];
for test_case in TEST_CASES {
let result = CommandParser::parse(test_case.as_bytes())
.expect_optional_identifier(b"\r")
.expect_optional_identifier(b"\n")
.expect_identifier(b"OK")
.expect_optional_identifier(b"\r")
.expect_optional_identifier(b"\n")
.finish();
assert_eq!(result, Ok(()), "Failed test case: {:?}", test_case);
}
}
#[test]
fn test_raw_string_parameter() {
let (x, y, raw, z) =
CommandParser::parse(b"+SYSGPIOREAD:654,\"true\",123ABC,-65154\r\nOK\r\n")
.expect_identifier(b"+SYSGPIOREAD:")
.expect_int_parameter()
.expect_string_parameter()
.expect_raw_string_parameter()
.expect_int_parameter()
.expect_identifier(b"\r\nOK\r\n")
.finish()
.unwrap();
assert_eq!(x, 654);
assert_eq!(y, "true");
assert_eq!(raw, "123ABC");
assert_eq!(z, -65154);
}
#[test]
fn raw_string_parameters_includes_non_ascii_characters() {
let (x, y, raw, z) =
CommandParser::parse("+SYSGPIOREAD:654,\"true\",123àABC,-65154\r\nOK\r\n".as_bytes())
.expect_identifier(b"+SYSGPIOREAD:")
.expect_int_parameter()
.expect_string_parameter()
.expect_raw_string_parameter()
.expect_int_parameter()
.expect_identifier(b"\r\nOK\r\n")
.finish()
.unwrap();
assert_eq!(x, 654);
assert_eq!(y, "true");
assert_eq!(raw, "123àABC");
assert_eq!(z, -65154);
}
#[test]
fn test_trim_whitespaces() {
let (x, y, raw, z) = CommandParser::parse(
"\r\n +SYSGPIOREAD:654,\"true\",123àABC,-65154\r\nOK\r\n".as_bytes(),
)
.trim_whitespace()
.expect_identifier(b"+SYSGPIOREAD:")
.expect_int_parameter()
.expect_string_parameter()
.expect_raw_string_parameter()
.expect_int_parameter()
.trim_whitespace()
.expect_identifier(b"OK\r\n")
.finish()
.unwrap();
assert_eq!(x, 654);
assert_eq!(y, "true");
assert_eq!(raw, "123àABC");
assert_eq!(z, -65154);
}
#[test]
fn test_trim_whitespaces_no_whitespace() {
let (x, y, raw, z) =
CommandParser::parse("+SYSGPIOREAD:654,\"true\",123àABC,-65154\r\nOK\r\n".as_bytes())
.trim_whitespace()
.expect_identifier(b"+SYSGPIOREAD:")
.trim_whitespace()
.expect_int_parameter()
.trim_whitespace()
.expect_string_parameter()
.trim_whitespace()
.expect_raw_string_parameter()
.trim_whitespace()
.expect_int_parameter()
.expect_identifier(b"\r\nOK\r\n")
.trim_whitespace()
.finish()
.unwrap();
assert_eq!(x, 654);
assert_eq!(y, "true");
assert_eq!(raw, "123àABC");
assert_eq!(z, -65154);
}
}