use std::{
convert::Infallible,
fmt,
fs::File,
io::{BufRead, BufReader, Read},
num::NonZero,
path::Path,
rc::Rc,
};
use proc_macro::Span;
use crate::cold;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[repr(u8)]
pub(crate) enum Magic {
P1 = b'1',
P2 = b'2',
P3 = b'3',
P4 = b'4',
P5 = b'5',
P6 = b'6',
P7 = b'7',
}
pub(crate) struct Reader {
path: Rc<Path>,
reader: BufReader<File>,
}
impl Reader {
pub(crate) fn open(path: &str) -> Reader {
let span = Span::call_site();
let mut source = span.local_file().unwrap_or_else(|| {
panic!(
"can't include file {path:?} from non-file {:?}",
span.file()
)
});
source.pop();
source.push(path);
let path = <Rc<Path>>::from(source);
let file = File::open(&path).unwrap_or_else(|e| cold::io_failure("open", &path, &e));
let reader = BufReader::new(file);
Reader { path, reader }
}
pub(crate) fn read_magic(&mut self) -> Magic {
let mut magic = [0; 2];
self.reader
.read_exact(&mut magic)
.unwrap_or_else(|e| cold::io_failure("read", &self.path, &e));
assert!(
magic[0] == b'P',
"{} wasn't a PNM file",
self.path.display()
);
match magic[1] {
b'1' => Magic::P1,
b'2' => Magic::P2,
b'3' => Magic::P3,
b'4' => Magic::P4,
b'5' => Magic::P5,
b'6' => Magic::P6,
b'7' => Magic::P7,
_ => panic!("{} wasn't a PNM file", self.path.display()),
}
}
pub(crate) fn read_token<T: IntoTokenizer>(
&mut self,
into_tokenizer: T,
) -> <<T as IntoTokenizer>::Output as Tokenizer>::Output {
let mut token = into_tokenizer.into_tokenizer();
let mut token_started = false;
let mut buf = [0u8];
loop {
self.reader
.read_exact(&mut buf)
.unwrap_or_else(|e| cold::io_failure("read", &self.path, &e));
let b = buf[0];
if b.is_ascii_whitespace() {
if token_started {
return token.finish();
}
} else if b == b'#' {
self.reader
.skip_until(b'\n')
.unwrap_or_else(|e| cold::io_failure("read", &self.path, &e));
if token_started {
return token.finish();
}
} else {
token_started = true;
if let Err(e) = token.receive(b) {
cold::io_failure("parse", &self.path, &e);
}
}
}
}
pub(crate) fn read_tokens<T: IntoTokenizer + Clone>(
&mut self,
into_tokenizer: T,
) -> Tokens<'_, T> {
Tokens {
reader: self,
into_tokenizer,
}
}
pub(crate) fn read_bytes(&mut self) -> impl Iterator<Item = u8> {
self.reader
.by_ref()
.bytes()
.map(|b| b.unwrap_or_else(|e| cold::io_failure("read", &self.path, &e)))
}
pub(crate) fn path(&self) -> Rc<Path> {
self.path.clone()
}
}
impl fmt::Display for Reader {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.path.display(), f)
}
}
impl fmt::Debug for Reader {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Reader")
.field("path", &self.path)
.finish_non_exhaustive()
}
}
#[derive(Debug)]
pub(crate) struct Tokens<'a, T> {
reader: &'a mut Reader,
into_tokenizer: T,
}
impl<T: IntoTokenizer + Clone> Iterator for Tokens<'_, T> {
type Item = <<T as IntoTokenizer>::Output as Tokenizer>::Output;
fn next(&mut self) -> Option<Self::Item> {
Some(self.reader.read_token(self.into_tokenizer.clone()))
}
}
pub(crate) trait IntoTokenizer {
type Output: Tokenizer;
fn into_tokenizer(self) -> Self::Output;
}
impl IntoTokenizer for u16 {
type Output = ParseNum;
fn into_tokenizer(self) -> Self::Output {
ParseNum::new(self)
}
}
pub(crate) trait Tokenizer {
type Output;
type Error: fmt::Display;
fn receive(&mut self, b: u8) -> Result<(), Self::Error>;
fn finish(self) -> Self::Output;
}
#[derive(Debug)]
pub(crate) struct ParseNum {
max_value: u16,
num: u16,
}
impl ParseNum {
pub(crate) fn new(max_value: u16) -> ParseNum {
ParseNum { max_value, num: 0 }
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub(crate) enum NumError {
NonDigit(u8),
Overflow(u16),
}
impl fmt::Display for NumError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NumError::NonDigit(b) => write!(f, "expected digit, found '{}'", b.escape_ascii()),
NumError::Overflow(max_value) => {
write!(f, "numeric value exceeded maximum of {}", max_value)
}
}
}
}
impl Tokenizer for ParseNum {
type Output = u16;
type Error = NumError;
fn receive(&mut self, b: u8) -> Result<(), NumError> {
if !b.is_ascii_digit() {
return Err(NumError::NonDigit(b));
}
self.num = self
.num
.checked_mul(10)
.and_then(|num| num.checked_add((b - b'0') as u16))
.ok_or(NumError::Overflow(self.max_value))?;
if self.num > self.max_value {
Err(NumError::Overflow(self.max_value))
} else {
Ok(())
}
}
fn finish(self) -> Self::Output {
self.num
}
}
#[test]
fn test_read_num_depth_one() {
let mut zero = 1u16.into_tokenizer();
zero.receive(b'0').unwrap();
assert_eq!(zero.finish(), 0);
let mut one = 1u16.into_tokenizer();
one.receive(b'0').unwrap();
one.receive(b'1').unwrap();
assert_eq!(one.finish(), 1);
let mut two = 1u16.into_tokenizer();
assert_eq!(two.receive(b'2'), Err(NumError::Overflow(1)));
let mut period = 1u16.into_tokenizer();
assert_eq!(period.receive(b'.'), Err(NumError::NonDigit(b'.')));
}
#[test]
fn test_read_num_depth_eight() {
let mut zero = 255u16.into_tokenizer();
zero.receive(b'0').unwrap();
assert_eq!(zero.finish(), 0);
let mut twenty_five = 255u16.into_tokenizer();
twenty_five.receive(b'2').unwrap();
twenty_five.receive(b'5').unwrap();
assert_eq!(twenty_five.finish(), 25);
let mut three_hundred = 255u16.into_tokenizer();
three_hundred.receive(b'3').unwrap();
three_hundred.receive(b'0').unwrap();
assert_eq!(three_hundred.receive(b'0'), Err(NumError::Overflow(255)));
let mut period = 255u16.into_tokenizer();
assert_eq!(period.receive(b'.'), Err(NumError::NonDigit(b'.')));
}
#[test]
fn test_read_num_depth_sixteen() {
let mut zero = 65535u16.into_tokenizer();
zero.receive(b'0').unwrap();
assert_eq!(zero.finish(), 0);
let mut sixty_five_thousand = 65535u16.into_tokenizer();
sixty_five_thousand.receive(b'6').unwrap();
sixty_five_thousand.receive(b'5').unwrap();
sixty_five_thousand.receive(b'0').unwrap();
sixty_five_thousand.receive(b'0').unwrap();
sixty_five_thousand.receive(b'0').unwrap();
assert_eq!(sixty_five_thousand.finish(), 65000);
let mut seventy_thousand = 65535u16.into_tokenizer();
seventy_thousand.receive(b'7').unwrap();
seventy_thousand.receive(b'0').unwrap();
seventy_thousand.receive(b'0').unwrap();
seventy_thousand.receive(b'0').unwrap();
assert_eq!(
seventy_thousand.receive(b'0'),
Err(NumError::Overflow(65535))
);
let mut period = 65535u16.into_tokenizer();
assert_eq!(period.receive(b'.'), Err(NumError::NonDigit(b'.')));
}
pub(crate) struct TuplType;
impl IntoTokenizer for TuplType {
type Output = TuplType;
fn into_tokenizer(self) -> Self::Output {
self
}
}
impl Tokenizer for TuplType {
type Output = ();
type Error = Infallible;
fn receive(&mut self, _: u8) -> Result<(), Self::Error> {
Ok(())
}
fn finish(self) {}
}
pub(crate) struct Header;
impl IntoTokenizer for Header {
type Output = ParseHeader;
fn into_tokenizer(self) -> Self::Output {
ParseHeader::new()
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub(crate) enum HeaderDirective {
EndHdr,
Height,
Width,
Depth,
MaxVal,
TuplType,
}
impl HeaderDirective {
fn as_str(self) -> &'static str {
match self {
HeaderDirective::EndHdr => "ENDHDR",
HeaderDirective::Height => "HEIGHT",
HeaderDirective::Width => "WIDTH",
HeaderDirective::Depth => "DEPTH",
HeaderDirective::MaxVal => "MAXVAL",
HeaderDirective::TuplType => "TUPLTYPE",
}
}
}
pub(crate) struct HeaderError {
prefix: &'static str,
b: u8,
}
impl fmt::Display for HeaderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"unknown PAM header directive starting with \"{}{}\"",
self.prefix,
self.b.escape_ascii()
)
}
}
pub(crate) struct ParseHeader {
head: HeaderDirective,
read: NonZero<usize>,
}
impl ParseHeader {
fn new() -> ParseHeader {
ParseHeader {
head: HeaderDirective::EndHdr,
read: const { NonZero::new(1).unwrap() },
}
}
}
impl Tokenizer for ParseHeader {
type Output = HeaderDirective;
type Error = HeaderError;
fn receive(&mut self, b: u8) -> Result<(), HeaderError> {
self.head = match (self.head, self.read.get(), b) {
(_, 1, b'E')
| (HeaderDirective::EndHdr, 2, b'N')
| (HeaderDirective::EndHdr, 3, b'D')
| (HeaderDirective::EndHdr, 4, b'H')
| (HeaderDirective::EndHdr, 5, b'D')
| (HeaderDirective::EndHdr, 6, b'R') => HeaderDirective::EndHdr,
(_, 1, b'H')
| (HeaderDirective::Height, 2, b'E')
| (HeaderDirective::Height, 3, b'I')
| (HeaderDirective::Height, 4, b'G')
| (HeaderDirective::Height, 5, b'H')
| (HeaderDirective::Height, 6, b'T') => HeaderDirective::Height,
(_, 1, b'W')
| (HeaderDirective::Width, 2, b'I')
| (HeaderDirective::Width, 3, b'D')
| (HeaderDirective::Width, 4, b'T')
| (HeaderDirective::Width, 5, b'H') => HeaderDirective::Width,
(_, 1, b'D')
| (HeaderDirective::Depth, 2, b'E')
| (HeaderDirective::Depth, 3, b'P')
| (HeaderDirective::Depth, 4, b'T')
| (HeaderDirective::Depth, 5, b'H') => HeaderDirective::Depth,
(_, 1, b'M')
| (HeaderDirective::MaxVal, 2, b'A')
| (HeaderDirective::MaxVal, 3, b'X')
| (HeaderDirective::MaxVal, 4, b'V')
| (HeaderDirective::MaxVal, 5, b'A')
| (HeaderDirective::MaxVal, 6, b'L') => HeaderDirective::MaxVal,
(_, 1, b'T')
| (HeaderDirective::TuplType, 2, b'U')
| (HeaderDirective::TuplType, 3, b'P')
| (HeaderDirective::TuplType, 4, b'L')
| (HeaderDirective::TuplType, 5, b'T')
| (HeaderDirective::TuplType, 6, b'Y')
| (HeaderDirective::TuplType, 7, b'P')
| (HeaderDirective::TuplType, 8, b'E') => HeaderDirective::TuplType,
(head, idx, b) => {
return Err(HeaderError {
prefix: &head.as_str()[..idx - 1],
b,
});
}
};
self.read = NonZero::new(self.read.get() + 1).unwrap();
Ok(())
}
fn finish(self) -> Self::Output {
self.head
}
}