diff --git a/crates/anstyle-parse/benches/parse.rs b/crates/anstyle-parse/benches/parse.rs index 3aa61833..8c64ecf4 100644 --- a/crates/anstyle-parse/benches/parse.rs +++ b/crates/anstyle-parse/benches/parse.rs @@ -3,7 +3,7 @@ use criterion::{black_box, Criterion}; use anstyle_parse::*; struct BenchDispatcher; -impl Perform for BenchDispatcher { +impl Perform for BenchDispatcher { fn print(&mut self, c: char) { black_box(c); } @@ -32,6 +32,35 @@ impl Perform for BenchDispatcher { black_box((intermediates, ignore, byte)); } } +impl Perform<&'_ str> for BenchDispatcher { + fn print(&mut self, c: &'_ str) { + black_box(c); + } + + fn execute(&mut self, byte: u8) { + black_box(byte); + } + + fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: u8) { + black_box((params, intermediates, ignore, c)); + } + + fn put(&mut self, byte: u8) { + black_box(byte); + } + + fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { + black_box((params, bell_terminated)); + } + + fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: u8) { + black_box((params, intermediates, ignore, c)); + } + + fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { + black_box((intermediates, ignore, byte)); + } +} #[derive(Default)] struct Strip(String); @@ -40,7 +69,7 @@ impl Strip { Self(String::with_capacity(capacity)) } } -impl Perform for Strip { +impl Perform for Strip { fn print_control(byte: u8) -> bool { byte.is_ascii_whitespace() } @@ -48,6 +77,14 @@ impl Perform for Strip { self.0.push(c); } } +impl Perform<&'_ str> for Strip { + fn print_control(byte: u8) -> bool { + byte.is_ascii_whitespace() + } + fn print(&mut self, c: &'_ str) { + self.0.push_str(c); + } +} fn parse(c: &mut Criterion) { for (name, content) in [ @@ -61,28 +98,50 @@ fn parse(c: &mut Criterion) { ), ] { let mut group = c.benchmark_group(name); - group.bench_function("advance", |b| { + group.bench_function("advance_byte", |b| { b.iter(|| { let mut dispatcher = BenchDispatcher; let mut parser = Parser::::new(); for byte in content { - parser.advance(&mut dispatcher, *byte); + parser.advance_byte(&mut dispatcher, *byte); } }) }); - group.bench_function("advance_strip", |b| { + if let Ok(content) = std::str::from_utf8(content) { + group.bench_function("advance_str", |b| { + b.iter(|| { + let mut dispatcher = BenchDispatcher; + let mut parser = Parser::::new(); + + parser.advance_str(&mut dispatcher, content); + }) + }); + } + group.bench_function("advance_byte(strip)", |b| { b.iter(|| { let mut stripped = Strip::with_capacity(content.len()); let mut parser = Parser::::new(); for byte in content { - parser.advance(&mut stripped, *byte); + parser.advance_byte(&mut stripped, *byte); } black_box(stripped.0) }) }); + if let Ok(content) = std::str::from_utf8(content) { + group.bench_function("advance_str(strip)", |b| { + b.iter(|| { + let mut stripped = Strip::with_capacity(content.len()); + let mut parser = Parser::::new(); + + parser.advance_str(&mut stripped, content); + + black_box(stripped.0) + }) + }); + } } } diff --git a/crates/anstyle-parse/examples/parselog.rs b/crates/anstyle-parse/examples/parselog.rs index ed89650d..237fa780 100644 --- a/crates/anstyle-parse/examples/parselog.rs +++ b/crates/anstyle-parse/examples/parselog.rs @@ -6,7 +6,7 @@ use anstyle_parse::{DefaultCharAccumulator, Params, Parser, Perform}; /// A type implementing Perform that just logs actions struct Log; -impl Perform for Log { +impl Perform for Log { fn print(&mut self, c: char) { println!("[print] {:?}", c); } @@ -66,7 +66,7 @@ fn main() { Ok(0) => break, Ok(n) => { for byte in &buf[..n] { - statemachine.advance(&mut performer, *byte); + statemachine.advance_byte(&mut performer, *byte); } } Err(err) => { diff --git a/crates/anstyle-parse/src/lib.rs b/crates/anstyle-parse/src/lib.rs index 38f1710c..d176d43a 100644 --- a/crates/anstyle-parse/src/lib.rs +++ b/crates/anstyle-parse/src/lib.rs @@ -96,7 +96,69 @@ where /// /// Requires a [`Perform`] in case `byte` triggers an action #[inline] - pub fn advance(&mut self, performer: &mut P, byte: u8) { + pub fn advance_str<'i, P>(&mut self, performer: &mut P, bytes: &'i str) + where + P: Perform<&'i str>, + { + let mut bytes = bytes.as_bytes(); + 'empty: while !bytes.is_empty() { + while !matches!(self.state, State::Ground) { + if !self.next_byte(performer, &mut bytes) { + break 'empty; + } + } + let offset = bytes.iter().copied().position(|b| { + let change = table::state_change(State::Ground, b); + let (_state, action) = unpack(change); + let printable = action == Action::Print + || action == Action::BeginUtf8 + // since we know the input is valid UTF-8, the only thing we can do with + // continuations is to print them + || is_utf8_continuation(b) + || (action == Action::Execute && P::print_control(b)); + !printable + }); + if let Some(offset) = offset { + if offset != 0 { + let (printable, next) = bytes.split_at(offset); + let printable = core::str::from_utf8(printable).unwrap(); + performer.print(printable); + bytes = next; + } + self.next_byte(performer, &mut bytes); + } else { + let (printable, next) = (bytes, b""); + let printable = core::str::from_utf8(printable).unwrap(); + performer.print(printable); + bytes = next; + } + } + } + + #[inline] + fn next_byte<'i, P, O>(&mut self, performer: &mut P, bytes: &mut &'i [u8]) -> bool + where + P: Perform, + { + if let Some((byte, next)) = bytes.split_first() { + self.advance_byte(&mut Forward::new(performer), *byte); + *bytes = next; + true + } else { + false + } + } + + /// Advance the parser state + /// + /// Requires a [`Perform`] in case `byte` triggers an action + /// + /// [`Perform`]: trait.Perform.html + #[inline] + pub fn advance_byte

(&mut self, performer: &mut P, byte: u8) + where + P: Perform, + { // Utf8 characters are handled out-of-band. if let State::Utf8 = self.state { self.process_utf8(performer, byte); @@ -120,7 +182,7 @@ where #[inline] fn process_utf8

(&mut self, performer: &mut P, byte: u8) where - P: Perform, + P: Perform, { if let Some(c) = self.utf8_parser.add(byte) { performer.print(c); @@ -131,7 +193,7 @@ where #[inline] fn perform_state_change

(&mut self, performer: &mut P, state: State, action: Action, byte: u8) where - P: Perform, + P: Perform, { match state { State::Anywhere => { @@ -179,7 +241,10 @@ where /// /// The aliasing is needed here for multiple slices into self.osc_raw #[inline] - fn osc_dispatch(&self, performer: &mut P, byte: u8) { + fn osc_dispatch

(&self, performer: &mut P, byte: u8) + where + P: Perform, + { let mut slices: [MaybeUninit<&[u8]>; MAX_OSC_PARAMS] = unsafe { MaybeUninit::uninit().assume_init() }; @@ -196,7 +261,10 @@ where } #[inline] - fn perform_action(&mut self, performer: &mut P, action: Action, byte: u8) { + fn perform_action

(&mut self, performer: &mut P, action: Action, byte: u8) + where + P: Perform, + { match action { Action::Print => performer.print(byte as char), Action::Execute if P::print_control(byte) => performer.print(byte as char), @@ -392,7 +460,7 @@ impl<'a> utf8::Receiver for VtUtf8Receiver<'a> { /// a useful way in my own words for completeness, but the site should be /// referenced if something isn't clear. If the site disappears at some point in /// the future, consider checking archive.org. -pub trait Perform { +pub trait Perform

{ /// Whether single-byte control characters should be [`Perform::execute`]d or /// [`Perform::print`]ed. fn print_control(_byte: u8) -> bool { @@ -400,7 +468,7 @@ pub trait Perform { } /// Draw a character to the screen and update states. - fn print(&mut self, _c: char) {} + fn print(&mut self, _c: P) {} /// Execute a C0 or C1 control function. fn execute(&mut self, _byte: u8) {} @@ -449,3 +517,68 @@ pub trait Perform { /// subsequent characters were ignored. fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {} } + +struct Forward<'p, P, O> { + inner: &'p mut P, + o: core::marker::PhantomData, +} + +impl<'p, P, O> Forward<'p, P, O> +where + P: Perform, +{ + fn new(inner: &'p mut P) -> Self { + Self { + inner, + o: Default::default(), + } + } +} + +impl<'p, P, O> Perform for Forward<'p, P, O> +where + P: Perform, +{ + fn print_control(_byte: u8) -> bool { + false + } + + fn print(&mut self, _c: char) { + #[cfg(debug_assertions)] + panic!("should not be printing {:?}", _c); + } + + fn execute(&mut self, byte: u8) { + self.inner.execute(byte) + } + + fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, action: u8) { + self.inner.hook(params, intermediates, ignore, action) + } + + fn put(&mut self, byte: u8) { + self.inner.put(byte) + } + + fn unhook(&mut self) { + self.inner.unhook() + } + + fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { + self.inner.osc_dispatch(params, bell_terminated) + } + + fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, action: u8) { + self.inner + .csi_dispatch(params, intermediates, ignore, action) + } + + fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { + self.inner.esc_dispatch(intermediates, ignore, byte) + } +} + +#[inline] +fn is_utf8_continuation(b: u8) -> bool { + matches!(b, 0x80..=0xbf) +} diff --git a/crates/anstyle-parse/tests/testsuite.proptest-regressions b/crates/anstyle-parse/tests/testsuite.proptest-regressions new file mode 100644 index 00000000..9a76f2de --- /dev/null +++ b/crates/anstyle-parse/tests/testsuite.proptest-regressions @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 001bdfa800d266450c8dc1f7d2b63920b433ced8528f12f343c7925d1ab943c9 # shrinks to input = "𐂀" diff --git a/crates/anstyle-parse/tests/testsuite.rs b/crates/anstyle-parse/tests/testsuite.rs index d5473cec..f2409a9b 100644 --- a/crates/anstyle-parse/tests/testsuite.rs +++ b/crates/anstyle-parse/tests/testsuite.rs @@ -24,7 +24,7 @@ struct Dispatcher { dispatched: Vec, } -impl Perform for Dispatcher { +impl Perform for Dispatcher { fn print(&mut self, c: char) { self.dispatched.push(Sequence::Print(c)); } @@ -63,6 +63,45 @@ impl Perform for Dispatcher { } } +impl Perform<&'_ str> for Dispatcher { + fn print(&mut self, c: &'_ str) { + self.dispatched.extend(Dispatcher::from(c).dispatched); + } + + fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { + let params = params.iter().map(|p| p.to_vec()).collect(); + self.dispatched.push(Sequence::Osc(params, bell_terminated)); + } + + fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: u8) { + let params = params.iter().map(|subparam| subparam.to_vec()).collect(); + let intermediates = intermediates.to_vec(); + self.dispatched + .push(Sequence::Csi(params, intermediates, ignore, c)); + } + + fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { + let intermediates = intermediates.to_vec(); + self.dispatched + .push(Sequence::Esc(intermediates, ignore, byte)); + } + + fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: u8) { + let params = params.iter().map(|subparam| subparam.to_vec()).collect(); + let intermediates = intermediates.to_vec(); + self.dispatched + .push(Sequence::DcsHook(params, intermediates, ignore, c)); + } + + fn put(&mut self, byte: u8) { + self.dispatched.push(Sequence::DcsPut(byte)); + } + + fn unhook(&mut self) { + self.dispatched.push(Sequence::DcsUnhook); + } +} + impl std::ops::Deref for Dispatcher { type Target = [Sequence]; @@ -137,9 +176,41 @@ impl From for Sequence { } } -#[test] -fn parse_osc() { - let input = OSC_BYTES; +macro_rules! advance_byte { + ($name: ident, $gen: ident) => { + #[test] + fn $name() { + let (input, expected) = $gen(); + let mut dispatcher = Dispatcher::default(); + let mut parser = Parser::::new(); + + for byte in &input { + parser.advance_byte(&mut dispatcher, *byte); + } + + assert_eq!(expected, dispatcher); + } + }; +} + +macro_rules! advance_str { + ($name: ident, $gen: ident) => { + #[test] + fn $name() { + let (input, expected) = $gen(); + let input = String::from_utf8(input).unwrap(); + let mut dispatcher = Dispatcher::default(); + let mut parser = Parser::::new(); + + parser.advance_str(&mut dispatcher, &input); + + assert_eq!(expected, dispatcher); + } + }; +} + +fn gen_osc() -> (Vec, Dispatcher) { + let input = OSC_BYTES.to_vec(); let expected = start() + Sequence::Osc( vec![ @@ -148,160 +219,121 @@ fn parse_osc() { ], true, ); - - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); - - for byte in input { - parser.advance(&mut dispatcher, *byte); - } - - assert_eq!(expected, dispatcher); + (input, expected) } -#[test] -fn parse_empty_osc() { - let input = &[0x1b, 0x5d, 0x07]; - let expected = start() + Sequence::Osc(vec![vec![]], true); +advance_byte!(advance_byte_osc, gen_osc); +advance_str!(advance_str_osc, gen_osc); - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); - - for byte in input { - parser.advance(&mut dispatcher, *byte); - } - - assert_eq!(expected, dispatcher); +fn gen_empty_osc() -> (Vec, Dispatcher) { + let input = [0x1b, 0x5d, 0x07].into(); + let expected = start() + Sequence::Osc(vec![vec![]], true); + (input, expected) } -#[test] -fn parse_osc_max_params() { +advance_byte!(advance_byte_empty_osc, gen_empty_osc); +advance_str!(advance_str_empty_osc, gen_empty_osc); + +fn gen_osc_max_params() -> (Vec, Dispatcher) { let params = ";".repeat(MAX_PARAMS + 1); let input = format!("\x1b]{}\x1b", ¶ms[..]).into_bytes(); let expected = start() + Sequence::Osc(vec![vec![]; MAX_OSC_PARAMS], false); - - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); - - for byte in input { - parser.advance(&mut dispatcher, byte); - } - - assert_eq!(expected, dispatcher); + (input, expected) } -#[test] -fn parse_osc_bell_terminated() { - static INPUT: &[u8] = b"\x1b]11;ff/00/ff\x07"; +advance_byte!(advance_byte_osc_max_params, gen_osc_max_params); +advance_str!(advance_str_osc_max_params, gen_osc_max_params); + +fn gen_osc_bell_terminated() -> (Vec, Dispatcher) { + let input = b"\x1b]11;ff/00/ff\x07".to_vec(); let expected = start() + Sequence::Osc( - vec![INPUT[2..4].to_vec(), INPUT[5..(INPUT.len() - 1)].to_vec()], + vec![input[2..4].to_vec(), input[5..(input.len() - 1)].to_vec()], true, ); - - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); - - for byte in INPUT { - parser.advance(&mut dispatcher, *byte); - } - - assert_eq!(expected, dispatcher); + (input, expected) } -#[test] -fn parse_osc_c0_st_terminated() { - static INPUT: &[u8] = b"\x1b]11;ff/00/ff\x1b\\"; +advance_byte!(advance_byte_osc_bell_terminated, gen_osc_bell_terminated); +advance_str!(advance_str_osc_bell_terminated, gen_osc_bell_terminated); + +fn gen_osc_c0_st_terminated() -> (Vec, Dispatcher) { + let input = b"\x1b]11;ff/00/ff\x1b\\".to_vec(); let expected = start() + Sequence::Osc( - vec![INPUT[2..4].to_vec(), INPUT[5..(INPUT.len() - 2)].to_vec()], + vec![input[2..4].to_vec(), input[5..(input.len() - 2)].to_vec()], false, ) + Sequence::Esc(vec![], false, 92); - - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); - - for byte in INPUT { - parser.advance(&mut dispatcher, *byte); - } - - assert_eq!(expected, dispatcher); + (input, expected) } -#[test] -fn parse_osc_with_utf8_arguments() { - static INPUT: &[u8] = &[ +advance_byte!(advance_byte_osc_c0_st_terminated, gen_osc_c0_st_terminated); +advance_str!(advance_str_osc_c0_st_terminated, gen_osc_c0_st_terminated); + +fn gen_osc_with_utf8_arguments() -> (Vec, Dispatcher) { + let input = [ 0x0d, 0x1b, 0x5d, 0x32, 0x3b, 0x65, 0x63, 0x68, 0x6f, 0x20, 0x27, 0xc2, 0xaf, 0x5c, 0x5f, 0x28, 0xe3, 0x83, 0x84, 0x29, 0x5f, 0x2f, 0xc2, 0xaf, 0x27, 0x20, 0x26, 0x26, 0x20, 0x73, 0x6c, 0x65, 0x65, 0x70, 0x20, 0x31, 0x07, - ]; + ] + .to_vec(); let expected = - start() + Sequence::Osc(vec![vec![b'2'], INPUT[5..(INPUT.len() - 1)].to_vec()], true); - - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); - - for byte in INPUT { - parser.advance(&mut dispatcher, *byte); - } - - assert_eq!(expected, dispatcher); + start() + Sequence::Osc(vec![vec![b'2'], input[5..(input.len() - 1)].to_vec()], true); + (input, expected) } -#[test] -fn parse_osc_containing_string_terminator() { - static INPUT: &[u8] = b"\x1b]2;\xe6\x9c\xab\x1b\\"; +advance_byte!( + advance_byte_osc_with_utf8_arguments, + gen_osc_with_utf8_arguments +); +advance_str!( + advance_str_osc_with_utf8_arguments, + gen_osc_with_utf8_arguments +); + +fn gen_osc_containing_string_terminator() -> (Vec, Dispatcher) { + let input = b"\x1b]2;\xe6\x9c\xab\x1b\\".to_vec(); let expected = start() + Sequence::Osc( - vec![vec![b'2'], INPUT[4..(INPUT.len() - 2)].to_vec()], + vec![vec![b'2'], input[4..(input.len() - 2)].to_vec()], false, ) + Sequence::Esc(vec![], false, 92); - - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); - - for byte in INPUT { - parser.advance(&mut dispatcher, *byte); - } - - assert_eq!(expected, dispatcher); + (input, expected) } -#[test] -fn parse_exceed_max_buffer_size() { +advance_byte!( + advance_byte_osc_containing_string_terminator, + gen_osc_containing_string_terminator +); + +fn gen_exceed_max_buffer_size() -> (Vec, Dispatcher) { static NUM_BYTES: usize = MAX_OSC_RAW + 100; static INPUT_START: &[u8] = &[0x1b, b']', b'5', b'2', b';', b's']; static INPUT_END: &[u8] = &[b'\x07']; + let mut input = INPUT_START.to_vec(); + input.resize(INPUT_START.len() + NUM_BYTES, b'a'); + input.extend(INPUT_END); let mut param = vec![115]; #[cfg(not(feature = "core"))] param.extend(vec![97; NUM_BYTES + INPUT_END.len() - 1]); #[cfg(feature = "core")] param.extend(vec![97; MAX_OSC_RAW - INPUT_END.len() - 2]); let expected = start() + Sequence::Osc(vec![b"52".to_vec(), param], true); - - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); - - // Create valid OSC escape - for byte in INPUT_START { - parser.advance(&mut dispatcher, *byte); - } - // Exceed max buffer size - for _ in 0..NUM_BYTES { - parser.advance(&mut dispatcher, b'a'); - } - // Terminate escape for dispatch - for byte in INPUT_END { - parser.advance(&mut dispatcher, *byte); - } - - assert_eq!(expected, dispatcher); + (input, expected) } -#[test] -fn parse_csi_max_params() { +advance_byte!( + advance_byte_exceed_max_buffer_size, + gen_exceed_max_buffer_size +); +advance_str!( + advance_str_exceed_max_buffer_size, + gen_exceed_max_buffer_size +); + +fn gen_csi_max_params() -> (Vec, Dispatcher) { // This will build a list of repeating '1;'s // The length is MAX_PARAMS - 1 because the last semicolon is interpreted // as an implicit zero, making the total number of parameters MAX_PARAMS @@ -310,19 +342,13 @@ fn parse_csi_max_params() { let mut params = vec![vec![1]; MAX_PARAMS - 1]; params.push(vec![0]); let expected = start() + Sequence::Csi(params, vec![], false, b'p'); - - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); - - for byte in input { - parser.advance(&mut dispatcher, byte); - } - - assert_eq!(expected, dispatcher); + (input, expected) } -#[test] -fn parse_csi_params_ignore_long_params() { +advance_byte!(advance_byte_csi_max_params, gen_csi_max_params); +advance_str!(advance_str_csi_max_params, gen_csi_max_params); + +fn gen_csi_params_ignore_long_params() -> (Vec, Dispatcher) { // This will build a list of repeating '1;'s // The length is MAX_PARAMS because the last semicolon is interpreted // as an implicit zero, making the total number of parameters MAX_PARAMS + 1 @@ -330,141 +356,89 @@ fn parse_csi_params_ignore_long_params() { let input = format!("\x1b[{}p", ¶ms[..]).into_bytes(); let params = vec![vec![1]; MAX_PARAMS]; let expected = start() + Sequence::Csi(params, vec![], true, b'p'); - - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); - - for byte in input { - parser.advance(&mut dispatcher, byte); - } - - assert_eq!(expected, dispatcher); + (input, expected) } -#[test] -fn parse_csi_params_trailing_semicolon() { - let input = b"\x1b[4;m"; - let expected = start() + Sequence::Csi(vec![vec![4], vec![0]], vec![], false, b'm'); - - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); +advance_byte!( + advance_byte_csi_params_ignore_long_params, + gen_csi_params_ignore_long_params +); - for byte in input { - parser.advance(&mut dispatcher, *byte); - } - - assert_eq!(expected, dispatcher); +fn gen_csi_params_trailing_semicolon() -> (Vec, Dispatcher) { + let input = b"\x1b[4;m".to_vec(); + let expected = start() + Sequence::Csi(vec![vec![4], vec![0]], vec![], false, b'm'); + (input, expected) } -#[test] -fn parse_csi_params_leading_semicolon() { - let input = b"\x1b[;4m"; - let expected = start() + Sequence::Csi(vec![vec![0], vec![4]], vec![], false, b'm'); - - // Create dispatcher and check state - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); +advance_byte!( + advance_byte_csi_params_trailing_semicolon, + gen_csi_params_trailing_semicolon +); - for byte in input { - parser.advance(&mut dispatcher, *byte); - } - - assert_eq!(expected, dispatcher); +fn gen_csi_params_leading_semicolon() -> (Vec, Dispatcher) { + let input = b"\x1b[;4m".to_vec(); + let expected = start() + Sequence::Csi(vec![vec![0], vec![4]], vec![], false, b'm'); + (input, expected) } -#[test] -fn parse_long_csi_param() { +advance_byte!( + advance_byte_csi_params_leading_semicolon, + gen_csi_params_leading_semicolon +); + +fn gen_csi_long_param() -> (Vec, Dispatcher) { // The important part is the parameter, which is (i64::MAX + 1) - static INPUT: &[u8] = b"\x1b[9223372036854775808m"; + let input = b"\x1b[9223372036854775808m".to_vec(); let expected = start() + Sequence::Csi(vec![vec![u16::MAX]], vec![], false, b'm'); - - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); - - for byte in INPUT { - parser.advance(&mut dispatcher, *byte); - } - - assert_eq!(expected, dispatcher); + (input, expected) } -#[test] -fn parse_csi_reset() { - static INPUT: &[u8] = b"\x1b[3;1\x1b[?1049h"; - let expected = start() + Sequence::Csi(vec![vec![1049]], vec![b'?'], false, b'h'); - - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); +advance_byte!(advance_byte_csi_long_param, gen_csi_long_param); +advance_str!(advance_str_csi_long_param, gen_csi_long_param); - for byte in INPUT { - parser.advance(&mut dispatcher, *byte); - } - - assert_eq!(expected, dispatcher); +fn gen_csi_reset() -> (Vec, Dispatcher) { + let input = b"\x1b[3;1\x1b[?1049h".to_vec(); + let expected = start() + Sequence::Csi(vec![vec![1049]], vec![b'?'], false, b'h'); + (input, expected) } -#[test] -fn parse_csi_subparameters() { - static INPUT: &[u8] = b"\x1b[38:2:255:0:255;1m"; +advance_byte!(advance_byte_csi_reset, gen_csi_reset); +advance_str!(advance_str_csi_reset, gen_csi_reset); + +fn gen_csi_subparameters() -> (Vec, Dispatcher) { + let input = b"\x1b[38:2:255:0:255;1m".to_vec(); let expected = start() + Sequence::Csi(vec![vec![38, 2, 255, 0, 255], vec![1]], vec![], false, b'm'); - - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); - - for byte in INPUT { - parser.advance(&mut dispatcher, *byte); - } - - assert_eq!(expected, dispatcher); + (input, expected) } -#[test] -fn parse_dcs_max_params() { +advance_byte!(advance_byte_csi_subparameters, gen_csi_subparameters); +advance_str!(advance_str_csi_subparameters, gen_csi_subparameters); + +fn gen_dcs_max_params() -> (Vec, Dispatcher) { let params = "1;".repeat(MAX_PARAMS + 1); let input = format!("\x1bP{}p", ¶ms[..]).into_bytes(); let expected = start() + Sequence::DcsHook(vec![vec![1]; MAX_PARAMS], vec![], true, b'p'); - - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); - - for byte in input { - parser.advance(&mut dispatcher, byte); - } - - assert_eq!(dispatcher.dispatched.len(), 1); - match &dispatcher.dispatched[0] { - Sequence::DcsHook(params, _, ignore, _) => { - assert_eq!(params.len(), MAX_PARAMS); - assert!(params.iter().all(|param| param == &[1])); - assert!(ignore); - } - _ => panic!("expected dcs sequence"), - } - assert_eq!(expected, dispatcher); + (input, expected) } -#[test] -fn parse_dcs_reset() { - static INPUT: &[u8] = b"\x1b[3;1\x1bP1$tx\x9c"; +advance_byte!(advance_byte_dcs_max_params, gen_dcs_max_params); +advance_str!(advance_str_dcs_max_params, gen_dcs_max_params); + +fn gen_dcs_reset() -> (Vec, Dispatcher) { + let input = b"\x1b[3;1\x1bP1$tx\x9c".to_vec(); let expected = start() + Sequence::DcsHook(vec![vec![1]], vec![36], false, b't') + Sequence::DcsPut(b'x') + Sequence::DcsUnhook; - - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); - - for byte in INPUT { - parser.advance(&mut dispatcher, *byte); - } - - assert_eq!(expected, dispatcher); + (input, expected) } -#[test] -fn parse_dcs() { - static INPUT: &[u8] = b"\x1bP0;1|17/ab\x9c"; +advance_byte!(advance_byte_dcs_reset, gen_dcs_reset); +// advance_str!(advance_str_dcs_reset, gen_dcs_reset); // input isn't UTF-8 + +fn gen_dcs() -> (Vec, Dispatcher) { + let input = b"\x1bP0;1|17/ab\x9c".to_vec(); let expected = start() + Sequence::DcsHook(vec![vec![0], vec![1]], vec![], false, b'|') + Sequence::DcsPut(b'1') @@ -473,20 +447,14 @@ fn parse_dcs() { + Sequence::DcsPut(b'a') + Sequence::DcsPut(b'b') + Sequence::DcsUnhook; - - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); - - for byte in INPUT { - parser.advance(&mut dispatcher, *byte); - } - - assert_eq!(expected, dispatcher); + (input, expected) } -#[test] -fn parse_intermediate_reset_on_dcs_exit() { - static INPUT: &[u8] = b"\x1bP=1sZZZ\x1b+\x5c"; +advance_byte!(advance_byte_dcs, gen_dcs); +// advance_str!(advance_str_dcs, gen_dcs); // input isn't UTF-8 + +fn gen_intermediate_reset_on_dcs_exit() -> (Vec, Dispatcher) { + let input = b"\x1bP=1sZZZ\x1b+\x5c".to_vec(); let expected = start() + Sequence::DcsHook(vec![vec![1]], vec![61], false, b's') + Sequence::DcsPut(b'Z') @@ -494,60 +462,61 @@ fn parse_intermediate_reset_on_dcs_exit() { + Sequence::DcsPut(b'Z') + Sequence::DcsUnhook + Sequence::Esc(vec![b'+'], false, b'\\'); + (input, expected) +} - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); - - for byte in INPUT { - parser.advance(&mut dispatcher, *byte); - } +advance_byte!( + advance_byte_intermediate_reset_on_dcs_exit, + gen_intermediate_reset_on_dcs_exit +); - assert_eq!(expected, dispatcher); +fn gen_esc_reset() -> (Vec, Dispatcher) { + let input = b"\x1b[3;1\x1b(A".to_vec(); + let expected = start() + Sequence::Esc(vec![b'('], false, b'A'); + (input, expected) } -#[test] -fn parse_esc_reset() { - static INPUT: &[u8] = b"\x1b[3;1\x1b(A"; - let expected = start() + Sequence::Esc(vec![b'('], false, b'A'); +advance_byte!(advance_byte_esc_reset, gen_esc_reset); +advance_str!(advance_str_esc_reset, gen_esc_reset); - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); +fn gen_params_buffer_filled_with_subparam() -> (Vec, Dispatcher) { + let input = b"\x1b[::::::::::::::::::::::::::::::::x\x1b".to_vec(); + let expected = start() + Sequence::Csi(vec![vec![0; 32]], vec![], true, b'x'); + (input, expected) +} - for byte in INPUT { - parser.advance(&mut dispatcher, *byte); - } +advance_byte!( + advance_byte_params_buffer_filled_with_subparam, + gen_params_buffer_filled_with_subparam +); - assert_eq!(expected, dispatcher); -} +proptest! { + #[test] + #[cfg(feature = "utf8")] + #[cfg_attr(any(miri, not(feature = "utf8")), ignore)] + fn advance_byte_utf8(input in "\\PC*") { + let expected = Dispatcher::from(input.as_str()); -#[test] -fn parse_params_buffer_filled_with_subparam() { - static INPUT: &[u8] = b"\x1b[::::::::::::::::::::::::::::::::x\x1b"; - let expected = start() + Sequence::Csi(vec![vec![0; 32]], vec![], true, b'x'); + let mut dispatcher = Dispatcher::default(); + let mut parser = Parser::::new(); - let mut dispatcher = Dispatcher::default(); - let mut parser = Parser::::new(); + for byte in input.as_bytes() { + parser.advance_byte(&mut dispatcher, *byte); + } - for byte in INPUT { - parser.advance(&mut dispatcher, *byte); + assert_eq!(expected, dispatcher); } - assert_eq!(expected, dispatcher); -} - -proptest! { #[test] #[cfg(feature = "utf8")] #[cfg_attr(any(miri, not(feature = "utf8")), ignore)] - fn parse_utf8(input in "\\PC*") { + fn advance_str_utf8(input in "\\PC*") { let expected = Dispatcher::from(input.as_str()); let mut dispatcher = Dispatcher::default(); let mut parser = Parser::::new(); - for byte in input.as_bytes() { - parser.advance(&mut dispatcher, *byte); - } + parser.advance_str(&mut dispatcher, &input); assert_eq!(expected, dispatcher); }