nmeasis 26.4.1

A memory-safe NMEA 0183 parser with a C FFI
Documentation
use crate::{
    message::NmeaMessageError,
    sentence::{NmeaSentenceError, Sentence},
};

#[derive(Debug, thiserror::Error)]
pub enum NmeaParserError {
    #[error("Sentence was too long for buffer")]
    SentenceTooLong,
    #[error("Sentence Error: {0}")]
    Sentence(#[from] NmeaSentenceError),
}

pub struct NmeaParser<const N: usize> {
    /// Internal buffer used by the Parser
    buf: [u8; N],
    /// Internal Position inside of the Buffer.
    pos: usize,
}

impl<const N: usize> Default for NmeaParser<N> {
    fn default() -> Self {
        Self {
            buf: [0u8; N],
            pos: 0,
        }
    }
}

impl<const N: usize> NmeaParser<N> {
    pub fn buffer(&mut self) -> &mut [u8] {
        &mut self.buf[self.pos..]
    }

    pub fn commit<F: FnMut(Result<Sentence<'_>, NmeaParserError>)>(
        &mut self,
        n: usize,
        mut on_sentence: F,
    ) {
        let available = N - self.pos;
        assert!(
            n <= available,
            "mark_written: n={n} exceeds available space={available}"
        );
        self.pos += n;

        while let Some(nl_index) = memchr::memchr(b'\n', &self.buf[..self.pos]) {
            let end = nl_index + 1;
            let raw_line = &self.buf[..end];

            let line = raw_line
                .strip_suffix(b"\r\n")
                .or_else(|| raw_line.strip_suffix(b"\n"))
                .unwrap_or(raw_line);

            if line.first() == Some(&b'$') {
                on_sentence(Sentence::parse(line).map_err(NmeaParserError::Sentence));
            }

            self.buf.copy_within(end..self.pos, 0);
            self.pos -= end;
        }

        if self.pos == N {
            self.pos = 0;
            on_sentence(Err(NmeaParserError::SentenceTooLong));
        }
    }
}

pub trait NmeaParse<'a>: Sized {
    fn parse(fields: &'a str) -> Result<Self, NmeaMessageError>;
}