Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ cargo run --release
* In addition to `\n`, other escape sequences (octal, hex, C) are supported
in the strings of the `y` command.
Under POSIX these yield undefined behavior.
* The `a`, `c`, and `i` commands do not require an initial backslash,
allow text to appear on the same line, and support escape sequences
in the specified text.
* The substitution command replacement group `\0` is a synonym for &.
* A `Q` command (optionally followed by an exit code) quits immediately.
* The `q` command can be optionally followed by an exit code.
Expand Down
193 changes: 189 additions & 4 deletions src/uu/sed/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1023,15 +1023,101 @@ fn compile_number_command(

/// Compile commands that take text as an argument.
// Handles a, c, i
// According to POSIX, these commands expect \ followed by text.
// As a GNU extension the initial \ can be ommitted, and from then on
// character escapes are honored.
fn compile_text_command(
lines: &mut ScriptLineProvider,
line: &mut ScriptCharProvider,
cmd: &mut Command,
_context: &mut ProcessingContext,
context: &mut ProcessingContext,
) -> UResult<CommandHandling> {
line.advance(); // Skip the command character.

line.eat_spaces(); // Skip any leading whitespace.
if context.posix {
compile_text_command_posix(lines, line, cmd, context)
} else {
compile_text_command_gnu(lines, line, cmd, context)
}
}

/// Compile commands that take text as an argument (GNU syntax).
// Handles a, c, i; after the command and initial whitespace have been consumed.
// According to POSIX, these commands expect \ followed by text.
// As a GNU extension the initial \ can be ommitted, and from then on
// character escapes are honored.
fn compile_text_command_gnu(
lines: &mut ScriptLineProvider,
line: &mut ScriptCharProvider,
cmd: &mut Command,
_context: &mut ProcessingContext,
) -> UResult<CommandHandling> {
// True after a \ at the end of a line
let mut escaped_newline = false;

// Skip optional \.
if !line.eol() && line.current() == '\\' {
line.advance();
escaped_newline = line.eol();
}

// Gather replacement text. Stop on a non-escaped newline.
let mut text = String::new();
'text_content: loop {
if escaped_newline {
match lines.next_line()? {
None => {
break 'text_content;
}
Some(line_string) => {
*line = ScriptCharProvider::new(&line_string);
}
}
escaped_newline = false;
}

// Non-escaped newline
if line.eol() {
text.push('\n');
break 'text_content;
}

if line.current() == '\\' {
line.advance();

if line.eol() {
escaped_newline = true;
text.push('\n');
continue 'text_content;
}

match parse_char_escape(line) {
Some(decoded) => text.push(decoded),
None => {
// Invalid escapes result in the escaped character.
text.push(line.current());
line.advance();
}
}
} else {
text.push(line.current());
line.advance();
}
}
cmd.data = CommandData::Text(Cow::Owned(text));
Ok(CommandHandling::Continue)
}

/// Compile commands that take text as an argument (POSIX syntax).
// Handles a, c, i; after the command and initial whitespace have been consumed.
// According to POSIX, these commands expect \ followed by text.
fn compile_text_command_posix(
lines: &mut ScriptLineProvider,
line: &mut ScriptCharProvider,
cmd: &mut Command,
_context: &mut ProcessingContext,
) -> UResult<CommandHandling> {
if line.eol() || line.current() != '\\' {
return compilation_error(
lines,
Expand Down Expand Up @@ -2462,11 +2548,12 @@ mod tests {
}

#[test]
fn test_compile_spaces_single_line_text_command() {
fn test_compile_text_command_posix_spaces_single_line() {
let mut chars = make_char_provider("a \\ ");
let mut lines = make_line_provider(&["line1", "line2"]);
let mut cmd = Command::default();
let mut context = ProcessingContext::default();
context.posix = true;

compile_text_command(&mut lines, &mut chars, &mut cmd, &mut context).unwrap();
match &cmd.data {
Expand All @@ -2477,6 +2564,102 @@ mod tests {
}
}

#[test]
fn test_compile_text_command_gnu_optional_backslash() {
let mut chars = make_char_provider("athere");
let mut lines = make_line_provider(&["line1", "line2"]);
let mut cmd = Command::default();
let mut context = ProcessingContext::default();

compile_text_command(&mut lines, &mut chars, &mut cmd, &mut context).unwrap();
match &cmd.data {
CommandData::Text(text) => {
assert_eq!(text, "there\n");
}
_ => panic!("Expected CommandData::Text"),
}
}

#[test]
fn test_compile_text_command_gnu_optional_backslash_spaces() {
let mut chars = make_char_provider("a \t there");
let mut lines = make_line_provider(&["line1", "line2"]);
let mut cmd = Command::default();
let mut context = ProcessingContext::default();

compile_text_command(&mut lines, &mut chars, &mut cmd, &mut context).unwrap();
match &cmd.data {
CommandData::Text(text) => {
assert_eq!(text, "there\n");
}
_ => panic!("Expected CommandData::Text"),
}
}

#[test]
fn test_compile_text_command_gnu_optional_backslash_eol_eof() {
let mut chars = make_char_provider("a");
let mut lines = make_line_provider(&[]);
let mut cmd = Command::default();
let mut context = ProcessingContext::default();

compile_text_command(&mut lines, &mut chars, &mut cmd, &mut context).unwrap();
match &cmd.data {
CommandData::Text(text) => {
assert_eq!(text, "\n");
}
_ => panic!("Expected CommandData::Text"),
}
}

#[test]
fn test_compile_text_command_gnu_optional_backslash_escape_eof() {
let mut chars = make_char_provider("a\\");
let mut lines = make_line_provider(&[]);
let mut cmd = Command::default();
let mut context = ProcessingContext::default();

compile_text_command(&mut lines, &mut chars, &mut cmd, &mut context).unwrap();
match &cmd.data {
CommandData::Text(text) => {
assert_eq!(text, "");
}
_ => panic!("Expected CommandData::Text"),
}
}

#[test]
fn test_compile_text_command_gnu_no_first_escape() {
let mut chars = make_char_provider("a\\tom");
let mut lines = make_line_provider(&[]);
let mut cmd = Command::default();
let mut context = ProcessingContext::default();

compile_text_command(&mut lines, &mut chars, &mut cmd, &mut context).unwrap();
match &cmd.data {
CommandData::Text(text) => {
assert_eq!(text, "tom\n");
}
_ => panic!("Expected CommandData::Text"),
}
}

#[test]
fn test_compile_text_command_gnu_char_escapes() {
let mut chars = make_char_provider("i\\>\\h\\elll\\bo\\nto\\");
let mut lines = make_line_provider(&["all\\a", ""]);
let mut cmd = Command::default();
let mut context = ProcessingContext::default();

compile_text_command(&mut lines, &mut chars, &mut cmd, &mut context).unwrap();
match &cmd.data {
CommandData::Text(text) => {
assert_eq!(text, ">helll\x08o\nto\nall\x07\n");
}
_ => panic!("Expected CommandData::Text"),
}
}

#[test]
fn test_compile_two_line_text_command() {
let mut chars = make_char_provider("a\\");
Expand All @@ -2494,11 +2677,12 @@ mod tests {
}

#[test]
fn test_compile_text_command_without_backslash() {
fn test_compile_text_command_posix_without_backslash() {
let mut chars = make_char_provider("a");
let mut lines = make_line_provider(&["line1", "line2"]);
let mut cmd = Command::default();
let mut context = ProcessingContext::default();
context.posix = true;

let result = compile_text_command(&mut lines, &mut chars, &mut cmd, &mut context);
assert!(result.is_err());
Expand All @@ -2507,11 +2691,12 @@ mod tests {
}

#[test]
fn test_compile_text_command_with_trailing_chars() {
fn test_compile_text_command_posix_with_trailing_chars() {
let mut chars = make_char_provider("a \\ foo");
let mut lines = make_line_provider(&["line1", "line2"]);
let mut cmd = Command::default();
let mut context = ProcessingContext::default();
context.posix = true;

let result = compile_text_command(&mut lines, &mut chars, &mut cmd, &mut context);
assert!(result.is_err());
Expand Down
4 changes: 4 additions & 0 deletions src/uu/sed/src/delimited_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ pub fn parse_char_escape(line: &mut ScriptCharProvider) -> Option<char> {
line.advance();
Some('\x07')
}
'b' => {
line.advance();
Some('\x08')
}
'f' => {
line.advance();
Some('\x0c')
Expand Down
2 changes: 1 addition & 1 deletion src/uu/sed/src/sed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ pub fn uu_app() -> Command {
arg!(-l --length <NUM> "Specify the 'l' command line-wrap length.")
.value_parser(clap::value_parser!(u32)),
arg!(-n --quiet "Suppress automatic printing of pattern space.").aliases(["silent"]),
arg!(--posix "Disable all POSIX extensions."),
arg!(--posix "Disable non-POSIX extensions."),
arg!(-s --separate "Consider files as separate rather than as a long stream."),
arg!(--sandbox "Operate in a sandbox by disabling e/r/w commands."),
arg!(-u --unbuffered "Load minimal input data and flush output buffers regularly."),
Expand Down
Loading
Loading