Skip to content

Commit 96e84a2

Browse files
frendsickcakebaker
authored andcommitted
expr: Fix parsing negated character classes "[^a]" (uutils#7884)
* expr: Fix regex escape logic We have to track if the previous character was already escaped to determine if the '\' character should be interpreted as an escape character. * expr: Fix parsing caret (^) as character class negation token * expr: Add tests for parsing carets in regex * expr: Add missing semicolon * expr: Simplify boolean assignment Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com> --------- Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>
1 parent c7a7397 commit 96e84a2

File tree

2 files changed

+32
-2
lines changed

2 files changed

+32
-2
lines changed

src/uu/expr/src/syntax_tree.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,19 @@ impl StringOp {
166166
};
167167

168168
// Handle the rest of the input pattern.
169-
// Escape characters that should be handled literally within the pattern.
169+
// Escaped previous character should not affect the current.
170170
let mut prev = first.unwrap_or_default();
171+
let mut prev_is_escaped = false;
171172
for curr in pattern_chars {
172173
match curr {
173-
'^' if prev != '\\' => re_string.push_str(r"\^"),
174+
// Carets are interpreted literally, unless used as character class negation "[^a]"
175+
'^' if prev_is_escaped || !matches!(prev, '\\' | '[') => {
176+
re_string.push_str(r"\^");
177+
}
174178
char => re_string.push(char),
175179
}
180+
181+
prev_is_escaped = prev == '\\' && !prev_is_escaped;
176182
prev = curr;
177183
}
178184

tests/by-util/test_expr.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,26 @@ fn test_regex() {
302302
.args(&["^^^^^^^^^", ":", "^^^"])
303303
.succeeds()
304304
.stdout_only("2\n");
305+
new_ucmd!()
306+
.args(&["ab[^c]", ":", "ab[^c]"])
307+
.succeeds()
308+
.stdout_only("3\n"); // Matches "ab["
309+
new_ucmd!()
310+
.args(&["ab[^c]", ":", "ab\\[^c]"])
311+
.succeeds()
312+
.stdout_only("6\n");
313+
new_ucmd!()
314+
.args(&["[^a]", ":", "\\[^a]"])
315+
.succeeds()
316+
.stdout_only("4\n");
317+
new_ucmd!()
318+
.args(&["\\a", ":", "\\\\[^^]"])
319+
.succeeds()
320+
.stdout_only("2\n");
321+
new_ucmd!()
322+
.args(&["^a", ":", "^^[^^]"])
323+
.succeeds()
324+
.stdout_only("2\n");
305325
new_ucmd!()
306326
.args(&["-5", ":", "-\\{0,1\\}[0-9]*$"])
307327
.succeeds()
@@ -319,6 +339,10 @@ fn test_regex() {
319339
.args(&["^abc", ":", "^abc"])
320340
.fails()
321341
.stdout_only("0\n");
342+
new_ucmd!()
343+
.args(&["abc", ":", "ab[^c]"])
344+
.fails()
345+
.stdout_only("0\n");
322346
}
323347

324348
#[test]

0 commit comments

Comments
 (0)