From 499a268b40e96c6f8922b93da560225caa2a27bd Mon Sep 17 00:00:00 2001 From: PranavRJoshi Date: Fri, 15 May 2026 19:41:57 +0545 Subject: [PATCH] feat: support zero-address file insertion Signed-off-by: PranavRJoshi --- src/sed/compiler.rs | 54 +++++++++++++++++++- src/sed/processor.rs | 20 ++++++++ tests/by-util/test_sed.rs | 8 +++ tests/fixtures/sed/output/cmd_read_one_addr | 23 +++++++++ tests/fixtures/sed/output/cmd_read_zero_addr | 23 +++++++++ 5 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/sed/output/cmd_read_one_addr create mode 100644 tests/fixtures/sed/output/cmd_read_zero_addr diff --git a/src/sed/compiler.rs b/src/sed/compiler.rs index bcfff31..7a784c7 100644 --- a/src/sed/compiler.rs +++ b/src/sed/compiler.rs @@ -403,8 +403,15 @@ fn compile_address_range( } } + // zero-address r command check if is_line0 && n_addr == 1 { - return compilation_error(lines, line, "address 0 requires a second address"); + // after retrieval of first address, subsequent spaces + // are consumed unconditionally. By now, the position + // must be in non-whitespace character or eol. + let next_cmd = if line.eol() { '\0' } else { line.current() }; + if !matches!(next_cmd, 'r') { + return compilation_error(lines, line, "address 0 requires a second address"); + } } Ok(n_addr) @@ -1804,6 +1811,51 @@ mod tests { ScriptCharProvider::new("") } + #[test] + fn test_zero_addr_r_accepted() { + for input in ["0r", "0 r"] { + let (lines, mut chars) = make_providers(input); + let mut cmd = Rc::new(RefCell::new(Command::default())); + let n_addr = compile_address_range(&lines, &mut chars, &mut cmd, &ctx()).unwrap(); + + assert_eq!(n_addr, 1); + assert!(matches!(cmd.borrow().addr1, Some(Address::Line(0)))); + assert_eq!(chars.current(), 'r'); + } + } + + // Zero-address with no commands + #[test] + fn test_zero_addr_no_commands() { + let (lines, mut chars) = make_providers("0"); + let mut cmd = Rc::new(RefCell::new(Command::default())); + let result = compile_address_range(&lines, &mut chars, &mut cmd, &ctx()); + + assert!(result.is_err()); + assert!( + result + .unwrap_err() + .to_string() + .contains("address 0 requires a second addres") + ); + } + + // Zero-address with a command other than 'r' must still be rejected. + #[test] + fn test_zero_addr_non_r_rejected() { + let (lines, mut chars) = make_providers("0p"); + let mut cmd = Rc::new(RefCell::new(Command::default())); + let result = compile_address_range(&lines, &mut chars, &mut cmd, &ctx()); + + assert!(result.is_err()); + assert!( + result + .unwrap_err() + .to_string() + .contains("address 0 requires a second addres") + ); + } + #[test] fn test_compile_sequence_empty_input() { let mut provider = make_line_provider(&[]); diff --git a/src/sed/processor.rs b/src/sed/processor.rs index 45d6b26..db39641 100644 --- a/src/sed/processor.rs +++ b/src/sed/processor.rs @@ -423,6 +423,26 @@ fn process_file( output: &mut OutputBuffer, context: &mut ProcessingContext, ) -> UResult<()> { + // Prescan for zero-address which must produce output + // before any input line is read. + { + let mut current = commands.clone(); + while let Some(cmd_rc) = current { + let cmd = cmd_rc.borrow(); + if matches!(cmd.code, 'r') + && matches!(&cmd.addr1, Some(Address::Line(0))) + && cmd.addr2.is_none() + { + let path = extract_variant!(cmd, Path); + output.copy_file(path)?; + } + let next = cmd.next.clone(); + // Release RefCell borrow before reassigning to 'current' + drop(cmd); + current = next; + } + } + // Loop over the input lines as pattern space. 'lines: while let Some(mut pattern) = reader.get_line()? { context.line_number += 1; diff --git a/tests/by-util/test_sed.rs b/tests/by-util/test_sed.rs index b31fed7..467a807 100644 --- a/tests/by-util/test_sed.rs +++ b/tests/by-util/test_sed.rs @@ -774,6 +774,14 @@ check_output!( check_output!(read_ok, [format!("4r {LINES2}"), LINES1.to_string()]); check_output!(read_missing, ["5r /xyzzyxyzy42", LINES1]); check_output!(read_empty, ["6r input/empty", LINES1]); +check_output!( + cmd_read_zero_addr, + [format!("0r {LINES2}"), LINES1.to_string()] +); +check_output!( + cmd_read_one_addr, + [format!("1r {LINES2}"), LINES1.to_string()] +); #[test] fn write_single_file() -> std::io::Result<()> { diff --git a/tests/fixtures/sed/output/cmd_read_one_addr b/tests/fixtures/sed/output/cmd_read_one_addr new file mode 100644 index 0000000..1e269c7 --- /dev/null +++ b/tests/fixtures/sed/output/cmd_read_one_addr @@ -0,0 +1,23 @@ +l1_1 +l2_1 +l2_2 +l2_3 +l2_4 +l2_5 +l2_6 +l2_7 +l2_8 +l2_9 +l1_2 +l1_3 +l1_4 +l1_5 +l1_6 +l1_7 +l1_8 +l1_9 +l1_10 +l1_11 +l1_12 +l1_13 +l1_14 diff --git a/tests/fixtures/sed/output/cmd_read_zero_addr b/tests/fixtures/sed/output/cmd_read_zero_addr new file mode 100644 index 0000000..97a9b19 --- /dev/null +++ b/tests/fixtures/sed/output/cmd_read_zero_addr @@ -0,0 +1,23 @@ +l2_1 +l2_2 +l2_3 +l2_4 +l2_5 +l2_6 +l2_7 +l2_8 +l2_9 +l1_1 +l1_2 +l1_3 +l1_4 +l1_5 +l1_6 +l1_7 +l1_8 +l1_9 +l1_10 +l1_11 +l1_12 +l1_13 +l1_14