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
40 changes: 33 additions & 7 deletions src/uu/expr/src/syntax_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ impl StringOp {
re_string.push('^');

// Handle first character from the input pattern
let mut pattern_chars = right.chars();
let mut pattern_chars = right.chars().peekable();
let first = pattern_chars.next();
match first {
Some('^') => {} // Start of string anchor is already added
Expand All @@ -166,16 +166,42 @@ impl StringOp {
};

// Handle the rest of the input pattern.
// Escaped previous character should not affect the current.
let mut prev = first.unwrap_or_default();
let mut prev_is_escaped = false;
for curr in pattern_chars {
while let Some(curr) = pattern_chars.next() {
match curr {
// Carets are interpreted literally, unless used as character class negation "[^a]"
'^' if prev_is_escaped || !matches!(prev, '\\' | '[') => {
re_string.push_str(r"\^");
'^' => match (prev, prev_is_escaped) {
// Start of a capturing group
('(', true)
// Start of an alternative pattern
| ('|', true)
// Character class negation "[^a]"
| ('[', false)
// Explicitly escaped caret
| ('\\', false) => re_string.push(curr),
_ => re_string.push_str(r"\^"),
},
'$' => {
if let Some('\\') = pattern_chars.peek() {
// The next character was checked to be a backslash
let backslash = pattern_chars.next().unwrap_or_default();
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"\$");
} else {
re_string.push('$');
}
}
char => re_string.push(char),
_ => re_string.push(curr),
}

prev_is_escaped = prev == '\\' && !prev_is_escaped;
Expand Down
21 changes: 20 additions & 1 deletion tests/by-util/test_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,10 +282,26 @@ fn test_regex() {
.args(&["a^b", ":", "a\\^b"])
.succeeds()
.stdout_only("3\n");
new_ucmd!()
.args(&["b", ":", "a\\|^b"])
.succeeds()
.stdout_only("1\n");
new_ucmd!()
.args(&["ab", ":", "\\(^a\\)b"])
.succeeds()
.stdout_only("a\n");
new_ucmd!()
.args(&["a$b", ":", "a\\$b"])
.succeeds()
.stdout_only("3\n");
new_ucmd!()
.args(&["a", ":", "a$\\|b"])
.succeeds()
.stdout_only("1\n");
new_ucmd!()
.args(&["ab", ":", "a\\(b$\\)"])
.succeeds()
.stdout_only("b\n");
new_ucmd!()
.args(&["abc", ":", "^abc"])
.succeeds()
Expand All @@ -298,6 +314,10 @@ fn test_regex() {
.args(&["b^$ic", ":", "b^\\$ic"])
.succeeds()
.stdout_only("5\n");
new_ucmd!()
.args(&["a$c", ":", "a$\\c"])
.succeeds()
.stdout_only("3\n");
new_ucmd!()
.args(&["^^^^^^^^^", ":", "^^^"])
.succeeds()
Expand Down Expand Up @@ -766,7 +786,6 @@ mod gnu_expr {
.stdout_only("3\n");
}

#[ignore]
#[test]
fn test_bre11() {
new_ucmd!()
Expand Down
Loading