Author SHA1 Message Date
Sylvestre Ledru 3bdf96d08d sed: keep script position for errors at end of script
An error found once the script is exhausted, like an unterminated
command, was reported as ":0:N" with no input name. Keep the last input
name and line number so the error points at the right line.
2026-09-27 18:50:16 +02:00
2 changed files with 52 additions and 9 deletions

No files matched your search

+38 -8
View File
@@ -40,7 +40,12 @@ enum State {
input_name: String, // Input description (path or script string)
line_number: usize, // Current line number
},
Done, // All scripts have been processed
// All scripts have been processed; keeps the last position for errors
// raised at the end of the script.
Done {
input_name: String,
line_number: usize,
},
}
impl ScriptLineProvider {
@@ -55,16 +60,18 @@ impl ScriptLineProvider {
/// Return the currently processed script line number.
pub fn get_line_number(&self) -> usize {
match &self.state {
State::Active { line_number, .. } => *line_number,
_ => 0,
State::Active { line_number, .. } | State::Done { line_number, .. } => *line_number,
State::NotStarted => 0,
}
}
/// Return the currently processed script descriptive name.
pub fn get_input_name(&self) -> &str {
match &self.state {
State::Active { input_name, .. } => input_name.as_str(),
_ => "",
State::Active { input_name, .. } | State::Done { input_name, .. } => {
input_name.as_str()
}
State::NotStarted => "",
}
}
@@ -94,7 +101,7 @@ impl ScriptLineProvider {
return Ok(Some(line));
}
}
State::Done => {
State::Done { .. } => {
return Ok(None);
}
};
@@ -108,7 +115,23 @@ impl ScriptLineProvider {
// Move to the next available script source.
fn advance_source(&mut self, next_index: usize) -> UResult<()> {
if next_index >= self.sources.len() {
self.state = State::Done;
let (input_name, line_number) =
match std::mem::replace(&mut self.state, State::NotStarted) {
State::Active {
input_name,
line_number,
..
}
| State::Done {
input_name,
line_number,
} => (input_name, line_number),
State::NotStarted => (String::new(), 0),
};
self.state = State::Done {
input_name,
line_number,
};
return Ok(());
}
@@ -151,7 +174,14 @@ impl fmt::Debug for State {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
State::NotStarted => f.debug_struct("NotStarted").finish(),
State::Done => f.debug_struct("Done").finish(),
State::Done {
input_name,
line_number,
} => f
.debug_struct("Done")
.field("input_name", input_name)
.field("line_number", line_number)
.finish(),
State::Active {
index,
input_name,
+14 -1
View File
@@ -2487,7 +2487,20 @@ fn test_incomplete_test_command_posix() {
.args(&["--posix", "i\\"])
.fails()
.code_is(1)
.stderr_is("sed: :0:3: error: incomplete command\n");
.stderr_is("sed: <script argument 1>:1:3: error: incomplete command\n");
}
#[test]
fn test_error_at_end_of_script_keeps_its_position() {
// The script is exhausted when the error is found: report its last line,
// as GNU sed does ("-e expression #1").
new_ucmd!()
.args(&["s/a/b\\"])
.fails()
.code_is(1)
.stderr_is(
"sed: <script argument 1>:1:7: error: unterminated substitute replacement (unexpected EOF)\n",
);
}
#[test]