diff --git a/src/sed/command.rs b/src/sed/command.rs index fbf335ee..73f02068 100644 --- a/src/sed/command.rs +++ b/src/sed/command.rs @@ -334,6 +334,7 @@ pub struct Command { pub addr2: Option
, // End address pub non_select: bool, // True if '!' pub start_line: Option, // Start line number (or None if unlatched) + pub range_started: bool, // True once the range has been entered pub data: CommandData, // Command-specific data pub next: Option>>, // Pointer to next command pub location: ScriptLocation, // Command's definition location @@ -347,6 +348,7 @@ impl Default for Command { addr2: None, non_select: false, start_line: None, + range_started: false, data: CommandData::None, next: None, location: ScriptLocation::default(), diff --git a/src/sed/processor.rs b/src/sed/processor.rs index 3f0ebb8b..5aa8ab57 100644 --- a/src/sed/processor.rs +++ b/src/sed/processor.rs @@ -124,7 +124,15 @@ fn applies( } } else if let Some(addr1) = &command.addr1 { // See if latch must start. - if match_address(addr1, reader, pattern, context, &command.location)? { + let starts = match addr1 { + // Numeric start already passed (block, d, n, branch): start once here. + Address::Line(n) if linenum > *n => { + !command.range_started && !matches!(addr2, Address::Line(m) if *m < linenum) + } + _ => match_address(addr1, reader, pattern, context, &command.location)?, + }; + if starts { + command.range_started = true; match addr2 { Address::Line(n) if linenum >= *n => { context.last_address = true; @@ -961,13 +969,10 @@ fn reset_latched_address_ranges(range_commands: &mut [Rc>]) { for cmd_rc in range_commands.iter() { let mut cmd = cmd_rc.borrow_mut(); - cmd.start_line = - // Check for address-spec line 0 pre-latch extension. - if let Some(addr1) = &cmd.addr1 && matches!(addr1, Address::Line(0)) { - Some(0) - } else { - None - }; + // Check for address-spec line 0 pre-latch extension. + let pre_latched = matches!(cmd.addr1, Some(Address::Line(0))); + cmd.start_line = if pre_latched { Some(0) } else { None }; + cmd.range_started = pre_latched; } } diff --git a/tests/by-util/test_sed.rs b/tests/by-util/test_sed.rs index ace171d3..bedf2671 100644 --- a/tests/by-util/test_sed.rs +++ b/tests/by-util/test_sed.rs @@ -2760,6 +2760,74 @@ fn test_step_match_zero_closes_range() { .stdout_is("1\n2\nX\n4\n5\n"); } +#[test] +fn test_range_numeric_start_already_passed() { + // The inner range is first evaluated on line 3, after its start line. + for (script, expected) in [ + ("3,6{ 1,4p }", "3\n4\n"), + ("3,6{ 3,4p }", "3\n4\n"), + ("3,6{ 1,3p }", "3\n"), + ("3,6{ 1,2p }", ""), + ("3,6{ 1,~4p }", "3\n4\n"), + ("3,6{ 1,+1p }", "3\n4\n"), + ("3,6{ 1,/5/p }", "3\n4\n5\n"), + ("3,6{ 1,$p }", "3\n4\n5\n6\n"), + ("3,6{ 1,2!p }", "3\n4\n5\n6\n"), + ] { + new_ucmd!() + .args(&["-n", script]) + .pipe_in("1\n2\n3\n4\n5\n6\n7\n") + .succeeds() + .stdout_is(expected); + } +} + +#[test] +fn test_range_numeric_start_passed_starts_once() { + // Once the range has ended, it does not start again. + for (script, expected) in [ + ("/[2468]/{ 1,/[48]/p }", "2\n4\n"), + ("/[3579]/{ 2,/./p }", "3\n5\n"), + ("1,/3/p", "1\n2\n3\n"), + ("0,/2/p", "1\n2\n"), + ("/[3-9]/{ 0,/5/p }", "3\n4\n5\n"), + ] { + new_ucmd!() + .args(&["-n", script]) + .pipe_in("1\n2\n3\n4\n5\n6\n7\n8\n9\n") + .succeeds() + .stdout_is(expected); + } +} + +#[test] +fn test_range_numeric_start_passed_separate_files() -> std::io::Result<()> { + // With -s the "started once" state is reset, so the range starts again in the second file. + let input = "1\n2\n3\n4\n5\n6\n7\n"; + let (_dir, path1, path2) = two_inputs(input, input)?; + + new_ucmd!() + .args(&[ + "-n", + "-s", + "3,6{ 1,4p }", + path1.to_str().unwrap(), + path2.to_str().unwrap(), + ]) + .succeeds() + .stdout_is("3\n4\n3\n4\n"); + Ok(()) +} + +#[test] +fn test_range_numeric_start_skipped_by_d() { + new_ucmd!() + .args(&["-e", "1,3d", "-e", "2,5s/$/!/"]) + .pipe_in("a\nb\nc\nd\ne\nf\n") + .succeeds() + .stdout_is("d!\ne!\nf\n"); +} + #[test] fn test_step_non_numeric() { for (script, column) in [("1~/x/p", 6), ("1,~/x/p", 7)] {