diff --git a/Sources/_StringProcessing/LiteralPrinter.swift b/Sources/_StringProcessing/LiteralPrinter.swift index df10bb903..2c8aa021d 100644 --- a/Sources/_StringProcessing/LiteralPrinter.swift +++ b/Sources/_StringProcessing/LiteralPrinter.swift @@ -444,7 +444,17 @@ extension String { extension UnicodeScalar { var escapedString: String { - "\\u{" + String(value, radix: 16) + "}" + switch self { + case "\n": return #"\n"# + case "\r": return #"\r"# + case "\t": return #"\t"# + default: + let code = String(value, radix: 16, uppercase: true) + let prefix = code.count <= 4 + ? #"\u"# + String(repeating: "0", count: 4 - code.count) + : #"\U"# + String(repeating: "0", count: 8 - code.count) + return prefix + code + } } } diff --git a/Tests/RegexTests/LiteralPrinterTests.swift b/Tests/RegexTests/LiteralPrinterTests.swift index eb7ee37d9..bbe63845a 100644 --- a/Tests/RegexTests/LiteralPrinterTests.swift +++ b/Tests/RegexTests/LiteralPrinterTests.swift @@ -14,17 +14,35 @@ import XCTest import _StringProcessing import RegexBuilder +@available(SwiftStdlib 5.11, *) +fileprivate func _literalTest( + _ regex: Regex, + expected: String?, + file: StaticString = #filePath, + line: UInt = #line +) { + XCTAssertEqual(regex._literalPattern, expected, file: file, line: line) + if let expected { + let remadeRegex = try? Regex(expected) + XCTAssertEqual(expected, remadeRegex?._literalPattern, file: file, line: line) + } +} + @available(SwiftStdlib 5.11, *) extension RegexTests { func testPrintableRegex() throws { let regexString = #"([a-fGH1-9[^\D]]+)?b*cd(e.+)\2\w\S+?"# - let regex = try! Regex(regexString) - let pattern = try XCTUnwrap(regex._literalPattern) + let regex = try Regex(regexString) // Note: This is true for this particular regex, but not all regexes - XCTAssertEqual(regexString, pattern) + _literalTest(regex, expected: regexString) let printableRegex = try XCTUnwrap(PrintableRegex(regex)) - XCTAssertEqual("\(printableRegex)", pattern) + XCTAssertEqual("\(printableRegex)", regexString) + } + + func testUnicodeEscapes() throws { + let regex = #/\r\n\t cafe\u{301} \u{1D11E}/# + _literalTest(regex, expected: #"\r\n\t cafe\u0301 \U0001D11E"#) } func testPrintableDSLRegex() throws { @@ -39,8 +57,7 @@ extension RegexTests { }.dotMatchesNewlines() Optionally("c") }.ignoresCase() - let pattern = try XCTUnwrap(regex._literalPattern) - XCTAssertEqual("(?i:(?:aaa)+?(?s:(?:bbb)*|d+|e{3,})c?)", pattern) + _literalTest(regex, expected: "(?i:(?:aaa)+?(?s:(?:bbb)*|d+|e{3,})c?)") let nonPrintableRegex = Regex { OneOrMore("a") @@ -49,7 +66,7 @@ extension RegexTests { } transform: { Int($0)! } Optionally("b") } - XCTAssertNil(nonPrintableRegex._literalPattern) + _literalTest(nonPrintableRegex, expected: nil) } }