use crate::{encoder::Encoder, Arc, ObjectIdentifier};
pub(crate) struct Parser {
current_arc: Arc,
encoder: Encoder,
}
impl Parser {
pub(crate) const fn parse(s: &str) -> Self {
let bytes = s.as_bytes();
const_assert!(!bytes.is_empty(), "OID string is empty");
const_assert!(
matches!(bytes[0], b'0'..=b'9'),
"OID must start with a digit"
);
let current_arc = 0;
let encoder = Encoder::new();
Self {
current_arc,
encoder,
}
.parse_bytes(bytes)
}
pub(crate) const fn finish(self) -> ObjectIdentifier {
self.encoder.finish()
}
const fn parse_bytes(mut self, bytes: &[u8]) -> Self {
match bytes {
[] => {
self.encoder = self.encoder.encode(self.current_arc);
self
}
[byte @ b'0'..=b'9', remaining @ ..] => {
self.current_arc = self.current_arc * 10 + parse_ascii_digit(*byte);
self.parse_bytes(remaining)
}
[b'.', remaining @ ..] => {
const_assert!(!remaining.is_empty(), "invalid trailing '.' in OID");
self.encoder = self.encoder.encode(self.current_arc);
self.current_arc = 0;
self.parse_bytes(remaining)
}
[byte, ..] => {
const_assert!(
matches!(byte, b'0'..=b'9' | b'.'),
"invalid character in OID"
);
self
}
}
}
}
const fn parse_ascii_digit(char: u8) -> Arc {
match char {
b'0' => 0,
b'1' => 1,
b'2' => 2,
b'3' => 3,
b'4' => 4,
b'5' => 5,
b'6' => 6,
b'7' => 7,
b'8' => 8,
b'9' => 9,
other => {
const_assert!(matches!(other, b'0'..=b'9'), "invalid ASCII digit");
0 }
}
}
#[cfg(test)]
mod tests {
use super::Parser;
#[test]
fn parse() {
let oid = Parser::parse("1.23.456").finish();
assert_eq!(oid, "1.23.456".parse().unwrap());
}
#[test]
#[should_panic]
fn reject_empty_string() {
Parser::parse("");
}
#[test]
#[should_panic]
fn reject_non_digits() {
Parser::parse("X");
}
#[test]
#[should_panic]
fn reject_trailing_dot() {
Parser::parse("1.23.");
}
}