Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ impl<'a> CommandBuilder<'a, Uninitialized> {
builder
}
}
impl<'a, ANY> CommandBuilder<'a, ANY> {
impl<ANY> CommandBuilder<'_, ANY> {
/// Tries to append data to the buffer.
///
/// If it won't fit, it silently fails and won't copy the data.
Expand Down Expand Up @@ -162,7 +162,7 @@ impl<'a, N: Nameable> CommandBuilder<'a, Initialized<N>> {
}
}

impl<'a> CommandBuilder<'a, Set> {
impl CommandBuilder<'_, Set> {
/// Add an integer parameter.
pub fn with_int_parameter<INT: Into<i32>>(mut self, value: INT) -> Self {
let mut formatting_buffer = [0; crate::formatter::MAX_INT_DIGITS];
Expand Down
172 changes: 153 additions & 19 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<i32>) {
let mut new_buffer_index = self.buffer_index;
// Get the end index of the current parameter.
Expand Down Expand Up @@ -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();
Expand All @@ -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<D, ParseError> {
if self.data_valid {
Expand All @@ -280,21 +322,21 @@ impl<'a, D: TupleConcat<i32>> 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()
}
}
}
Expand All @@ -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()
}
}

Expand All @@ -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()
}
}
}
Expand All @@ -383,13 +457,13 @@ impl<'a, D: TupleConcat<Option<i32>>> 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()
}
}

Expand All @@ -407,13 +481,13 @@ impl<'a, D: TupleConcat<Option<&'a str>>> 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
Expand All @@ -428,14 +502,36 @@ impl<'a, D: TupleConcat<Option<&'a str>>> 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()
}
}

Expand Down Expand Up @@ -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);
}
}
Loading