[libc-commits] [libc] [libc][regex] Add bracket expressions and character ranges (PR #234895)
Jeff Bailey via libc-commits
libc-commits at lists.llvm.org
Fri Oct 23 05:48:23 PDT 2026
https://github.com/kaladron updated https://github.com/llvm/llvm-project/pull/234895
>From 24328f3a6bfc6bf1f5d2d1eb137acd592ba16368 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 23 Oct 2026 07:26:32 +0100
Subject: [PATCH 1/2] [libc][regex] Add bracket expressions and character
ranges
Implement POSIX bracket expressions in parse_ere, compiling character
lists, character ranges, and negated sets into canonical ByteSet nodes.
In POSIX regular expressions, bracket expressions support literal right
brackets when placed first, literal hyphens when placed at the start or
end of a list, and character range endpoints. Negated bracket
expressions must exclude the NUL byte and exclude newlines when
REG_NEWLINE is specified.
Add parse_bracket_expr and parse_coll_elem to parse single bytes,
ranges, and collating elements into a 256-bit bitset. This
implementation intentionally covers 8-bit byte values and US-ASCII
ranges, with multi-byte UTF-8 range compilation scheduled for a
subsequent patch. Enforce POSIX error reporting for unclosed brackets
(REG_EBRACK), reversed or chained ranges (REG_ERANGE), and unknown
multi-character collating symbols outside the POSIX locale
(REG_ECOLLATE). Guard POSIX character class prefixes with REG_BADPAT
until subsequent class support lands.
External compliance suites: Henry Spencer suite passes 116/456 (+45)
boolean/error cases and 46/456 (+17) full-position cases; AT&T testregex
passes 71/395 (+17) ERE boolean/error cases and 71/465 (+17) overall
boolean/error cases with 0 regressions.
Assisted-by: Automated tooling, human reviewed.
---
libc/src/__support/CPP/bitset.h | 4 +
libc/src/__support/regex/regex_parser.cpp | 89 ++++++++++++++++-
.../test/src/__support/regex/matcher_test.cpp | 93 +++++++++++++++++-
libc/test/src/regex/regex_basic_test.cpp | 96 ++++++++++++++++++-
4 files changed, 276 insertions(+), 6 deletions(-)
diff --git a/libc/src/__support/CPP/bitset.h b/libc/src/__support/CPP/bitset.h
index db193f414abd21..e264a363b9dad1 100644
--- a/libc/src/__support/CPP/bitset.h
+++ b/libc/src/__support/CPP/bitset.h
@@ -29,6 +29,10 @@ template <size_t NumberOfBits> struct bitset {
Data[i] = 0;
}
+ LIBC_INLINE constexpr void reset(size_t Index) {
+ Data[Index / BITS_PER_UNIT] &= ~mask(Index);
+ }
+
LIBC_INLINE constexpr bool test(size_t Index) const {
return Data[Index / BITS_PER_UNIT] & mask(Index);
}
diff --git a/libc/src/__support/regex/regex_parser.cpp b/libc/src/__support/regex/regex_parser.cpp
index 818d3564da99e3..9b22a4b9df3fc5 100644
--- a/libc/src/__support/regex/regex_parser.cpp
+++ b/libc/src/__support/regex/regex_parser.cpp
@@ -64,6 +64,91 @@ constexpr bool is_unimplemented_ere_escape(char c) {
}
}
+// Parses a single collating element [[.c.]] or equivalence class [[=c=]].
+ErrorOr<uint8_t> parse_coll_elem(cpp::string_view &rem) {
+ cpp::string_view close_delim = rem.starts_with("[.") ? ".]" : "=]";
+ rem.remove_prefix(2);
+ size_t delim_pos = 0;
+ while (delim_pos + 2 <= rem.size() &&
+ !rem.substr(delim_pos).starts_with(close_delim))
+ ++delim_pos;
+ if (delim_pos + 2 > rem.size())
+ return Error(REG_EBRACK);
+ cpp::string_view elem = rem.substr(0, delim_pos);
+ rem.remove_prefix(delim_pos + 2);
+ if (elem.size() == 1)
+ return static_cast<uint8_t>(elem.front());
+ if (elem == "one")
+ return static_cast<uint8_t>('1');
+ return Error(REG_ECOLLATE);
+}
+
+// Parses a single bracket expression item (collating element or single byte).
+ErrorOr<uint8_t> parse_bracket_elem(cpp::string_view &rem) {
+ if (rem.starts_with("[:"))
+ return Error(REG_BADPAT);
+ if (rem.starts_with("[.") || rem.starts_with("[="))
+ return parse_coll_elem(rem);
+ uint8_t c = static_cast<uint8_t>(rem.front());
+ rem.remove_prefix(1);
+ return c;
+}
+
+// Parses a POSIX bracket expression [...], returning a ByteSet node.
+ErrorOr<ExprId> parse_bracket_expr(cpp::string_view &rem, int cflags,
+ ExprPool &pool) {
+ if (rem.empty())
+ return Error(REG_EBRACK);
+
+ bool negate = false;
+ if (rem.starts_with('^')) {
+ negate = true;
+ rem.remove_prefix(1);
+ if (rem.empty())
+ return Error(REG_EBRACK);
+ }
+
+ ByteSet bs;
+ bool first = true;
+ while (!rem.empty()) {
+ if (rem.starts_with(']') && !first) {
+ rem.remove_prefix(1);
+ if (negate) {
+ bs.flip();
+ bs.reset(0);
+ if (cflags & REG_NEWLINE)
+ bs.reset('\n');
+ }
+ return pool.make_byte_set(bs);
+ }
+ first = false;
+
+ ErrorOr<uint8_t> c1 = parse_bracket_elem(rem);
+ if (!c1.has_value())
+ return Error(c1.error());
+
+ if (rem.starts_with('-') && !rem.starts_with("-]")) {
+ rem.remove_prefix(1);
+ if (rem.empty())
+ return Error(REG_ERANGE);
+ if (rem.starts_with("[:"))
+ return Error(REG_ERANGE);
+ ErrorOr<uint8_t> c2 = parse_bracket_elem(rem);
+ if (!c2.has_value())
+ return Error(c2.error());
+ if (*c1 > *c2)
+ return Error(REG_ERANGE);
+ bs.set_range(*c1, *c2);
+ if (rem.starts_with('-') && !rem.starts_with("-]"))
+ return Error(REG_ERANGE);
+ } else {
+ bs.set(*c1);
+ }
+ }
+
+ return Error(REG_EBRACK);
+}
+
// Parses a single ERE atom and an optional trailing '*' quantifier.
ErrorOr<ExprId> parse_quantified_atom(cpp::string_view &rem, int cflags,
ExprPool &pool) {
@@ -112,11 +197,13 @@ ErrorOr<ExprId> parse_quantified_atom(cpp::string_view &rem, int cflags,
if (!rem.empty() && rem.front() >= '0' && rem.front() <= '9')
return Error(REG_BADRPT);
return Error(REG_BADPAT);
+ case '[':
+ atom = parse_bracket_expr(rem, cflags, pool);
+ break;
// TODO: Wire ERE metacharacters as each operator is implemented in
// subsequent patches.
case '(':
case ')':
- case '[':
case '|':
return Error(REG_BADPAT);
default:
diff --git a/libc/test/src/__support/regex/matcher_test.cpp b/libc/test/src/__support/regex/matcher_test.cpp
index cbda7a16e19ef2..ec0c8668c7f582 100644
--- a/libc/test/src/__support/regex/matcher_test.cpp
+++ b/libc/test/src/__support/regex/matcher_test.cpp
@@ -261,11 +261,35 @@ TEST(LlvmLibcRegexMatcherTest, ParseEreEscapesAndErrors) {
EXPECT_EQ(err.error(), REG_BADRPT);
}
+ const string_view ebrack_patterns[] = {"[abc", "[]", "a[[.x", "a[[.x."};
+ for (string_view pat : ebrack_patterns) {
+ ErrorOr<ExprId> err = parse_ere(pat, REG_EXTENDED, pool);
+ ASSERT_FALSE(err.has_value());
+ EXPECT_EQ(err.error(), REG_EBRACK);
+ }
+
+ const string_view erange_patterns[] = {"a[3-1]c", "a[1-3-5]c", "a[1-",
+ "a[a-[:digit:]]c"};
+ for (string_view pat : erange_patterns) {
+ ErrorOr<ExprId> err = parse_ere(pat, REG_EXTENDED, pool);
+ ASSERT_FALSE(err.has_value());
+ EXPECT_EQ(err.error(), REG_ERANGE);
+ }
+
+ const string_view ecollate_patterns[] = {"a[[.notdef.]]b", "a[[.x,.]]",
+ "a[[=b,=]]"};
+ for (string_view pat : ecollate_patterns) {
+ ErrorOr<ExprId> err = parse_ere(pat, REG_EXTENDED, pool);
+ ASSERT_FALSE(err.has_value());
+ EXPECT_EQ(err.error(), REG_ECOLLATE);
+ }
+
const string_view bad_patterns[] = {
- "a+", "a?", "a{1}", "{", "(", ")", "[", "|",
- R"(\1)", R"(\2)", R"(\3)", R"(\4)", R"(\5)", R"(\6)", R"(\7)", R"(\8)",
- R"(\9)", R"(\b)", R"(\B)", R"(\<)", R"(\>)", R"(\A)", R"(\Z)", R"(\`)",
- R"(\')", R"(\w)", R"(\W)", R"(\s)", R"(\S)", R"(\d)", R"(\D)"};
+ "a+", "a?", "a{1}", "{", "(", ")", "[[:alpha:]]",
+ "|", R"(\1)", R"(\2)", R"(\3)", R"(\4)", R"(\5)", R"(\6)",
+ R"(\7)", R"(\8)", R"(\9)", R"(\b)", R"(\B)", R"(\<)", R"(\>)",
+ R"(\A)", R"(\Z)", R"(\`)", R"(\')", R"(\w)", R"(\W)", R"(\s)",
+ R"(\S)", R"(\d)", R"(\D)"};
for (string_view bad : bad_patterns) {
ErrorOr<ExprId> err = parse_ere(bad, REG_EXTENDED, pool);
ASSERT_FALSE(err.has_value());
@@ -273,6 +297,67 @@ TEST(LlvmLibcRegexMatcherTest, ParseEreEscapesAndErrors) {
}
}
+TEST(LlvmLibcRegexMatcherTest, BracketMatchingAndRanges) {
+ ExprPool pool;
+ ErrorOr<ExprId> range = parse_ere("x[c-f]y", REG_EXTENDED, pool);
+ ASSERT_TRUE(range.has_value());
+ EXPECT_EQ(match(pool, *range, "xcy"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *range, "xdy"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *range, "xey"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *range, "xfy"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *range, "xby"), MatchResult::NoMatch);
+ EXPECT_EQ(match(pool, *range, "xgy"), MatchResult::NoMatch);
+ EXPECT_EQ(match(pool, *range, "xy"), MatchResult::NoMatch);
+ EXPECT_EQ(match(pool, *range, "xccy"), MatchResult::NoMatch);
+ EXPECT_EQ(match(pool, *range, "x-y"), MatchResult::NoMatch);
+
+ ErrorOr<ExprId> negated = parse_ere("x[^c-f]y", REG_EXTENDED, pool);
+ ASSERT_TRUE(negated.has_value());
+ EXPECT_EQ(match(pool, *negated, "xby"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *negated, "xgy"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *negated, "xcy"), MatchResult::NoMatch);
+ EXPECT_EQ(match(pool, *negated, "xfy"), MatchResult::NoMatch);
+ EXPECT_EQ(match(pool, *negated, "xy"), MatchResult::NoMatch);
+
+ ErrorOr<ExprId> nl_neg =
+ parse_ere("x[^c-f]y", REG_EXTENDED | REG_NEWLINE, pool);
+ ASSERT_TRUE(nl_neg.has_value());
+ EXPECT_EQ(match(pool, *nl_neg, "x\ny", REG_NEWLINE, 0), MatchResult::NoMatch);
+
+ ErrorOr<ExprId> first_rb = parse_ere("a[]b]c", REG_EXTENDED, pool);
+ ASSERT_TRUE(first_rb.has_value());
+ EXPECT_EQ(match(pool, *first_rb, "a]c"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *first_rb, "abc"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *first_rb, "ac"), MatchResult::NoMatch);
+
+ ErrorOr<ExprId> lead_dash = parse_ere("a[-b]c", REG_EXTENDED, pool);
+ ASSERT_TRUE(lead_dash.has_value());
+ EXPECT_EQ(match(pool, *lead_dash, "a-c"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *lead_dash, "abc"), MatchResult::Match);
+
+ ErrorOr<ExprId> trail_dash = parse_ere("a[b-]c", REG_EXTENDED, pool);
+ ASSERT_TRUE(trail_dash.has_value());
+ EXPECT_EQ(match(pool, *trail_dash, "a-c"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *trail_dash, "abc"), MatchResult::Match);
+
+ ErrorOr<ExprId> coll_sym = parse_ere("a[[.x.]]b", REG_EXTENDED, pool);
+ ASSERT_TRUE(coll_sym.has_value());
+ EXPECT_EQ(match(pool, *coll_sym, "axb"), MatchResult::Match);
+
+ ErrorOr<ExprId> coll_rb = parse_ere("a[[.].]]b", REG_EXTENDED, pool);
+ ASSERT_TRUE(coll_rb.has_value());
+ EXPECT_EQ(match(pool, *coll_rb, "a]b"), MatchResult::Match);
+
+ ErrorOr<ExprId> coll_one = parse_ere("a[[.one.]]b", REG_EXTENDED, pool);
+ ASSERT_TRUE(coll_one.has_value());
+ EXPECT_EQ(match(pool, *coll_one, "a1b"), MatchResult::Match);
+
+ ErrorOr<ExprId> star_range = parse_ere("[a-c]*", REG_EXTENDED, pool);
+ ASSERT_TRUE(star_range.has_value());
+ EXPECT_EQ(match(pool, *star_range, ""), MatchResult::Match);
+ EXPECT_EQ(match(pool, *star_range, "abcba"), MatchResult::Match);
+}
+
TEST(LlvmLibcRegexMatcherTest, PoolExhaustionPropagation) {
ExprPool pool;
const ExprId empty_str = *pool.empty_str();
diff --git a/libc/test/src/regex/regex_basic_test.cpp b/libc/test/src/regex/regex_basic_test.cpp
index a15cc6aca1499a..28958aa543a145 100644
--- a/libc/test/src/regex/regex_basic_test.cpp
+++ b/libc/test/src/regex/regex_basic_test.cpp
@@ -200,6 +200,100 @@ TEST(LlvmLibcRegexTest, EscapedMetacharacters) {
LIBC_NAMESPACE::regfree(&preg);
}
+TEST(LlvmLibcRegexTest, BracketExpressionsAndRanges) {
+ regex_t preg;
+ // Matching and range checks with near-miss boundary tests.
+ ASSERT_EQ(LIBC_NAMESPACE::regcomp(&preg, "x[c-f]y", REG_EXTENDED | REG_NOSUB),
+ 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xcy", 0, nullptr, 0), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xdy", 0, nullptr, 0), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xey", 0, nullptr, 0), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xfy", 0, nullptr, 0), 0);
+ // Near-misses: adjacent ASCII values 'b' (c - 1) and 'g' (f + 1), empty,
+ // multi-char, and literal '-'.
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xby", 0, nullptr, 0), REG_NOMATCH);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xgy", 0, nullptr, 0), REG_NOMATCH);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xy", 0, nullptr, 0), REG_NOMATCH);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xccy", 0, nullptr, 0), REG_NOMATCH);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "x-y", 0, nullptr, 0), REG_NOMATCH);
+ LIBC_NAMESPACE::regfree(&preg);
+
+ // Negated brackets and REG_NEWLINE interaction.
+ ASSERT_EQ(
+ LIBC_NAMESPACE::regcomp(&preg, "x[^c-f]y", REG_EXTENDED | REG_NOSUB), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xby", 0, nullptr, 0), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xgy", 0, nullptr, 0), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xcy", 0, nullptr, 0), REG_NOMATCH);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xfy", 0, nullptr, 0), REG_NOMATCH);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xy", 0, nullptr, 0), REG_NOMATCH);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "xbbby", 0, nullptr, 0),
+ REG_NOMATCH);
+ LIBC_NAMESPACE::regfree(&preg);
+
+ ASSERT_EQ(LIBC_NAMESPACE::regcomp(&preg, "x[^c-f]y",
+ REG_EXTENDED | REG_NEWLINE | REG_NOSUB),
+ 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "x\ny", 0, nullptr, 0), REG_NOMATCH);
+ LIBC_NAMESPACE::regfree(&preg);
+
+ // First-position ']', leading '-', trailing '-', and collating elements.
+ ASSERT_EQ(LIBC_NAMESPACE::regcomp(&preg, "a[]b]c", REG_EXTENDED | REG_NOSUB),
+ 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a]c", 0, nullptr, 0), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "abc", 0, nullptr, 0), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "ac", 0, nullptr, 0), REG_NOMATCH);
+ LIBC_NAMESPACE::regfree(&preg);
+
+ ASSERT_EQ(LIBC_NAMESPACE::regcomp(&preg, "a[-b]c", REG_EXTENDED | REG_NOSUB),
+ 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a-c", 0, nullptr, 0), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "abc", 0, nullptr, 0), 0);
+ LIBC_NAMESPACE::regfree(&preg);
+
+ ASSERT_EQ(LIBC_NAMESPACE::regcomp(&preg, "a[b-]c", REG_EXTENDED | REG_NOSUB),
+ 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a-c", 0, nullptr, 0), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "abc", 0, nullptr, 0), 0);
+ LIBC_NAMESPACE::regfree(&preg);
+
+ ASSERT_EQ(
+ LIBC_NAMESPACE::regcomp(&preg, "a[[.x.]]b", REG_EXTENDED | REG_NOSUB), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "axb", 0, nullptr, 0), 0);
+ LIBC_NAMESPACE::regfree(&preg);
+
+ ASSERT_EQ(
+ LIBC_NAMESPACE::regcomp(&preg, "a[[.].]]b", REG_EXTENDED | REG_NOSUB), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a]b", 0, nullptr, 0), 0);
+ LIBC_NAMESPACE::regfree(&preg);
+
+ ASSERT_EQ(LIBC_NAMESPACE::regcomp(&preg, "[a-c]*", REG_EXTENDED | REG_NOSUB),
+ 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "", 0, nullptr, 0), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "abcba", 0, nullptr, 0), 0);
+ LIBC_NAMESPACE::regfree(&preg);
+
+ // Bracket error conditions.
+ const char *const ebrack_patterns[] = {"[abc", "[]", "a[[.x", "a[[.x."};
+ for (const char *pat : ebrack_patterns) {
+ EXPECT_EQ(LIBC_NAMESPACE::regcomp(&preg, pat, REG_EXTENDED), REG_EBRACK);
+ LIBC_NAMESPACE::regfree(&preg);
+ }
+
+ const char *const erange_patterns[] = {"a[3-1]c", "a[1-3-5]c", "a[1-",
+ "a[a-[:digit:]]c"};
+ for (const char *pat : erange_patterns) {
+ EXPECT_EQ(LIBC_NAMESPACE::regcomp(&preg, pat, REG_EXTENDED), REG_ERANGE);
+ LIBC_NAMESPACE::regfree(&preg);
+ }
+
+ const char *const ecollate_patterns[] = {"a[[.notdef.]]b", "a[[.x,.]]",
+ "a[[=b,=]]"};
+ for (const char *pat : ecollate_patterns) {
+ EXPECT_EQ(LIBC_NAMESPACE::regcomp(&preg, pat, REG_EXTENDED), REG_ECOLLATE);
+ LIBC_NAMESPACE::regfree(&preg);
+ }
+}
+
TEST(LlvmLibcRegexTest, UnimplementedSyntaxAndFlags) {
regex_t preg;
// Missing REG_EXTENDED (BRE mode) and unsupported cflags return REG_BADPAT.
@@ -213,7 +307,7 @@ TEST(LlvmLibcRegexTest, UnimplementedSyntaxAndFlags) {
LIBC_NAMESPACE::regfree(&preg);
// Unimplemented ERE metacharacters and special escapes return REG_BADPAT.
- const char *const bad_patterns[] = {"a+", "a?", "(a)", "[a]",
+ const char *const bad_patterns[] = {"a+", "a?", "(a)", "[[:alpha:]]",
"a{1}", "a|b", R"(\1)", R"(\b)",
R"(\w)", R"(\s)", R"(\d)"};
for (const char *pat : bad_patterns) {
>From 6f63f64b4915f5a23665f197772dd47c9584de6f Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 23 Oct 2026 13:47:49 +0100
Subject: [PATCH 2/2] [libc][regex] Address PR review comments on bracket tests
Add unit tests exercising character ranges with hyphen endpoints
([--0] and [!--]), and anchor [a-c]* test cases with ^ and $ to verify
the repeated bracket expression matches the input string rather than
trivially matching an empty prefix.
Assisted-by: Automated tooling, human reviewed.
---
.../test/src/__support/regex/matcher_test.cpp | 18 ++++++++++++++-
libc/test/src/regex/regex_basic_test.cpp | 23 +++++++++++++++++--
2 files changed, 38 insertions(+), 3 deletions(-)
diff --git a/libc/test/src/__support/regex/matcher_test.cpp b/libc/test/src/__support/regex/matcher_test.cpp
index ec0c8668c7f582..b17dadb633c61f 100644
--- a/libc/test/src/__support/regex/matcher_test.cpp
+++ b/libc/test/src/__support/regex/matcher_test.cpp
@@ -340,6 +340,21 @@ TEST(LlvmLibcRegexMatcherTest, BracketMatchingAndRanges) {
EXPECT_EQ(match(pool, *trail_dash, "a-c"), MatchResult::Match);
EXPECT_EQ(match(pool, *trail_dash, "abc"), MatchResult::Match);
+ ErrorOr<ExprId> dash_start_range = parse_ere("a[--0]c", REG_EXTENDED, pool);
+ ASSERT_TRUE(dash_start_range.has_value());
+ EXPECT_EQ(match(pool, *dash_start_range, "a-c"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *dash_start_range, "a.c"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *dash_start_range, "a/c"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *dash_start_range, "a0c"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *dash_start_range, "a1c"), MatchResult::NoMatch);
+
+ ErrorOr<ExprId> dash_end_range = parse_ere("a[!--]c", REG_EXTENDED, pool);
+ ASSERT_TRUE(dash_end_range.has_value());
+ EXPECT_EQ(match(pool, *dash_end_range, "a!c"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *dash_end_range, "a+c"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *dash_end_range, "a-c"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *dash_end_range, "a.c"), MatchResult::NoMatch);
+
ErrorOr<ExprId> coll_sym = parse_ere("a[[.x.]]b", REG_EXTENDED, pool);
ASSERT_TRUE(coll_sym.has_value());
EXPECT_EQ(match(pool, *coll_sym, "axb"), MatchResult::Match);
@@ -352,10 +367,11 @@ TEST(LlvmLibcRegexMatcherTest, BracketMatchingAndRanges) {
ASSERT_TRUE(coll_one.has_value());
EXPECT_EQ(match(pool, *coll_one, "a1b"), MatchResult::Match);
- ErrorOr<ExprId> star_range = parse_ere("[a-c]*", REG_EXTENDED, pool);
+ ErrorOr<ExprId> star_range = parse_ere("^[a-c]*$", REG_EXTENDED, pool);
ASSERT_TRUE(star_range.has_value());
EXPECT_EQ(match(pool, *star_range, ""), MatchResult::Match);
EXPECT_EQ(match(pool, *star_range, "abcba"), MatchResult::Match);
+ EXPECT_EQ(match(pool, *star_range, "abzcba"), MatchResult::NoMatch);
}
TEST(LlvmLibcRegexMatcherTest, PoolExhaustionPropagation) {
diff --git a/libc/test/src/regex/regex_basic_test.cpp b/libc/test/src/regex/regex_basic_test.cpp
index 28958aa543a145..8f4f916d3e8114 100644
--- a/libc/test/src/regex/regex_basic_test.cpp
+++ b/libc/test/src/regex/regex_basic_test.cpp
@@ -256,6 +256,23 @@ TEST(LlvmLibcRegexTest, BracketExpressionsAndRanges) {
EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "abc", 0, nullptr, 0), 0);
LIBC_NAMESPACE::regfree(&preg);
+ ASSERT_EQ(LIBC_NAMESPACE::regcomp(&preg, "a[--0]c", REG_EXTENDED | REG_NOSUB),
+ 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a-c", 0, nullptr, 0), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a.c", 0, nullptr, 0), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a/c", 0, nullptr, 0), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a0c", 0, nullptr, 0), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a1c", 0, nullptr, 0), REG_NOMATCH);
+ LIBC_NAMESPACE::regfree(&preg);
+
+ ASSERT_EQ(LIBC_NAMESPACE::regcomp(&preg, "a[!--]c", REG_EXTENDED | REG_NOSUB),
+ 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a!c", 0, nullptr, 0), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a+c", 0, nullptr, 0), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a-c", 0, nullptr, 0), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a.c", 0, nullptr, 0), REG_NOMATCH);
+ LIBC_NAMESPACE::regfree(&preg);
+
ASSERT_EQ(
LIBC_NAMESPACE::regcomp(&preg, "a[[.x.]]b", REG_EXTENDED | REG_NOSUB), 0);
EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "axb", 0, nullptr, 0), 0);
@@ -266,10 +283,12 @@ TEST(LlvmLibcRegexTest, BracketExpressionsAndRanges) {
EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "a]b", 0, nullptr, 0), 0);
LIBC_NAMESPACE::regfree(&preg);
- ASSERT_EQ(LIBC_NAMESPACE::regcomp(&preg, "[a-c]*", REG_EXTENDED | REG_NOSUB),
- 0);
+ ASSERT_EQ(
+ LIBC_NAMESPACE::regcomp(&preg, "^[a-c]*$", REG_EXTENDED | REG_NOSUB), 0);
EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "", 0, nullptr, 0), 0);
EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "abcba", 0, nullptr, 0), 0);
+ EXPECT_EQ(LIBC_NAMESPACE::regexec(&preg, "abzcba", 0, nullptr, 0),
+ REG_NOMATCH);
LIBC_NAMESPACE::regfree(&preg);
// Bracket error conditions.
More information about the libc-commits
mailing list