diff --git a/CHANGELOG.md b/CHANGELOG.md index ce23746..36f6e5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- Add raw string parameter to command parser [diondokter/at-commands#14](https://github.com/diondokter/at-commands/pull/14) + ## [0.5.5] - 2024-09-26 - Add raw parameter to command builder. diff --git a/src/builder.rs b/src/builder.rs index 500aaa2..a1b1821 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -122,7 +122,7 @@ impl<'a> CommandBuilder<'a, Uninitialized> { builder } } -impl<'a, ANY> CommandBuilder<'a, ANY> { +impl CommandBuilder<'_, ANY> { /// Tries to append data to the buffer. /// /// If it won't fit, it silently fails and won't copy the data. @@ -162,7 +162,7 @@ impl<'a, N: Nameable> CommandBuilder<'a, Initialized> { } } -impl<'a> CommandBuilder<'a, Set> { +impl CommandBuilder<'_, Set> { /// Add an integer parameter. pub fn with_int_parameter>(mut self, value: INT) -> Self { let mut formatting_buffer = [0; crate::formatter::MAX_INT_DIGITS]; diff --git a/src/parser.rs b/src/parser.rs index 4a43015..7c6e062 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -169,6 +169,16 @@ impl<'a, D> CommandParser<'a, D> { .unwrap_or(self.buffer.len()) } + /// Finds the index of the character after the raw string parameter (comma or end of data). + 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) { let mut new_buffer_index = self.buffer_index; // Get the end index of the current parameter. @@ -237,7 +247,7 @@ impl<'a, D> CommandParser<'a, D> { } } - fn parse_raw_string_parameter(&self) -> (usize, bool, Option<&'a str>) { + fn parse_raw_string(&self) -> (usize, bool, Option<&'a str>) { let mut new_buffer_index = self.buffer_index; // Get the end index of the current string. let end = self.find_end_of_raw_string(); @@ -255,6 +265,38 @@ impl<'a, D> CommandParser<'a, D> { } } + fn parse_raw_string_parameter(&self) -> (usize, bool, Option<&'a str>) { + let mut new_buffer_index = self.buffer_index; + // Get the end index of the current parameter. + let parameter_end = self.find_end_of_raw_string_parameter(); + // Get the bytes in which the string should reside. + let string_slice = &self.buffer[new_buffer_index..parameter_end]; + + if string_slice.is_empty() { + // We probably hit the end of the buffer. + // The parameter is empty but as it is optional not invalid + // Advance the index to the character after the parameter separator (comma) if it's there. + 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 + }; + + // Advance the index to the character after the parameter separator. + new_buffer_index = parameter_end + has_comma_after_parameter as usize; + // If we've found a valid string, then the data may be valid and we allow the closure to set the result ok data. + if let Ok(parameter_value) = core::str::from_utf8(string_slice) { + (new_buffer_index, true, Some(parameter_value)) + } else { + (new_buffer_index, false, None) + } + } + /// Finish parsing the command and get the results pub fn finish(self) -> Result { if self.data_valid { @@ -280,21 +322,21 @@ impl<'a, D: TupleConcat> CommandParser<'a, D> { let (buffer_index, data_valid, data) = self.parse_int_parameter(); if let Some(parameter_value) = data { - return CommandParser { + CommandParser { buffer: self.buffer, buffer_index, data_valid, data: self.data.tup_cat(parameter_value), } - .trim_space(); + .trim_space() } else { - return CommandParser { + CommandParser { buffer: self.buffer, buffer_index, data_valid: false, data: self.data.tup_cat(0), } - .trim_space(); + .trim_space() } } } @@ -314,21 +356,21 @@ impl<'a, D: TupleConcat<&'a str>> CommandParser<'a, D> { let (buffer_index, data_valid, data) = self.parse_string_parameter(); if let Some(parameter_value) = data { - return CommandParser { + CommandParser { buffer: self.buffer, buffer_index, data_valid, data: self.data.tup_cat(parameter_value), } - .trim_space(); + .trim_space() } else { - return CommandParser { + CommandParser { buffer: self.buffer, buffer_index, data_valid: false, data: self.data.tup_cat(""), } - .trim_space(); + .trim_space() } } @@ -344,23 +386,55 @@ impl<'a, D: TupleConcat<&'a str>> CommandParser<'a, D> { }; } - let (buffer_index, data_valid, data) = self.parse_raw_string_parameter(); + let (buffer_index, data_valid, data) = self.parse_raw_string(); if let Some(parameter_value) = data { - return CommandParser { + CommandParser { buffer: self.buffer, buffer_index, data_valid, data: self.data.tup_cat(parameter_value), } - .trim_space(); + .trim_space() } else { + CommandParser { + buffer: self.buffer, + buffer_index, + data_valid: false, + data: self.data.tup_cat(""), + } + .trim_space() + } + } + + /// Tries reading a raw string parameter (non-quoted string separated by commas) + pub fn expect_raw_string_parameter(self) -> CommandParser<'a, D::Out> { + // If we're already not valid, then quit + 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(); + .trim_space() } } } @@ -383,13 +457,13 @@ impl<'a, D: TupleConcat>> CommandParser<'a, D> { } let (buffer_index, data_valid, data) = self.parse_int_parameter(); - return CommandParser { + CommandParser { buffer: self.buffer, buffer_index, data_valid, data: self.data.tup_cat(data), } - .trim_space(); + .trim_space() } } @@ -407,13 +481,13 @@ impl<'a, D: TupleConcat>> CommandParser<'a, D> { } let (buffer_index, data_valid, data) = self.parse_string_parameter(); - return CommandParser { + CommandParser { buffer: self.buffer, buffer_index, data_valid, data: self.data.tup_cat(data), } - .trim_space(); + .trim_space() } /// Tries reading a non-parameter, non-quoted string @@ -428,14 +502,36 @@ impl<'a, D: TupleConcat>> CommandParser<'a, D> { }; } + 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() + } + + /// Tries reading an optional raw string parameter (non-quoted string separated by commas) + pub fn expect_optional_raw_string_parameter(self) -> CommandParser<'a, D::Out> { + // If we're already not valid, then quit + 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(); - return CommandParser { + CommandParser { buffer: self.buffer, buffer_index, data_valid, data: self.data.tup_cat(data), } - .trim_space(); + .trim_space() } } @@ -593,4 +689,42 @@ mod tests { assert_eq!(y, Some("true")); assert_eq!(z, None); } + + #[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); + } }