[llvm] [Support] Allow \n, \t, \x in regexes (PR #223024)
Harald van Dijk via llvm-commits
llvm-commits at lists.llvm.org
Fri Sep 11 14:01:41 PDT 2026
https://github.com/hvdijk updated https://github.com/llvm/llvm-project/pull/223024
>From a37715527a6d156911abc5a14219e5d2b355c859 Mon Sep 17 00:00:00 2001
From: Harald van Dijk <hdijk at accesssoftek.com>
Date: Fri, 11 Sep 2026 20:35:27 +0100
Subject: [PATCH 1/2] [Support] Allow \n, \t, \x in regexes
\n and \t were already permitted in Regex::sub, but not in the regex
pattern itself. This commit allows them there too.
\x was not supported at all. This commit allows it both in regex
patterns and in Regex::sub.
The goal is to allow \x to be used in FileCheck patterns and to that
end, a test is also included that this works in FileCheck as expected.
---
llvm/lib/Support/Regex.cpp | 12 ++++++++
llvm/lib/Support/regcomp.c | 46 +++++++++++++++++++++++-----
llvm/test/FileCheck/escape.txt | 5 +++
llvm/unittests/Support/RegexTest.cpp | 35 +++++++++++++++++++++
4 files changed, 91 insertions(+), 7 deletions(-)
create mode 100644 llvm/test/FileCheck/escape.txt
diff --git a/llvm/lib/Support/Regex.cpp b/llvm/lib/Support/Regex.cpp
index 5a96f1974341d..039108be9ad89 100644
--- a/llvm/lib/Support/Regex.cpp
+++ b/llvm/lib/Support/Regex.cpp
@@ -12,6 +12,7 @@
#include "llvm/Support/Regex.h"
#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "regex_impl.h"
@@ -202,6 +203,17 @@ std::string Regex::sub(StringRef Repl, StringRef String,
Repl = Repl.substr(1);
break;
+ // Hex character escapes.
+ case 'x':
+ if (Repl.size() >= 3 && isHexDigit(Repl[1]) && isHexDigit(Repl[2])) {
+ Res += char(hexDigitValue(Repl[1]) * 16 + hexDigitValue(Repl[2]));
+ Repl = Repl.substr(3);
+ } else {
+ Res += Repl[0];
+ Repl = Repl.substr(1);
+ }
+ break;
+
// Decimal escapes are backreferences.
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9': {
diff --git a/llvm/lib/Support/regcomp.c b/llvm/lib/Support/regcomp.c
index 6838b96a0e5df..92dfff397056a 100644
--- a/llvm/lib/Support/regcomp.c
+++ b/llvm/lib/Support/regcomp.c
@@ -211,6 +211,7 @@ static char p_b_symbol(struct parse *);
static char p_b_coll_elem(struct parse *, int);
static char othercase(int);
static void bothcases(struct parse *, int);
+static void uncased(struct parse *, int);
static void ordinary(struct parse *, int);
static void nonnewline(struct parse *);
static void repeat(struct parse *, sopno, int, int);
@@ -519,6 +520,31 @@ static void p_ere_exp(struct parse *p) {
backrefnum = backrefnum * 10 + c - '0';
}
MUSTEAT('}', REG_BADRPT);
+ } else if (c == 'n') {
+ ordinary(p, '\n');
+ break;
+ } else if (c == 't') {
+ ordinary(p, '\t');
+ break;
+ } else if (c == 'x') {
+ /* Support \xAA hexadecimal escape sequences. \x must be followed by
+ * exactly two hex digits, otherwise it is interpreted literally.
+ */
+ char hexstr[3] = {0};
+ char *hexp;
+ int val;
+ if (MORE2()) {
+ hexstr[0] = PEEK();
+ hexstr[1] = PEEK2();
+ val = strtol(hexstr, &hexp, 16);
+ if (*hexp == '\0') {
+ NEXT2();
+ uncased(p, (char)val);
+ break;
+ }
+ }
+ ordinary(p, 'x');
+ break;
} else {
/* Other chars are simply themselves when escaped with a backslash.
*/
@@ -1063,18 +1089,24 @@ static void bothcases(struct parse *p, int ch) {
}
/*
- - ordinary - emit an ordinary character
+ - uncased - emit an ordinary character that is never treated as cased
*/
-static void ordinary(struct parse *p, int ch) {
+static void uncased(struct parse *p, int ch) {
cat_t *cap = p->g->categories;
+ EMIT(OCHAR, (uch)ch);
+ if (cap[ch] == 0)
+ cap[ch] = p->g->ncategories++;
+}
+
+/*
+ - ordinary - emit an ordinary character
+ */
+static void ordinary(struct parse *p, int ch) {
if ((p->g->cflags & REG_ICASE) && isalpha((uch)ch) && othercase(ch) != ch)
bothcases(p, ch);
- else {
- EMIT(OCHAR, (uch)ch);
- if (cap[ch] == 0)
- cap[ch] = p->g->ncategories++;
- }
+ else
+ uncased(p, ch);
}
/*
diff --git a/llvm/test/FileCheck/escape.txt b/llvm/test/FileCheck/escape.txt
new file mode 100644
index 0000000000000..6f04e89aab56b
--- /dev/null
+++ b/llvm/test/FileCheck/escape.txt
@@ -0,0 +1,5 @@
+; RUN: printf '\t\n\xC2\xA1\n' > %t.1
+; RUN: FileCheck --strict-whitespace --match-full-lines %s < %t.1
+
+CHECK:{{\t}}
+CHECK:{{\xC2\xA1}}
diff --git a/llvm/unittests/Support/RegexTest.cpp b/llvm/unittests/Support/RegexTest.cpp
index 18a78b731f3e9..c21e294854b7c 100644
--- a/llvm/unittests/Support/RegexTest.cpp
+++ b/llvm/unittests/Support/RegexTest.cpp
@@ -68,6 +68,37 @@ TEST_F(RegexTest, EmptyPattern) {
EXPECT_FALSE(r.match(""));
}
+TEST_F(RegexTest, Escapes) {
+ Regex r1("\\n");
+ EXPECT_TRUE(r1.match("\n"));
+ EXPECT_FALSE(r1.match("n"));
+ EXPECT_FALSE(r1.match("\\n"));
+
+ Regex r2("\\t");
+ EXPECT_TRUE(r2.match("\t"));
+ EXPECT_FALSE(r2.match("t"));
+ EXPECT_FALSE(r2.match("\\t"));
+
+ Regex r3("\\x40");
+ EXPECT_TRUE(r3.match("\x40"));
+ EXPECT_FALSE(r3.match("x40"));
+ EXPECT_FALSE(r3.match("\\x40"));
+
+ Regex r4("A\\x41", Regex::IgnoreCase);
+ EXPECT_TRUE(r4.match("AA"));
+ EXPECT_FALSE(r4.match("Aa"));
+ EXPECT_TRUE(r4.match("aA"));
+ EXPECT_FALSE(r4.match("aa"));
+
+ Regex r5("\\q");
+ EXPECT_TRUE(r5.match("q"));
+ EXPECT_FALSE(r5.match("\\q"));
+
+ Regex r6("\\xjq");
+ EXPECT_TRUE(r6.match("xjq"));
+ EXPECT_FALSE(r6.match("\\xjq"));
+}
+
TEST_F(RegexTest, Backreferences) {
Regex r1("([a-z]+)_\\1");
SmallVector<StringRef, 4> Matches;
@@ -139,6 +170,10 @@ TEST_F(RegexTest, Substitution) {
EXPECT_EQ("", Error);
EXPECT_EQ("a\tber", Regex("[0-9]+").sub("\\t", "a1234ber", &Error));
EXPECT_EQ("", Error);
+ EXPECT_EQ("a\100ber", Regex("[0-9]+").sub("\\x40", "a1234ber", &Error));
+ EXPECT_EQ("", Error);
+ EXPECT_EQ("axjqber", Regex("[0-9]+").sub("\\xjq", "a1234ber", &Error));
+ EXPECT_EQ("", Error);
EXPECT_EQ("ajber", Regex("[0-9]+").sub("\\j", "a1234ber", &Error));
EXPECT_EQ("", Error);
>From fe5fc9ada06dba56913aff428881319a440781ca Mon Sep 17 00:00:00 2001
From: Harald van Dijk <hdijk at accesssoftek.com>
Date: Fri, 11 Sep 2026 20:42:23 +0100
Subject: [PATCH 2/2] Apply clang-format suggestion
---
llvm/lib/Support/Regex.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/lib/Support/Regex.cpp b/llvm/lib/Support/Regex.cpp
index 039108be9ad89..045a9d17116f3 100644
--- a/llvm/lib/Support/Regex.cpp
+++ b/llvm/lib/Support/Regex.cpp
@@ -11,11 +11,11 @@
//===----------------------------------------------------------------------===//
#include "llvm/Support/Regex.h"
+#include "regex_impl.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
-#include "regex_impl.h"
#include <cassert>
#include <string>
More information about the llvm-commits
mailing list