1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 03:27:44 +00:00

expr: Handle $ at the beginning of the regex pattern

This commit is contained in:
Teemu Pätsi 2025-05-24 01:28:53 +03:00
parent 4555e6fe48
commit b0390fe36e
No known key found for this signature in database
GPG key ID: 5494F73B045AB692
2 changed files with 40 additions and 15 deletions

View file

@ -161,6 +161,7 @@ impl StringOp {
match first { match first {
Some('^') => {} // Start of string anchor is already added Some('^') => {} // Start of string anchor is already added
Some('*') => re_string.push_str(r"\*"), Some('*') => re_string.push_str(r"\*"),
Some('$') if !is_end_of_expression(&pattern_chars) => re_string.push_str(r"\$"),
Some('\\') if right.len() == 1 => return Err(ExprError::TrailingBackslash), Some('\\') if right.len() == 1 => return Err(ExprError::TrailingBackslash),
Some(char) => re_string.push(char), Some(char) => re_string.push(char),
None => return Ok(0.into()), None => return Ok(0.into()),
@ -185,23 +186,12 @@ impl StringOp {
_ => re_string.push_str(r"\^"), _ => re_string.push_str(r"\^"),
}, },
'$' => { '$' => {
if let Some('\\') = pattern_chars.peek() { if is_end_of_expression(&pattern_chars) {
// The next character was checked to be a backslash re_string.push(curr);
let backslash = pattern_chars.next().unwrap_or_default(); } else if !curr_is_escaped {
match pattern_chars.peek() {
// End of a capturing group
Some(')') => re_string.push('$'),
// End of an alternative pattern
Some('|') => re_string.push('$'),
_ => re_string.push_str(r"\$"),
}
re_string.push(backslash);
} else if (prev_is_escaped || prev != '\\')
&& pattern_chars.peek().is_some()
{
re_string.push_str(r"\$"); re_string.push_str(r"\$");
} else { } else {
re_string.push('$'); re_string.push(curr);
} }
} }
'\\' if !curr_is_escaped && pattern_chars.peek().is_none() => { '\\' if !curr_is_escaped && pattern_chars.peek().is_none() => {
@ -247,6 +237,25 @@ impl StringOp {
} }
} }
/// Check if regex pattern character iterator is at the end of a regex expression or subexpression
fn is_end_of_expression<I>(pattern_chars: &I) -> bool
where
I: Iterator<Item = char> + Clone,
{
let mut pattern_chars_clone = pattern_chars.clone();
match pattern_chars_clone.next() {
Some('\\') => {
match pattern_chars_clone.next() {
Some(')') // End of a capturing group
| Some('|') => true, // End of an alternative pattern
_ => false,
}
}
None => true, // No characters left
_ => false,
}
}
/// Check for errors in a supplied regular expression /// Check for errors in a supplied regular expression
/// ///
/// GNU coreutils shows messages for invalid regular expressions /// GNU coreutils shows messages for invalid regular expressions

View file

@ -318,6 +318,14 @@ fn test_regex() {
.args(&["a$c", ":", "a$\\c"]) .args(&["a$c", ":", "a$\\c"])
.succeeds() .succeeds()
.stdout_only("3\n"); .stdout_only("3\n");
new_ucmd!()
.args(&["$a", ":", "$a"])
.succeeds()
.stdout_only("2\n");
new_ucmd!()
.args(&["a", ":", "a$\\|b"])
.succeeds()
.stdout_only("1\n");
new_ucmd!() new_ucmd!()
.args(&["^^^^^^^^^", ":", "^^^"]) .args(&["^^^^^^^^^", ":", "^^^"])
.succeeds() .succeeds()
@ -363,6 +371,14 @@ fn test_regex() {
.args(&["abc", ":", "ab[^c]"]) .args(&["abc", ":", "ab[^c]"])
.fails() .fails()
.stdout_only("0\n"); .stdout_only("0\n");
new_ucmd!()
.args(&["$", ":", "$"])
.fails()
.stdout_only("0\n");
new_ucmd!()
.args(&["a$", ":", "a$\\|b"])
.fails()
.stdout_only("0\n");
} }
#[test] #[test]