use crate::{encoder::Encoder, Arc, Error, ObjectIdentifier, Result};
#[derive(Debug)]
pub(crate) struct Parser {
current_arc: Arc,
encoder: Encoder,
}
impl Parser {
pub(crate) const fn parse(s: &str) -> Result<Self> {
let bytes = s.as_bytes();
if bytes.is_empty() {
return Err(Error::Empty);
}
match bytes[0] {
b'0'..=b'9' => Self {
current_arc: 0,
encoder: Encoder::new(),
}
.parse_bytes(bytes),
actual => Err(Error::DigitExpected { actual }),
}
}
pub(crate) const fn finish(self) -> Result<ObjectIdentifier> {
self.encoder.finish()
}
const fn parse_bytes(mut self, bytes: &[u8]) -> Result<Self> {
match bytes {
[] => match self.encoder.arc(self.current_arc) {
Ok(encoder) => {
self.encoder = encoder;
Ok(self)
}
Err(err) => Err(err),
},
#[allow(clippy::integer_arithmetic)]
[byte @ b'0'..=b'9', remaining @ ..] => {
let digit = byte.saturating_sub(b'0');
self.current_arc = self.current_arc * 10 + digit as Arc;
self.parse_bytes(remaining)
}
[b'.', remaining @ ..] => {
if remaining.is_empty() {
return Err(Error::TrailingDot);
}
match self.encoder.arc(self.current_arc) {
Ok(encoder) => {
self.encoder = encoder;
self.current_arc = 0;
self.parse_bytes(remaining)
}
Err(err) => Err(err),
}
}
[byte, ..] => Err(Error::DigitExpected { actual: *byte }),
}
}
}
#[cfg(test)]
mod tests {
use super::Parser;
use crate::Error;
#[test]
fn parse() {
let oid = Parser::parse("1.23.456").unwrap().finish().unwrap();
assert_eq!(oid, "1.23.456".parse().unwrap());
}
#[test]
fn reject_empty_string() {
assert_eq!(Parser::parse("").err().unwrap(), Error::Empty);
}
#[test]
fn reject_non_digits() {
assert_eq!(
Parser::parse("X").err().unwrap(),
Error::DigitExpected { actual: b'X' }
);
assert_eq!(
Parser::parse("1.2.X").err().unwrap(),
Error::DigitExpected { actual: b'X' }
);
}
#[test]
fn reject_trailing_dot() {
assert_eq!(Parser::parse("1.23.").err().unwrap(), Error::TrailingDot);
}
}