From 5df7c01cb5f50fdf62e320008ce15af0d6599c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 26 Feb 2024 12:00:31 +0200 Subject: [PATCH] closedcaption: Port from nom to winnow Part-of: --- Cargo.lock | 2 +- video/closedcaption/Cargo.toml | 2 +- video/closedcaption/src/mcc_parse/imp.rs | 5 +- video/closedcaption/src/mcc_parse/parser.rs | 807 +++++++++----------- video/closedcaption/src/parser_utils.rs | 162 ++-- video/closedcaption/src/scc_parse/imp.rs | 5 +- video/closedcaption/src/scc_parse/parser.rs | 351 ++++----- 7 files changed, 598 insertions(+), 736 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6aad70b9..d65a6003 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2255,7 +2255,6 @@ dependencies = [ "gstreamer-base", "gstreamer-check", "gstreamer-video", - "nom", "once_cell", "pango", "pangocairo", @@ -2264,6 +2263,7 @@ dependencies = [ "serde", "serde_json", "uuid", + "winnow 0.6.2", ] [[package]] diff --git a/video/closedcaption/Cargo.toml b/video/closedcaption/Cargo.toml index 285ebc82..14c484a7 100644 --- a/video/closedcaption/Cargo.toml +++ b/video/closedcaption/Cargo.toml @@ -10,7 +10,6 @@ repository.workspace = true [dependencies] anyhow = "1" -nom = "7.0" either = "1" uuid = { version = "1.0", features = ["v4"] } chrono = "0.4.23" @@ -26,6 +25,7 @@ once_cell.workspace = true gst = { workspace = true, features = ["v1_16"]} gst-base = { workspace = true, features = ["v1_16"]} gst-video = { workspace = true, features = ["v1_16"]} +winnow = "0.6" [dev-dependencies] pretty_assertions = "1" diff --git a/video/closedcaption/src/mcc_parse/imp.rs b/video/closedcaption/src/mcc_parse/imp.rs index 439929d2..0c8611cc 100644 --- a/video/closedcaption/src/mcc_parse/imp.rs +++ b/video/closedcaption/src/mcc_parse/imp.rs @@ -139,7 +139,10 @@ fn parse_timecode_rate( impl State { #[allow(clippy::type_complexity)] - fn line(&mut self, drain: bool) -> Result, (&[u8], nom::error::Error<&[u8]>)> { + fn line( + &mut self, + drain: bool, + ) -> Result, (&[u8], winnow::error::ContextError)> { let line = if self.replay_last_line { self.replay_last_line = false; &self.last_raw_line diff --git a/video/closedcaption/src/mcc_parse/parser.rs b/video/closedcaption/src/mcc_parse/parser.rs index 93acefd8..c6c76519 100644 --- a/video/closedcaption/src/mcc_parse/parser.rs +++ b/video/closedcaption/src/mcc_parse/parser.rs @@ -9,7 +9,7 @@ use either::Either; use crate::parser_utils::{digits, digits_range, end_of_line, timecode, TimeCode}; -use nom::IResult; +use winnow::{error::StrContext, PResult, Parser}; #[derive(Clone, Debug, PartialEq, Eq)] pub enum MccLine<'a> { @@ -37,110 +37,84 @@ pub struct MccParser { } /// Parser for the MCC header -fn header(s: &[u8]) -> IResult<&[u8], MccLine> { - use nom::branch::alt; - use nom::bytes::complete::tag; - use nom::combinator::{map, opt}; - use nom::error::context; - use nom::sequence::tuple; +fn header<'a>(s: &mut &'a [u8]) -> PResult> { + use winnow::combinator::{alt, opt}; + use winnow::token::literal; - context( - "invalid header", - map( - tuple(( - opt(tag(&[0xEFu8, 0xBBu8, 0xBFu8][..])), - tag("File Format=MacCaption_MCC V"), - alt((tag("1.0"), tag("2.0"))), - end_of_line, - )), - |_| MccLine::Header, - ), - )(s) + ( + opt(literal(&[0xEFu8, 0xBBu8, 0xBFu8][..])), + literal("File Format=MacCaption_MCC V"), + alt((literal("1.0"), literal("2.0"))), + end_of_line, + ) + .map(|_| MccLine::Header) + .context(StrContext::Label("invalid header")) + .parse_next(s) } /// Parser for an MCC comment, i.e. a line starting with `//`. We don't return the actual comment /// text as it's irrelevant for us. -fn comment(s: &[u8]) -> IResult<&[u8], MccLine> { - use nom::bytes::complete::tag; - use nom::combinator::{map, rest}; - use nom::error::context; - use nom::sequence::tuple; +fn comment<'a>(s: &mut &'a [u8]) -> PResult> { + use winnow::combinator::rest; + use winnow::token::literal; - context( - "invalid comment", - map(tuple((tag("//"), rest)), |_| MccLine::Comment), - )(s) + (literal("//"), rest) + .map(|_| MccLine::Comment) + .context(StrContext::Label("invalid comment")) + .parse_next(s) } /// Parser for the MCC UUID line. -fn uuid(s: &[u8]) -> IResult<&[u8], MccLine> { - use nom::bytes::complete::{tag, take_while1}; - use nom::combinator::map; - use nom::error::context; - use nom::sequence::tuple; +fn uuid<'a>(s: &mut &'a [u8]) -> PResult> { + use winnow::token::{literal, take_while}; - context( - "invalid uuid", - map( - tuple(( - tag("UUID="), - take_while1(|b| b != b'\n' && b != b'\r'), - end_of_line, - )), - |(_, uuid, _)| MccLine::Uuid(uuid), - ), - )(s) + ( + literal("UUID="), + take_while(1.., |b| b != b'\n' && b != b'\r'), + end_of_line, + ) + .map(|(_, uuid, _)| MccLine::Uuid(uuid)) + .context(StrContext::Label("invalid uuid")) + .parse_next(s) } /// Parser for the MCC Time Code Rate line. -fn time_code_rate(s: &[u8]) -> IResult<&[u8], MccLine> { - use nom::bytes::complete::tag; - use nom::combinator::{map, opt}; - use nom::error::context; - use nom::sequence::tuple; +fn time_code_rate<'a>(s: &mut &'a [u8]) -> PResult> { + use winnow::combinator::opt; + use winnow::token::literal; - context( - "invalid timecode rate", - map( - tuple(( - tag("Time Code Rate="), - digits_range(1..256), - opt(tag("DF")), - end_of_line, - )), - |(_, v, df, _)| MccLine::TimeCodeRate(v as u8, df.is_some()), - ), - )(s) + ( + literal("Time Code Rate="), + digits_range(1..256), + opt(literal("DF")), + end_of_line, + ) + .map(|(_, v, df, _)| MccLine::TimeCodeRate(v as u8, df.is_some())) + .context(StrContext::Label("invalid timecode rate")) + .parse_next(s) } /// Parser for generic MCC metadata lines in the form `key=value`. -fn metadata(s: &[u8]) -> IResult<&[u8], MccLine> { - use nom::bytes::complete::take_while1; - use nom::character::complete::char; - use nom::combinator::map; - use nom::error::context; - use nom::sequence::tuple; +fn metadata<'a>(s: &mut &'a [u8]) -> PResult> { + use winnow::token::{one_of, take_while}; - context( - "invalid metadata", - map( - tuple(( - take_while1(|b| b != b'='), - char('='), - take_while1(|b| b != b'\n' && b != b'\r'), - end_of_line, - )), - |(name, _, value, _)| MccLine::Metadata(name, value), - ), - )(s) + ( + take_while(1.., |b| b != b'='), + one_of('='), + take_while(1.., |b| b != b'\n' && b != b'\r'), + end_of_line, + ) + .map(|(name, _, value, _)| MccLine::Metadata(name, value)) + .context(StrContext::Label("invalid metadata")) + .parse_next(s) } /// Parser that accepts only an empty line -fn empty_line(s: &[u8]) -> IResult<&[u8], MccLine> { - use nom::combinator::map; - use nom::error::context; - - context("invalid empty line", map(end_of_line, |_| MccLine::Empty))(s) +fn empty_line<'a>(s: &mut &'a [u8]) -> PResult> { + end_of_line + .map(|_| MccLine::Empty) + .context(StrContext::Label("invalid empty line")) + .parse_next(s) } /// A single MCC payload item. This is ASCII hex encoded bytes plus some single-character @@ -148,152 +122,145 @@ fn empty_line(s: &[u8]) -> IResult<&[u8], MccLine> { /// /// It returns an `Either` of the single hex encoded byte or the short-cut byte sequence as a /// static byte slice. -fn mcc_payload_item(s: &[u8]) -> IResult<&[u8], Either> { - use nom::branch::alt; - use nom::bytes::complete::tag; - use nom::bytes::complete::take_while_m_n; - use nom::character::is_hex_digit; - use nom::combinator::map; - use nom::error::context; +fn mcc_payload_item(s: &mut &[u8]) -> PResult> { + use winnow::combinator::alt; + use winnow::stream::AsChar; + use winnow::token::{literal, take_while}; - context( - "invalid payload item", - alt(( - map(tag("G"), |_| Either::Right([0xfa, 0x00, 0x00].as_ref())), - map(tag("H"), |_| { - Either::Right([0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00].as_ref()) - }), - map(tag("I"), |_| { - Either::Right([0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00].as_ref()) - }), - map(tag("J"), |_| { - Either::Right( - [ - 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, - ] - .as_ref(), - ) - }), - map(tag("K"), |_| { - Either::Right( - [ - 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, - 0xfa, 0x00, 0x00, - ] - .as_ref(), - ) - }), - map(tag("L"), |_| { - Either::Right( - [ - 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, - 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, - ] - .as_ref(), - ) - }), - map(tag("M"), |_| { - Either::Right( - [ - 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, - 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, - ] - .as_ref(), - ) - }), - map(tag("N"), |_| { - Either::Right( - [ - 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, - 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, - ] - .as_ref(), - ) - }), - map(tag("O"), |_| { - Either::Right( - [ - 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, - 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, - 0xfa, 0x00, 0x00, - ] - .as_ref(), - ) - }), - map(tag("P"), |_| Either::Right([0xfb, 0x80, 0x80].as_ref())), - map(tag("Q"), |_| Either::Right([0xfc, 0x80, 0x80].as_ref())), - map(tag("R"), |_| Either::Right([0xfd, 0x80, 0x80].as_ref())), - map(tag("S"), |_| Either::Right([0x96, 0x69].as_ref())), - map(tag("T"), |_| Either::Right([0x61, 0x01].as_ref())), - map(tag("U"), |_| Either::Right([0xe1, 0x00, 0x00].as_ref())), - map(tag("Z"), |_| Either::Right([0x00].as_ref())), - map(take_while_m_n(2, 2, is_hex_digit), |s: &[u8]| { - let hex_to_u8 = |v: u8| match v { - v if v.is_ascii_digit() => v - b'0', - v if (b'A'..=b'F').contains(&v) => 10 + v - b'A', - v if (b'a'..=b'f').contains(&v) => 10 + v - b'a', - _ => unreachable!(), - }; - let val = (hex_to_u8(s[0]) << 4) | hex_to_u8(s[1]); - Either::Left(val) - }), - )), - )(s) + alt(( + literal("G").map(|_| Either::Right([0xfa, 0x00, 0x00].as_ref())), + literal("H").map(|_| Either::Right([0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00].as_ref())), + literal("I").map(|_| { + Either::Right([0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00].as_ref()) + }), + literal("J").map(|_| { + Either::Right( + [ + 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, + ] + .as_ref(), + ) + }), + literal("K").map(|_| { + Either::Right( + [ + 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, + 0x00, 0x00, + ] + .as_ref(), + ) + }), + literal("L").map(|_| { + Either::Right( + [ + 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, + 0x00, 0x00, 0xfa, 0x00, 0x00, + ] + .as_ref(), + ) + }), + literal("M").map(|_| { + Either::Right( + [ + 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, + 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, + ] + .as_ref(), + ) + }), + literal("N").map(|_| { + Either::Right( + [ + 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, + 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, + ] + .as_ref(), + ) + }), + literal("O").map(|_| { + Either::Right( + [ + 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, + 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, + 0x00, + ] + .as_ref(), + ) + }), + literal("P").map(|_| Either::Right([0xfb, 0x80, 0x80].as_ref())), + literal("Q").map(|_| Either::Right([0xfc, 0x80, 0x80].as_ref())), + literal("R").map(|_| Either::Right([0xfd, 0x80, 0x80].as_ref())), + literal("S").map(|_| Either::Right([0x96, 0x69].as_ref())), + literal("T").map(|_| Either::Right([0x61, 0x01].as_ref())), + literal("U").map(|_| Either::Right([0xe1, 0x00, 0x00].as_ref())), + literal("Z").map(|_| Either::Right([0x00].as_ref())), + take_while(2..=2, AsChar::is_hex_digit).map(|s: &[u8]| { + let hex_to_u8 = |v: u8| match v { + v if v.is_ascii_digit() => v - b'0', + v if (b'A'..=b'F').contains(&v) => 10 + v - b'A', + v if (b'a'..=b'f').contains(&v) => 10 + v - b'a', + _ => unreachable!(), + }; + let val = (hex_to_u8(s[0]) << 4) | hex_to_u8(s[1]); + Either::Left(val) + }), + )) + .context(StrContext::Label("invalid payload item")) + .parse_next(s) } /// Parser for the whole MCC payload with conversion to the underlying byte values. -fn mcc_payload(s: &[u8]) -> IResult<&[u8], Vec> { - use nom::error::context; - use nom::multi::fold_many1; +fn mcc_payload(s: &mut &[u8]) -> PResult> { + use winnow::combinator::repeat; - context( - "invalid MCC payload", - fold_many1(mcc_payload_item, Vec::new, |mut acc: Vec<_>, item| { + repeat(1.., mcc_payload_item) + .fold(Vec::new, |mut acc: Vec<_>, item| { match item { Either::Left(val) => acc.push(val), Either::Right(vals) => acc.extend_from_slice(vals), } acc - }), - )(s) + }) + .context(StrContext::Label("invalid MCC payload")) + .parse_next(s) } /// Parser for a MCC caption line in the form `timecode\tpayload`. -fn caption(parse_payload: bool) -> impl FnMut(&[u8]) -> IResult<&[u8], MccLine> { - use nom::bytes::complete::take_while; - use nom::character::complete::{char, one_of}; - use nom::combinator::{map, opt}; - use nom::error::context; - use nom::sequence::tuple; +fn caption<'a>( + parse_payload: bool, +) -> impl Parser<&'a [u8], MccLine<'a>, winnow::error::ContextError> { + use winnow::combinator::opt; + use winnow::token::{one_of, take_while}; - fn parse(parse_payload: bool) -> impl FnMut(&[u8]) -> IResult<&[u8], Option>> { - move |s| { + fn parse<'a>( + parse_payload: bool, + ) -> impl Parser<&'a [u8], Option>, winnow::error::ContextError> { + move |s: &mut &'a [u8]| { if parse_payload { - map(mcc_payload, Some)(s) + mcc_payload.map(Some).parse_next(s) } else { - map(take_while(|b| b != b'\n' && b != b'\r'), |_| None)(s) + take_while(0.., |b| b != b'\n' && b != b'\r') + .map(|_| None) + .parse_next(s) } } } - move |s: &[u8]| { - context( - "invalid MCC caption", - map( - tuple(( - timecode, - opt(tuple(( - char('.'), - one_of("01"), - opt(tuple((char(','), digits))), - ))), - char('\t'), - parse(parse_payload), - end_of_line, - )), - |(tc, _, _, value, _)| MccLine::Caption(tc, value), - ), - )(s) + move |s: &mut &'a [u8]| { + ( + timecode, + opt(( + one_of('.'), + one_of(|c| b"01".contains(&c)), + opt((one_of(','), digits)), + )), + one_of('\t'), + parse(parse_payload), + end_of_line, + ) + .map(|(tc, _, _, value, _)| MccLine::Caption(tc, value)) + .context(StrContext::Label("invalid MCC caption")) + .parse_next(s) } } @@ -317,60 +284,64 @@ impl MccParser { pub fn parse_line<'a>( &mut self, - line: &'a [u8], + mut line: &'a [u8], parse_payload: bool, - ) -> Result, nom::error::Error<&'a [u8]>> { - use nom::branch::alt; + ) -> Result, winnow::error::ContextError> { + use winnow::combinator::alt; match self.state { - State::Header => header(line) + State::Header => header(&mut line) .map(|v| { self.state = State::EmptyAfterHeader; - v.1 + v }) .map_err(|err| match err { - nom::Err::Incomplete(_) => unreachable!(), - nom::Err::Error(e) | nom::Err::Failure(e) => e, + winnow::error::ErrMode::Incomplete(_) => unreachable!(), + winnow::error::ErrMode::Backtrack(e) | winnow::error::ErrMode::Cut(e) => e, }), - State::EmptyAfterHeader => empty_line(line) + State::EmptyAfterHeader => empty_line(&mut line) .map(|v| { self.state = State::Comments; - v.1 + v }) .map_err(|err| match err { - nom::Err::Incomplete(_) => unreachable!(), - nom::Err::Error(e) | nom::Err::Failure(e) => e, + winnow::error::ErrMode::Incomplete(_) => unreachable!(), + winnow::error::ErrMode::Backtrack(e) | winnow::error::ErrMode::Cut(e) => e, }), - State::Comments => alt((empty_line, comment))(line) + State::Comments => alt((empty_line, comment)) + .parse_next(&mut line) .map(|v| { - if v.1 == MccLine::Empty { + if v == MccLine::Empty { self.state = State::Metadata; } - v.1 + v }) .map_err(|err| match err { - nom::Err::Incomplete(_) => unreachable!(), - nom::Err::Error(e) | nom::Err::Failure(e) => e, + winnow::error::ErrMode::Incomplete(_) => unreachable!(), + winnow::error::ErrMode::Backtrack(e) | winnow::error::ErrMode::Cut(e) => e, }), - State::Metadata => alt((empty_line, uuid, time_code_rate, metadata))(line) + State::Metadata => alt((empty_line, uuid, time_code_rate, metadata)) + .parse_next(&mut line) .map(|v| { - if v.1 == MccLine::Empty { + if v == MccLine::Empty { self.state = State::Captions; } - v.1 + v }) .map_err(|err| match err { - nom::Err::Incomplete(_) => unreachable!(), - nom::Err::Error(e) | nom::Err::Failure(e) => e, - }), - State::Captions => caption(parse_payload)(line) - .map(|v| v.1) - .map_err(|err| match err { - nom::Err::Incomplete(_) => unreachable!(), - nom::Err::Error(e) | nom::Err::Failure(e) => e, + winnow::error::ErrMode::Incomplete(_) => unreachable!(), + winnow::error::ErrMode::Backtrack(e) | winnow::error::ErrMode::Cut(e) => e, }), + State::Captions => { + caption(parse_payload) + .parse_next(&mut line) + .map_err(|err| match err { + winnow::error::ErrMode::Incomplete(_) => unreachable!(), + winnow::error::ErrMode::Backtrack(e) | winnow::error::ErrMode::Cut(e) => e, + }) + } } } } @@ -381,293 +352,221 @@ mod tests { #[test] fn test_header() { - assert_eq!( - header(b"File Format=MacCaption_MCC V1.0".as_ref()), - Ok((b"".as_ref(), MccLine::Header)) - ); + let mut input = b"File Format=MacCaption_MCC V1.0".as_slice(); + assert_eq!(header(&mut input), Ok(MccLine::Header)); + assert!(input.is_empty()); - assert_eq!( - header(b"File Format=MacCaption_MCC V1.0\n".as_ref()), - Ok((b"".as_ref(), MccLine::Header)) - ); + let mut input = b"File Format=MacCaption_MCC V1.0\n".as_slice(); + assert_eq!(header(&mut input), Ok(MccLine::Header)); + assert!(input.is_empty()); - assert_eq!( - header(b"File Format=MacCaption_MCC V1.0\r\n".as_ref()), - Ok((b"".as_ref(), MccLine::Header)) - ); + let mut input = b"File Format=MacCaption_MCC V1.0\r\n".as_slice(); + assert_eq!(header(&mut input), Ok(MccLine::Header)); + assert!(input.is_empty()); - assert_eq!( - header(b"File Format=MacCaption_MCC V2.0\r\n".as_ref()), - Ok((b"".as_ref(), MccLine::Header)) - ); + let mut input = b"File Format=MacCaption_MCC V2.0\r\n".as_slice(); + assert_eq!(header(&mut input), Ok(MccLine::Header)); + assert!(input.is_empty()); - assert_eq!( - header(b"File Format=MacCaption_MCC V1.1".as_ref()), - Err(nom::Err::Error(nom::error::Error::new( - b"1.1".as_ref(), - nom::error::ErrorKind::Tag - ))), - ); + let mut input = b"File Format=MacCaption_MCC V1.1".as_slice(); + assert!(header(&mut input).is_err()); } #[test] fn test_empty_line() { - assert_eq!(empty_line(b"".as_ref()), Ok((b"".as_ref(), MccLine::Empty))); + let mut input = b"".as_slice(); + assert_eq!(empty_line(&mut input), Ok(MccLine::Empty)); + assert!(input.is_empty()); - assert_eq!( - empty_line(b"\n".as_ref()), - Ok((b"".as_ref(), MccLine::Empty)) - ); + let mut input = b"\n".as_slice(); + assert_eq!(empty_line(&mut input), Ok(MccLine::Empty)); + assert!(input.is_empty()); - assert_eq!( - empty_line(b"\r\n".as_ref()), - Ok((b"".as_ref(), MccLine::Empty)) - ); + let mut input = b"\r\n".as_slice(); + assert_eq!(empty_line(&mut input), Ok(MccLine::Empty)); + assert!(input.is_empty()); - assert_eq!( - empty_line(b" \r\n".as_ref()), - Err(nom::Err::Error(nom::error::Error::new( - b" \r\n".as_ref(), - nom::error::ErrorKind::Eof - ))), - ); + let mut input = b" \r\n".as_slice(); + assert!(empty_line(&mut input).is_err()); } #[test] fn test_comment() { - assert_eq!( - comment(b"// blabla".as_ref()), - Ok((b"".as_ref(), MccLine::Comment)) - ); + let mut input = b"// blabla".as_slice(); + assert_eq!(comment(&mut input), Ok(MccLine::Comment)); + assert!(input.is_empty()); - assert_eq!( - comment(b"//\n".as_ref()), - Ok((b"".as_ref(), MccLine::Comment)) - ); + let mut input = b"//\n".as_slice(); + assert_eq!(comment(&mut input), Ok(MccLine::Comment)); + assert!(input.is_empty()); - assert_eq!( - comment(b"//".as_ref()), - Ok((b"".as_ref(), MccLine::Comment)) - ); + let mut input = b"//".as_slice(); + assert_eq!(comment(&mut input), Ok(MccLine::Comment)); + assert!(input.is_empty()); - assert_eq!( - comment(b" //".as_ref()), - Err(nom::Err::Error(nom::error::Error::new( - b" //".as_ref(), - nom::error::ErrorKind::Tag - ))), - ); + let mut input = b" //".as_slice(); + assert!(comment(&mut input).is_err()); } #[test] fn test_uuid() { - assert_eq!( - uuid(b"UUID=1234".as_ref()), - Ok((b"".as_ref(), MccLine::Uuid(b"1234".as_ref()))) - ); + let mut input = b"UUID=1234".as_slice(); + assert_eq!(uuid(&mut input), Ok(MccLine::Uuid(b"1234".as_ref()))); + assert!(input.is_empty()); - assert_eq!( - uuid(b"UUID=1234\n".as_ref()), - Ok((b"".as_ref(), MccLine::Uuid(b"1234".as_ref()))) - ); + let mut input = b"UUID=1234\n".as_slice(); + assert_eq!(uuid(&mut input), Ok(MccLine::Uuid(b"1234".as_ref()))); + assert!(input.is_empty()); - assert_eq!( - uuid(b"UUID=1234\r\n".as_ref()), - Ok((b"".as_ref(), MccLine::Uuid(b"1234".as_ref()))) - ); + let mut input = b"UUID=1234\r\n".as_slice(); + assert_eq!(uuid(&mut input), Ok(MccLine::Uuid(b"1234".as_ref()))); + assert!(input.is_empty()); - assert_eq!( - uuid(b"UUID=".as_ref()), - Err(nom::Err::Error(nom::error::Error::new( - b"".as_ref(), - nom::error::ErrorKind::TakeWhile1 - ))), - ); + let mut input = b"UUID=".as_slice(); + assert!(uuid(&mut input).is_err()); - assert_eq!( - uuid(b"uUID=1234".as_ref()), - Err(nom::Err::Error(nom::error::Error::new( - b"uUID=1234".as_ref(), - nom::error::ErrorKind::Tag, - ))), - ); + let mut input = b"uUID=1234".as_slice(); + assert!(uuid(&mut input).is_err()); } #[test] fn test_time_code_rate() { + let mut input = b"Time Code Rate=30".as_slice(); assert_eq!( - time_code_rate(b"Time Code Rate=30".as_ref()), - Ok((b"".as_ref(), MccLine::TimeCodeRate(30, false))) + time_code_rate(&mut input), + Ok(MccLine::TimeCodeRate(30, false)) ); + assert!(input.is_empty()); + let mut input = b"Time Code Rate=30DF".as_slice(); assert_eq!( - time_code_rate(b"Time Code Rate=30DF".as_ref()), - Ok((b"".as_ref(), MccLine::TimeCodeRate(30, true))) + time_code_rate(&mut input), + Ok(MccLine::TimeCodeRate(30, true)) ); + assert!(input.is_empty()); + let mut input = b"Time Code Rate=60".as_slice(); assert_eq!( - time_code_rate(b"Time Code Rate=60".as_ref()), - Ok((b"".as_ref(), MccLine::TimeCodeRate(60, false))) + time_code_rate(&mut input), + Ok(MccLine::TimeCodeRate(60, false)) ); + assert!(input.is_empty()); - assert_eq!( - time_code_rate(b"Time Code Rate=17F".as_ref()), - Err(nom::Err::Error(nom::error::Error::new( - b"F".as_ref(), - nom::error::ErrorKind::Eof - ))), - ); + let mut input = b"Time Code Rate=17F".as_slice(); + assert!(time_code_rate(&mut input).is_err(),); - assert_eq!( - time_code_rate(b"Time Code Rate=256".as_ref()), - Err(nom::Err::Error(nom::error::Error::new( - b"256".as_ref(), - nom::error::ErrorKind::Verify - ))), - ); + let mut input = b"Time Code Rate=256".as_slice(); + assert!(time_code_rate(&mut input).is_err(),); } #[test] fn test_metadata() { + let mut input = b"Creation Date=Thursday, June 04, 2015".as_slice(); assert_eq!( - metadata(b"Creation Date=Thursday, June 04, 2015".as_ref()), - Ok(( - b"".as_ref(), - MccLine::Metadata( - b"Creation Date".as_ref(), - b"Thursday, June 04, 2015".as_ref() - ), + metadata(&mut input), + Ok(MccLine::Metadata( + b"Creation Date".as_ref(), + b"Thursday, June 04, 2015".as_ref() )) ); + assert!(input.is_empty()); + let mut input = b"Creation Date= ".as_slice(); assert_eq!( - metadata(b"Creation Date= ".as_ref()), - Ok(( - b"".as_ref(), - MccLine::Metadata(b"Creation Date".as_ref(), b" ".as_ref()), - )) + metadata(&mut input), + Ok(MccLine::Metadata(b"Creation Date".as_ref(), b" ".as_ref()),) ); + assert!(input.is_empty()); - assert_eq!( - metadata(b"Creation Date".as_ref()), - Err(nom::Err::Error(nom::error::Error::new( - b"".as_ref(), - nom::error::ErrorKind::Char - ))), - ); + let mut input = b"Creation Date".as_slice(); + assert!(metadata(&mut input).is_err()); - assert_eq!( - metadata(b"Creation Date\n".as_ref()), - Err(nom::Err::Error(nom::error::Error::new( - b"".as_ref(), - nom::error::ErrorKind::Char, - ))), - ); + let mut input = b"Creation Date\n".as_slice(); + assert!(metadata(&mut input).is_err()); - assert_eq!( - metadata(b"Creation Date=".as_ref()), - Err(nom::Err::Error(nom::error::Error::new( - b"".as_ref(), - nom::error::ErrorKind::TakeWhile1 - ))), - ); + let mut input = b"Creation Date=".as_slice(); + assert!(metadata(&mut input).is_err()); - assert_eq!( - metadata(b"Creation Date=\n".as_ref()), - Err(nom::Err::Error(nom::error::Error::new( - b"\n".as_ref(), - nom::error::ErrorKind::TakeWhile1 - ))), - ); + let mut input = b"Creation Date=\n".as_slice(); + assert!(metadata(&mut input).is_err()); } #[test] fn test_caption() { + let mut input = b"00:00:00:00\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_slice(); assert_eq!( - caption(true)(b"00:00:00:00\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_ref()), - Ok(( - b"".as_ref(), - MccLine::Caption( - TimeCode { - hours: 0, - minutes: 0, - seconds: 0, - frames: 0, - drop_frame: false - }, - Some(vec![ - 0x61, 0x01, 0x52, 0x96, 0x69, 0x52, 0x4F, 0x67, 0x00, 0x00, 0x72, 0xF4, - 0xFC, 0x80, 0x80, 0xFD, 0x80, 0x80, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, - 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, - 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, - 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, - 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, - 0x73, 0x91, 0xE1, 0x00, 0x00, 0xC1, 0x3F, 0xFF, 0x74, 0x00, 0x00, 0xAE, - 0xB4 - ]) - ), + caption(true).parse_next(&mut input), + Ok(MccLine::Caption( + TimeCode { + hours: 0, + minutes: 0, + seconds: 0, + frames: 0, + drop_frame: false + }, + Some(vec![ + 0x61, 0x01, 0x52, 0x96, 0x69, 0x52, 0x4F, 0x67, 0x00, 0x00, 0x72, 0xF4, 0xFC, + 0x80, 0x80, 0xFD, 0x80, 0x80, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, + 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, + 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, + 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, + 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0x73, 0x91, 0xE1, 0x00, 0x00, 0xC1, + 0x3F, 0xFF, 0x74, 0x00, 0x00, 0xAE, 0xB4 + ]) )) ); + assert!(input.is_empty()); + let mut input = b"00:00:00:00.0\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_slice(); assert_eq!( - caption(true)(b"00:00:00:00.0\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_ref()), - Ok(( - b"".as_ref(), - MccLine::Caption( - TimeCode { - hours: 0, - minutes: 0, - seconds: 0, - frames: 0, - drop_frame: false - }, - Some(vec![ - 0x61, 0x01, 0x52, 0x96, 0x69, 0x52, 0x4F, 0x67, 0x00, 0x00, 0x72, 0xF4, - 0xFC, 0x80, 0x80, 0xFD, 0x80, 0x80, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, - 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, - 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, - 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, - 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, - 0x73, 0x91, 0xE1, 0x00, 0x00, 0xC1, 0x3F, 0xFF, 0x74, 0x00, 0x00, 0xAE, - 0xB4 - ]) - ), + caption(true).parse_next(&mut input), + Ok(MccLine::Caption( + TimeCode { + hours: 0, + minutes: 0, + seconds: 0, + frames: 0, + drop_frame: false + }, + Some(vec![ + 0x61, 0x01, 0x52, 0x96, 0x69, 0x52, 0x4F, 0x67, 0x00, 0x00, 0x72, 0xF4, 0xFC, + 0x80, 0x80, 0xFD, 0x80, 0x80, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, + 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, + 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, + 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, + 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0x73, 0x91, 0xE1, 0x00, 0x00, 0xC1, + 0x3F, 0xFF, 0x74, 0x00, 0x00, 0xAE, 0xB4 + ]) )) ); + assert!(input.is_empty()); + let mut input = b"00:00:00:00.0,9\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_slice(); assert_eq!( - caption(true)(b"00:00:00:00.0,9\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_ref()), - Ok(( - b"".as_ref(), - MccLine::Caption( - TimeCode { - hours: 0, - minutes: 0, - seconds: 0, - frames: 0, - drop_frame: false - }, - Some(vec![ - 0x61, 0x01, 0x52, 0x96, 0x69, 0x52, 0x4F, 0x67, 0x00, 0x00, 0x72, 0xF4, - 0xFC, 0x80, 0x80, 0xFD, 0x80, 0x80, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, - 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, - 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, - 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, - 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, - 0x73, 0x91, 0xE1, 0x00, 0x00, 0xC1, 0x3F, 0xFF, 0x74, 0x00, 0x00, 0xAE, - 0xB4 - ]) - ), + caption(true).parse_next(&mut input), + Ok(MccLine::Caption( + TimeCode { + hours: 0, + minutes: 0, + seconds: 0, + frames: 0, + drop_frame: false + }, + Some(vec![ + 0x61, 0x01, 0x52, 0x96, 0x69, 0x52, 0x4F, 0x67, 0x00, 0x00, 0x72, 0xF4, 0xFC, + 0x80, 0x80, 0xFD, 0x80, 0x80, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, + 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, + 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, + 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, + 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0x73, 0x91, 0xE1, 0x00, 0x00, 0xC1, + 0x3F, 0xFF, 0x74, 0x00, 0x00, 0xAE, 0xB4 + ]) )) ); + assert!(input.is_empty()); - assert_eq!( - caption(true)(b"Creation Date=\n".as_ref()), - Err(nom::Err::Error(nom::error::Error::new( - b"Creation Date=\n".as_ref(), - nom::error::ErrorKind::MapRes - ))), - ); + let mut input = b"Creation Date=\n".as_slice(); + assert!(caption(true).parse_next(&mut input).is_err()); } #[test] diff --git a/video/closedcaption/src/parser_utils.rs b/video/closedcaption/src/parser_utils.rs index 26a2853b..635c1e21 100644 --- a/video/closedcaption/src/parser_utils.rs +++ b/video/closedcaption/src/parser_utils.rs @@ -6,7 +6,7 @@ // // SPDX-License-Identifier: MPL-2.0 -use nom::IResult; +use winnow::{error::StrContext, PResult, Parser}; #[derive(Clone, Debug, PartialEq, Eq)] pub struct TimeCode { @@ -18,66 +18,62 @@ pub struct TimeCode { } /// Parser for parsing a run of ASCII, decimal digits and converting them into a `u32` -pub fn digits(s: &[u8]) -> IResult<&[u8], u32> { - use nom::bytes::complete::take_while; - use nom::character::is_digit; - use nom::combinator::map_res; +pub fn digits(s: &mut &[u8]) -> PResult { + use winnow::stream::AsChar; + use winnow::token::take_while; - map_res( - map_res(take_while(is_digit), std::str::from_utf8), - |s: &str| s.parse::(), - )(s) + take_while(0.., AsChar::is_dec_digit) + .try_map(std::str::from_utf8) + .try_map(|s: &str| s.parse::()) + .parse_next(s) } /// Parser for a run of decimal digits, that converts them into a `u32` and checks if the result is /// in the allowed range. -pub fn digits_range>( +pub fn digits_range<'a, R: std::ops::RangeBounds>( range: R, -) -> impl FnMut(&[u8]) -> IResult<&[u8], u32> { - use nom::combinator::verify; - use nom::error::context; - - move |s: &[u8]| context("digits out of range", verify(digits, |v| range.contains(v)))(s) +) -> impl Parser<&'a [u8], u32, winnow::error::ContextError> { + move |s: &mut &'a [u8]| { + digits + .verify(|v| range.contains(v)) + .context(StrContext::Label("digits out of range")) + .parse_next(s) + } } /// Parser for a timecode in the form `hh:mm:ss:fs` -pub fn timecode(s: &[u8]) -> IResult<&[u8], TimeCode> { - use nom::character::complete::{char, one_of}; - use nom::combinator::map; - use nom::error::context; - use nom::sequence::tuple; +pub fn timecode(s: &mut &[u8]) -> PResult { + use winnow::token::one_of; - context( - "invalid timecode", - map( - tuple(( - digits, - char(':'), - digits_range(0..60), - char(':'), - digits_range(0..60), - one_of(":.;,"), - digits, - )), - |(hours, _, minutes, _, seconds, sep, frames)| TimeCode { - hours, - minutes, - seconds, - frames, - drop_frame: sep == ';' || sep == ',', - }, - ), - )(s) + ( + digits, + one_of(':'), + digits_range(0..60), + one_of(':'), + digits_range(0..60), + one_of(|c| b":.;,".contains(&c)), + digits, + ) + .map(|(hours, _, minutes, _, seconds, sep, frames)| TimeCode { + hours, + minutes, + seconds, + frames, + drop_frame: sep == b';' || sep == b',', + }) + .context(StrContext::Label("invalid timecode")) + .parse_next(s) } /// Parser that checks for EOF and optionally `\n` or `\r\n` before EOF -pub fn end_of_line(s: &[u8]) -> IResult<&[u8], ()> { - use nom::branch::alt; - use nom::bytes::complete::tag; - use nom::combinator::{eof, map, opt}; - use nom::sequence::pair; +pub fn end_of_line(s: &mut &[u8]) -> PResult<()> { + use winnow::combinator::alt; + use winnow::combinator::{eof, opt}; + use winnow::token::literal; - map(pair(opt(alt((tag("\r\n"), tag("\n")))), eof), |_| ())(s) + (opt(alt((literal("\r\n"), literal("\n")))), eof) + .map(|_| ()) + .parse_next(s) } #[cfg(test)] @@ -86,54 +82,46 @@ mod tests { #[test] fn test_timecode() { + let mut input = b"11:12:13;14".as_slice(); assert_eq!( - timecode(b"11:12:13;14".as_ref()), - Ok(( - b"".as_ref(), - TimeCode { - hours: 11, - minutes: 12, - seconds: 13, - frames: 14, - drop_frame: true - }, - )) + timecode(&mut input), + Ok(TimeCode { + hours: 11, + minutes: 12, + seconds: 13, + frames: 14, + drop_frame: true + }) ); + assert!(input.is_empty()); + let mut input = b"11:12:13:14".as_slice(); assert_eq!( - timecode(b"11:12:13:14".as_ref()), - Ok(( - b"".as_ref(), - TimeCode { - hours: 11, - minutes: 12, - seconds: 13, - frames: 14, - drop_frame: false - }, - )) + timecode(&mut input), + Ok(TimeCode { + hours: 11, + minutes: 12, + seconds: 13, + frames: 14, + drop_frame: false + }) ); + assert!(input.is_empty()); + let mut input = b"11:12:13:14abcd".as_slice(); assert_eq!( - timecode(b"11:12:13:14abcd".as_ref()), - Ok(( - b"abcd".as_ref(), - TimeCode { - hours: 11, - minutes: 12, - seconds: 13, - frames: 14, - drop_frame: false - }, - )) + timecode(&mut input), + Ok(TimeCode { + hours: 11, + minutes: 12, + seconds: 13, + frames: 14, + drop_frame: false + }) ); + assert_eq!(input, b"abcd"); - assert_eq!( - timecode(b"abcd11:12:13:14".as_ref()), - Err(nom::Err::Error(nom::error::Error::new( - b"abcd11:12:13:14".as_ref(), - nom::error::ErrorKind::MapRes - ))), - ); + let mut input = b"abcd11:12:13:14".as_slice(); + assert!(timecode(&mut input).is_err()); } } diff --git a/video/closedcaption/src/scc_parse/imp.rs b/video/closedcaption/src/scc_parse/imp.rs index e184f139..ae0e9945 100644 --- a/video/closedcaption/src/scc_parse/imp.rs +++ b/video/closedcaption/src/scc_parse/imp.rs @@ -123,7 +123,10 @@ fn parse_timecode( impl State { #[allow(clippy::type_complexity)] - fn line(&mut self, drain: bool) -> Result, (&[u8], nom::error::Error<&[u8]>)> { + fn line( + &mut self, + drain: bool, + ) -> Result, (&[u8], winnow::error::ContextError)> { let line = match self.reader.line_with_drain(drain) { None => { return Ok(None); diff --git a/video/closedcaption/src/scc_parse/parser.rs b/video/closedcaption/src/scc_parse/parser.rs index 24515820..cdf45e7e 100644 --- a/video/closedcaption/src/scc_parse/parser.rs +++ b/video/closedcaption/src/scc_parse/parser.rs @@ -8,7 +8,7 @@ // SPDX-License-Identifier: MPL-2.0 use crate::parser_utils::{end_of_line, timecode, TimeCode}; -use nom::IResult; +use winnow::{error::StrContext, PResult, Parser}; #[derive(Clone, Debug, PartialEq, Eq)] pub enum SccLine { @@ -30,44 +30,36 @@ pub struct SccParser { } /// Parser for the SCC header -fn header(s: &[u8]) -> IResult<&[u8], SccLine> { - use nom::bytes::complete::tag; - use nom::combinator::{map, opt}; - use nom::error::context; - use nom::sequence::tuple; +fn header(s: &mut &[u8]) -> PResult { + use winnow::combinator::opt; + use winnow::token::literal; - context( - "invalid header", - map( - tuple(( - opt(tag(&[0xEFu8, 0xBBu8, 0xBFu8][..])), - tag("Scenarist_SCC V1.0"), - end_of_line, - )), - |_| SccLine::Header, - ), - )(s) + ( + opt(literal(&[0xEFu8, 0xBBu8, 0xBFu8][..])), + literal("Scenarist_SCC V1.0"), + end_of_line, + ) + .map(|_| SccLine::Header) + .context(StrContext::Label("invalid header")) + .parse_next(s) } /// Parser that accepts only an empty line -fn empty_line(s: &[u8]) -> IResult<&[u8], SccLine> { - use nom::combinator::map; - use nom::error::context; - - context("invalid empty line", map(end_of_line, |_| SccLine::Empty))(s) +fn empty_line(s: &mut &[u8]) -> PResult { + end_of_line + .map(|_| SccLine::Empty) + .context(StrContext::Label("invalid empty line")) + .parse_next(s) } /// A single SCC payload item. This is ASCII hex encoded bytes. /// It returns an tuple of `(u8, u8)` of the hex encoded bytes. -fn scc_payload_item(s: &[u8]) -> IResult<&[u8], (u8, u8)> { - use nom::bytes::complete::take_while_m_n; - use nom::character::is_hex_digit; - use nom::combinator::map; - use nom::error::context; +fn scc_payload_item(s: &mut &[u8]) -> PResult<(u8, u8)> { + use winnow::stream::AsChar; + use winnow::token::take_while; - context( - "invalid SCC payload item", - map(take_while_m_n(4, 4, is_hex_digit), |s: &[u8]| { + take_while(4..=4, AsChar::is_hex_digit) + .map(|s: &[u8]| { let hex_to_u8 = |v: u8| match v { v if v.is_ascii_digit() => v - b'0', v if (b'A'..=b'F').contains(&v) => 10 + v - b'A', @@ -79,50 +71,47 @@ fn scc_payload_item(s: &[u8]) -> IResult<&[u8], (u8, u8)> { let val2 = (hex_to_u8(s[2]) << 4) | hex_to_u8(s[3]); (val1, val2) - }), - )(s) + }) + .context(StrContext::Label("invalid SCC payload item")) + .parse_next(s) } /// Parser for the whole SCC payload with conversion to the underlying byte values. -fn scc_payload(s: &[u8]) -> IResult<&[u8], Vec> { - use nom::branch::alt; - use nom::bytes::complete::tag; - use nom::combinator::map; - use nom::error::context; - use nom::multi::fold_many1; - use nom::sequence::pair; - use nom::Parser; +fn scc_payload(s: &mut &[u8]) -> PResult> { + use winnow::combinator::{alt, repeat}; + use winnow::token::literal; - let parse_item = map( - pair(scc_payload_item, alt((tag(" ").map(|_| ()), end_of_line))), - |(item, _)| item, - ); + let parse_item = ( + scc_payload_item, + alt((literal(" ").map(|_| ()), end_of_line)), + ) + .map(|(item, _)| item); - context( - "invalid SCC payload", - fold_many1(parse_item, Vec::new, |mut acc: Vec<_>, item| { + repeat(1.., parse_item) + .fold(Vec::new, |mut acc: Vec<_>, item| { acc.push(item.0); acc.push(item.1); acc - }), - )(s) + }) + .context(StrContext::Label("invalid SCC payload")) + .parse_next(s) } /// Parser for a SCC caption line in the form `timecode\tpayload`. -fn caption(s: &[u8]) -> IResult<&[u8], SccLine> { - use nom::bytes::complete::tag; - use nom::character::complete::multispace0; - use nom::combinator::map; - use nom::error::context; - use nom::sequence::tuple; +fn caption(s: &mut &[u8]) -> PResult { + use winnow::ascii::multispace0; + use winnow::token::literal; - context( - "invalid SCC caption line", - map( - tuple((timecode, tag("\t"), multispace0, scc_payload, end_of_line)), - |(tc, _, _, value, _)| SccLine::Caption(tc, value), - ), - )(s) + ( + timecode, + literal("\t"), + multispace0, + scc_payload, + end_of_line, + ) + .map(|(tc, _, _, value, _)| SccLine::Caption(tc, value)) + .context(StrContext::Label("invalid SCC caption line")) + .parse_next(s) } /// SCC parser the parses line-by-line and keeps track of the current state in the file. @@ -143,37 +132,36 @@ impl SccParser { self.state = State::Header; } - pub fn parse_line<'a>( - &mut self, - line: &'a [u8], - ) -> Result> { - use nom::branch::alt; + pub fn parse_line(&mut self, mut line: &[u8]) -> Result { + use winnow::combinator::alt; match self.state { - State::Header => header(line) + State::Header => header(&mut line) .map(|v| { self.state = State::Empty; - v.1 + v }) .map_err(|err| match err { - nom::Err::Incomplete(_) => unreachable!(), - nom::Err::Error(e) | nom::Err::Failure(e) => e, + winnow::error::ErrMode::Incomplete(_) => unreachable!(), + winnow::error::ErrMode::Backtrack(e) | winnow::error::ErrMode::Cut(e) => e, }), - State::Empty => empty_line(line) + State::Empty => empty_line(&mut line) .map(|v| { self.state = State::CaptionOrEmpty; - v.1 + v }) .map_err(|err| match err { - nom::Err::Incomplete(_) => unreachable!(), - nom::Err::Error(e) | nom::Err::Failure(e) => e, - }), - State::CaptionOrEmpty => alt((caption, empty_line))(line) - .map(|v| v.1) - .map_err(|err| match err { - nom::Err::Incomplete(_) => unreachable!(), - nom::Err::Error(e) | nom::Err::Failure(e) => e, + winnow::error::ErrMode::Incomplete(_) => unreachable!(), + winnow::error::ErrMode::Backtrack(e) | winnow::error::ErrMode::Cut(e) => e, }), + State::CaptionOrEmpty => { + alt((caption, empty_line)) + .parse_next(&mut line) + .map_err(|err| match err { + winnow::error::ErrMode::Incomplete(_) => unreachable!(), + winnow::error::ErrMode::Backtrack(e) | winnow::error::ErrMode::Cut(e) => e, + }) + } } } } @@ -184,149 +172,130 @@ mod tests { #[test] fn test_header() { - assert_eq!( - header(b"Scenarist_SCC V1.0".as_ref()), - Ok((b"".as_ref(), SccLine::Header,)) - ); + let mut input = b"Scenarist_SCC V1.0".as_slice(); + assert_eq!(header(&mut input), Ok(SccLine::Header)); + assert!(input.is_empty()); - assert_eq!( - header(b"Scenarist_SCC V1.0\n".as_ref()), - Ok((b"".as_ref(), SccLine::Header)) - ); + let mut input = b"Scenarist_SCC V1.0\n".as_slice(); + assert_eq!(header(&mut input), Ok(SccLine::Header)); + assert!(input.is_empty()); - assert_eq!( - header(b"Scenarist_SCC V1.0\r\n".as_ref()), - Ok((b"".as_ref(), SccLine::Header)) - ); + let mut input = b"Scenarist_SCC V1.0\r\n".as_slice(); + assert_eq!(header(&mut input), Ok(SccLine::Header)); + assert!(input.is_empty()); - assert_eq!( - header(b"Scenarist_SCC V1.1".as_ref()), - Err(nom::Err::Error(nom::error::Error::new( - b"Scenarist_SCC V1.1".as_ref(), - nom::error::ErrorKind::Tag - ))), - ); + let mut input = b"Scenarist_SCC V1.1".as_slice(); + assert!(header(&mut input).is_err()); } #[test] fn test_empty_line() { - assert_eq!(empty_line(b"".as_ref()), Ok((b"".as_ref(), SccLine::Empty))); + let mut input = b"".as_slice(); + assert_eq!(empty_line(&mut input), Ok(SccLine::Empty)); + assert!(input.is_empty()); - assert_eq!( - empty_line(b"\n".as_ref()), - Ok((b"".as_ref(), SccLine::Empty)) - ); + let mut input = b"\n".as_slice(); + assert_eq!(empty_line(&mut input), Ok(SccLine::Empty)); + assert!(input.is_empty()); - assert_eq!( - empty_line(b"\r\n".as_ref()), - Ok((b"".as_ref(), SccLine::Empty)) - ); + let mut input = b"\r\n".as_slice(); + assert_eq!(empty_line(&mut input), Ok(SccLine::Empty)); + assert!(input.is_empty()); - assert_eq!( - empty_line(b" \r\n".as_ref()), - Err(nom::Err::Error(nom::error::Error::new( - b" \r\n".as_ref(), - nom::error::ErrorKind::Eof - ))), - ); + let mut input = b" \r\n".as_slice(); + assert!(empty_line(&mut input).is_err()); } #[test] fn test_caption() { + let mut input = b"01:02:53:14\t94ae 94ae 9420 9420 947a 947a 97a2 97a2 a820 68ef f26e 2068 ef6e 6be9 6e67 2029 942c 942c 8080 8080 942f 942f".as_slice(); assert_eq!( - caption(b"01:02:53:14\t94ae 94ae 9420 9420 947a 947a 97a2 97a2 a820 68ef f26e 2068 ef6e 6be9 6e67 2029 942c 942c 8080 8080 942f 942f".as_ref()), - Ok(( - b"".as_ref(), - SccLine::Caption( - TimeCode { - hours: 1, - minutes: 2, - seconds: 53, - frames: 14, - drop_frame: false - }, - - vec![ - 0x94, 0xae, 0x94, 0xae, 0x94, 0x20, 0x94, 0x20, 0x94, 0x7a, 0x94, 0x7a, 0x97, 0xa2, - 0x97, 0xa2, 0xa8, 0x20, 0x68, 0xef, 0xf2, 0x6e, 0x20, 0x68, 0xef, 0x6e, 0x6b, 0xe9, - 0x6e, 0x67, 0x20, 0x29, 0x94, 0x2c, 0x94, 0x2c, 0x80, 0x80, 0x80, 0x80, 0x94, 0x2f, - 0x94, 0x2f, - ] - ), + caption(&mut input), + Ok(SccLine::Caption( + TimeCode { + hours: 1, + minutes: 2, + seconds: 53, + frames: 14, + drop_frame: false + }, + vec![ + 0x94, 0xae, 0x94, 0xae, 0x94, 0x20, 0x94, 0x20, 0x94, 0x7a, 0x94, 0x7a, 0x97, + 0xa2, 0x97, 0xa2, 0xa8, 0x20, 0x68, 0xef, 0xf2, 0x6e, 0x20, 0x68, 0xef, 0x6e, + 0x6b, 0xe9, 0x6e, 0x67, 0x20, 0x29, 0x94, 0x2c, 0x94, 0x2c, 0x80, 0x80, 0x80, + 0x80, 0x94, 0x2f, 0x94, 0x2f, + ] )) ); + assert!(input.is_empty()); + let mut input = b"01:02:55;14 942c 942c".as_slice(); assert_eq!( - caption(b"01:02:55;14 942c 942c".as_ref()), - Ok(( - b"".as_ref(), - SccLine::Caption( - TimeCode { - hours: 1, - minutes: 2, - seconds: 55, - frames: 14, - drop_frame: true - }, - vec![0x94, 0x2c, 0x94, 0x2c] - ), + caption(&mut input), + Ok(SccLine::Caption( + TimeCode { + hours: 1, + minutes: 2, + seconds: 55, + frames: 14, + drop_frame: true + }, + vec![0x94, 0x2c, 0x94, 0x2c] )) ); + assert!(input.is_empty()); + let mut input = b"01:03:27:29 94ae 94ae 9420 9420 94f2 94f2 c845 d92c 2054 c845 5245 ae80 942c 942c 8080 8080 942f 942f".as_slice(); assert_eq!( - caption(b"01:03:27:29 94ae 94ae 9420 9420 94f2 94f2 c845 d92c 2054 c845 5245 ae80 942c 942c 8080 8080 942f 942f".as_ref()), - Ok(( - b"".as_ref(), - SccLine::Caption( - TimeCode { - hours: 1, - minutes: 3, - seconds: 27, - frames: 29, - drop_frame: false - }, - vec![ - 0x94, 0xae, 0x94, 0xae, 0x94, 0x20, 0x94, 0x20, 0x94, 0xf2, 0x94, 0xf2, 0xc8, 0x45, - 0xd9, 0x2c, 0x20, 0x54, 0xc8, 0x45, 0x52, 0x45, 0xae, 0x80, 0x94, 0x2c, 0x94, 0x2c, - 0x80, 0x80, 0x80, 0x80, 0x94, 0x2f, 0x94, 0x2f, - ] - ), + caption(&mut input), + Ok(SccLine::Caption( + TimeCode { + hours: 1, + minutes: 3, + seconds: 27, + frames: 29, + drop_frame: false + }, + vec![ + 0x94, 0xae, 0x94, 0xae, 0x94, 0x20, 0x94, 0x20, 0x94, 0xf2, 0x94, 0xf2, 0xc8, + 0x45, 0xd9, 0x2c, 0x20, 0x54, 0xc8, 0x45, 0x52, 0x45, 0xae, 0x80, 0x94, 0x2c, + 0x94, 0x2c, 0x80, 0x80, 0x80, 0x80, 0x94, 0x2f, 0x94, 0x2f, + ] )) ); + assert!(input.is_empty()); + let mut input = b"00:00:00;00\t942c 942c".as_slice(); assert_eq!( - caption(b"00:00:00;00\t942c 942c".as_ref()), - Ok(( - b"".as_ref(), - SccLine::Caption( - TimeCode { - hours: 0, - minutes: 0, - seconds: 00, - frames: 00, - drop_frame: true, - }, - vec![0x94, 0x2c, 0x94, 0x2c], - ), + caption(&mut input), + Ok(SccLine::Caption( + TimeCode { + hours: 0, + minutes: 0, + seconds: 00, + frames: 00, + drop_frame: true, + }, + vec![0x94, 0x2c, 0x94, 0x2c], )) ); + assert!(input.is_empty()); + let mut input = b"00:00:00;00\t942c 942c\r\n".as_slice(); assert_eq!( - caption(b"00:00:00;00\t942c 942c\r\n".as_ref()), - Ok(( - b"".as_ref(), - SccLine::Caption( - TimeCode { - hours: 0, - minutes: 0, - seconds: 00, - frames: 00, - drop_frame: true, - }, - vec![0x94, 0x2c, 0x94, 0x2c], - ), + caption(&mut input), + Ok(SccLine::Caption( + TimeCode { + hours: 0, + minutes: 0, + seconds: 00, + frames: 00, + drop_frame: true, + }, + vec![0x94, 0x2c, 0x94, 0x2c], )) ); + assert!(input.is_empty()); } #[test]