1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//! Parser data source.
//!
//! FBX parsers requires cursor position tracking and skipping.
//! These features are used through `ParserSource` trait.
//!
//! For reader types which does not provide these features, `reader::*Source`
//! wrappers are provided.
//! Using those wrappers, any types implementing `std::io::Read` can be used as
//! source reader for parsers.
//!
//! Usually users don't need to deal with these types and traits, because parser
//! types or modules will provide simple functions to automatically wrap readers
//! if necessary.

use std::io;

pub use self::{
    position_cache::PositionCacheReader,
    source::{PlainSource, SeekableSource},
};

mod position_cache;
mod source;

/// A trait for types which can be data sources.
///
/// Users can implement this manually, but usually it is enough to use wrappers
/// in the [`reader`] module.
///
/// [`reader`]: index.html
pub trait ParserSource: Sized + io::Read {
    /// Returns the offset of a byte which would be read next.
    ///
    /// This is called many times during parsing, so it is desirable to be fast
    /// as possible.
    ///
    /// Reader types with `std::io::Seek` can implement this as
    /// `self.seek(SeekFrom::Current(0)).unwrap()`, but this is fallible and
    /// can be inefficient.
    /// Use of [`PositionCacheReader`] is reccomended.
    ///
    /// [`PositionCacheReader`]: struct.PositionCacheReader.html
    fn position(&self) -> u64;

    /// Skips (seeks formward) the given size.
    ///
    /// Reader types can make this more efficient using `io::Seek::seek` if
    /// possible.
    ///
    /// # Examples
    ///
    /// ```
    /// use fbxcel::pull_parser::{ParserSource, reader::PlainSource};
    ///
    /// let msg = "Hello, world!";
    /// let len = msg.len() as u64;
    /// let mut reader = std::io::Cursor::new(msg);
    /// let mut reader = PlainSource::new(&mut reader);
    ///
    /// assert_eq!(reader.position(), 0);
    /// reader.skip_distance(7).expect("Failed to skip");
    /// assert_eq!(reader.position(), 7);
    /// ```
    fn skip_distance(&mut self, distance: u64) -> io::Result<()> {
        // NOTE: `let mut limited = self.by_ref().take(distance);` is E0507.
        let mut limited = io::Read::take(self.by_ref(), distance);
        io::copy(&mut limited, &mut io::sink())?;
        Ok(())
    }

    /// Skips (seeks forward) to the given position.
    ///
    /// Reader types can make this more efficient using `io::Seek::seek` if
    /// possible.
    ///
    /// # Panics
    ///
    /// Panics if the given position is behind the current position.
    ///
    /// ```
    /// use fbxcel::pull_parser::{ParserSource, reader::PlainSource};
    ///
    /// let msg = "Hello, world!";
    /// let len = msg.len() as u64;
    /// let mut reader = std::io::Cursor::new(msg);
    /// let mut reader = PlainSource::new(&mut reader);
    ///
    /// assert_eq!(reader.position(), 0);
    /// reader.skip_to(2).expect("Failed to skip");
    /// assert_eq!(reader.position(), 2);
    /// reader.skip_to(7).expect("Failed to skip");
    /// assert_eq!(reader.position(), 7);
    /// ```
    fn skip_to(&mut self, pos: u64) -> io::Result<()> {
        let distance = pos
            .checked_sub(self.position())
            .expect("Attempt to skip backward");
        self.skip_distance(distance)
    }
}

impl<R: ParserSource> ParserSource for &mut R {
    fn position(&self) -> u64 {
        (**self).position()
    }

    fn skip_distance(&mut self, distance: u64) -> io::Result<()> {
        (**self).skip_distance(distance)
    }

    fn skip_to(&mut self, pos: u64) -> io::Result<()> {
        (**self).skip_to(pos)
    }
}