//! End-of-file detection for complete input consumption. //! //! This module provides the [`EndOfFile`] parser that succeeds only when there //! are no more bytes to read from the input source. This is essential for //! ensuring that parsers have consumed the entire input, not just a prefix. //! //! The EndOfFile parser is commonly used as the final parser in a sequence //! to validate that parsing was complete and no unexpected trailing content //! remains in the input stream. use crate::{ cache::ParsingCache, parser::{Parsable, Parser, Source}, result::{Error, ParseResult}, }; /// A parser that succeeds only at the end of input. /// /// EndOfFile succeeds when there are no more bytes to read from the input source, /// making it useful for ensuring complete consumption of input. It returns unit /// on success and fails with `NoMatch` if there is still input remaining. /// /// # Examples /// /// ```rust /// use neotoma::{eof::EndOfFile, literal::Literal, seq, parser::{parse, Source}}; /// use std::io::Cursor; /// /// // Parser that matches "hello" followed by end of file /// let hello_eof = seq![Literal::from_str("hello"), EndOfFile::new()]; /// /// // Succeeds: "hello" (complete match) /// let mut input1 = Cursor::new(b"hello"); /// let mut source1 = Source::new(input1); /// let result1 = parse(hello_eof.clone(), &mut source1); /// assert!(result1.is_ok()); /// /// // Fails: "hello world" (has trailing content) /// let mut input2 = Cursor::new(b"hello world"); /// let mut source2 = Source::new(input2); /// let result2 = parse(hello_eof, &mut source2); /// assert!(result2.is_err()); /// ``` /// /// # Use Cases /// /// - Ensuring complete parsing of input files /// - Validating that expressions don't have trailing garbage /// - Creating strict parsers for configuration files /// - Testing parser completeness in unit tests #[derive(Clone, Debug, PartialEq, Eq)] pub struct EndOfFile; impl EndOfFile { /// Create a new EndOfFile parser. /// /// # Examples /// /// ```rust /// use neotoma::{eof::EndOfFile, parser::{parse, Source}}; /// use std::io::Cursor; /// /// let eof = EndOfFile::new(); /// /// // Succeeds on empty input /// let mut input1 = Cursor::new(b""); /// let mut source1 = Source::new(input1); /// let result1 = parse(eof.clone(), &mut source1); /// assert!(result1.is_ok()); /// /// // Fails on non-empty input /// let mut input2 = Cursor::new(b"a"); /// let mut source2 = Source::new(input2); /// let result2 = parse(eof, &mut source2); /// assert!(result2.is_err()); /// ``` pub fn new() -> Self { Self } } impl Default for EndOfFile { fn default() -> Self { Self::new() } } impl Parser for EndOfFile { type Output = (); fn read( &self, source: &mut Source, _cache: &mut impl ParsingCache, _context: &mut Ctx, ) -> ParseResult where S: Parsable, { // Try to peek at the next byte - if we get EOF, that's success match source.peek1() { Err(Error::NoMatch) => Ok(()), // End of file reached - success! Ok(_) => Err(Error::NoMatch), // Still have input - fail Err(other_error) => Err(other_error), // Propagate other I/O errors } } } #[cfg(test)] mod tests { use super::*; use crate::{literal::Literal, parser::parse, seq}; use std::io::Cursor; #[test] fn test_eof_on_empty_input() { let eof_parser = EndOfFile::new(); let mut input = Cursor::new(b""); let mut source = Source::new(&mut input); let result = parse(eof_parser, &mut source); assert!(result.is_ok()); } #[test] fn test_eof_fails_on_non_empty_input() { let eof_parser = EndOfFile::new(); let mut input = Cursor::new(b"a"); let mut source = Source::new(&mut input); let result = parse(eof_parser, &mut source); assert!(result.is_err()); assert!(matches!(result, Err(Error::NoMatch))); } #[test] fn test_eof_fails_on_whitespace() { let eof_parser = EndOfFile::new(); let mut input = Cursor::new(b" "); let mut source = Source::new(&mut input); let result = parse(eof_parser, &mut source); assert!(result.is_err()); assert!(matches!(result, Err(Error::NoMatch))); } #[test] fn test_eof_with_sequence_success() { let parser = seq![Literal::from_str("hello"), EndOfFile::new()]; let mut input = Cursor::new(b"hello"); let mut source = Source::new(&mut input); let result = parse(parser, &mut source); assert!(result.is_ok()); } #[test] fn test_eof_with_sequence_failure_trailing_content() { let parser = seq![Literal::from_str("hello"), EndOfFile::new()]; let mut input = Cursor::new(b"hello world"); let mut source = Source::new(&mut input); let result = parse(parser, &mut source); assert!(result.is_err()); assert!(matches!(result, Err(Error::NoMatch))); } #[test] fn test_eof_with_sequence_failure_incomplete() { let parser = seq![Literal::from_str("hello"), EndOfFile::new()]; let mut input = Cursor::new(b"hel"); let mut source = Source::new(&mut input); let result = parse(parser, &mut source); assert!(result.is_err()); } #[test] fn test_eof_id_consistency() { let eof1 = EndOfFile::new(); let eof2 = EndOfFile::new(); // Same parser type should have same ID assert_eq!( >::id(&eof1), >::id(&eof2) ); } #[test] fn test_eof_default() { let eof_default = EndOfFile; let eof_new = EndOfFile::new(); assert_eq!( >::id(&eof_default), >::id(&eof_new) ); } }