From 8b838b73d43de0c158001107ca0e9ffed2d6e2d8 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 12 May 2022 15:09:11 -0700 Subject: [PATCH 1/8] Wrap character classes around One --- .../_StringProcessing/PrintAsPattern.swift | 240 +++++++++++++----- 1 file changed, 181 insertions(+), 59 deletions(-) diff --git a/Sources/_StringProcessing/PrintAsPattern.swift b/Sources/_StringProcessing/PrintAsPattern.swift index 5d8991d00..dd0d34f44 100644 --- a/Sources/_StringProcessing/PrintAsPattern.swift +++ b/Sources/_StringProcessing/PrintAsPattern.swift @@ -143,6 +143,51 @@ extension PrettyPrinter { blockName = "\(amount)" } + // Special case single child character classes for repetition nodes. + // This lets us do something like the following: + // + // OneOrMore(.digit) + // vs + // OneOrMore { + // One(.digit) + // } + // + func printSimpleCCC( + _ ccc: DSLTree.CustomCharacterClass + ) { + indent() + + if kind != ".eager" { + blockName.removeLast() + output("\(blockName), ") + } else { + output("\(blockName)(") + } + + printAsPattern(ccc, wrap: false, terminateLine: false) + output(")") + terminateLine() + } + + switch child { + case let .customCharacterClass(ccc): + if ccc.isSimplePrint { + printSimpleCCC(ccc) + return + } + + break + case let .convertedRegexLiteral(.customCharacterClass(ccc), _): + if ccc.isSimplePrint { + printSimpleCCC(ccc) + return + } + + break + default: + break + } + printBlock(blockName) { printer in printer.printAsPattern(convertedFromAST: child) } @@ -191,10 +236,10 @@ extension PrettyPrinter { print("/* TODO: absent function */") } } - - // TODO: Some way to integrate this with conversion... + mutating func printAsPattern( _ ccc: DSLTree.CustomCharacterClass, + wrap: Bool = true, terminateLine: Bool = true ) { if ccc.hasUnprintableProperty { @@ -206,11 +251,10 @@ extension PrettyPrinter { if ccc.isInverted { printIndented { printer in printer.indent() + printer.output(".inverted") if terminateLine { - printer.print(".inverted") - } else { - printer.output(".inverted") + printer.terminateLine() } } } @@ -219,7 +263,7 @@ extension PrettyPrinter { // If we only have 1 member, then we can emit it without the extra // CharacterClass initialization if ccc.members.count == 1 { - printAsPattern(ccc.members[0]) + printAsPattern(ccc.members[0], wrap: wrap) if terminateLine { self.terminateLine() @@ -283,12 +327,20 @@ extension PrettyPrinter { // Also in the same vein, if we have a few atom members but no // nonAtomMembers, then we can emit a single .anyOf(...) for them. if !charMembers.isEmpty, nonCharMembers.isEmpty { - if terminateLine { - print(".anyOf(\(charMembers._quoted))") + let anyOf = ".anyOf(\(charMembers._quoted))" + + indent() + + if wrap { + output("One(\(anyOf))") } else { - indent() - output(".anyOf(\(charMembers._quoted))") + output(anyOf) } + + if terminateLine { + self.terminateLine() + } + return } @@ -308,7 +360,7 @@ extension PrettyPrinter { } for (i, member) in nonCharMembers.enumerated() { - printer.printAsPattern(member) + printer.printAsPattern(member, wrap: false) if i != nonCharMembers.count - 1 { printer.output(",") @@ -318,17 +370,18 @@ extension PrettyPrinter { } } + indent() + output(")") + if terminateLine { - print(")") - } else { - indent() - output(")") + self.terminateLine() } } // TODO: Some way to integrate this with conversion... mutating func printAsPattern( - _ member: DSLTree.CustomCharacterClass.Member + _ member: DSLTree.CustomCharacterClass.Member, + wrap: Bool = true ) { switch member { case let .custom(ccc): @@ -348,17 +401,40 @@ extension PrettyPrinter { indent() switch a { case let .char(c): - output(".anyOf(\(String(c)._quoted))") + + if wrap { + output("One(.anyOf(\(String(c)._quoted)))") + } else { + output(".anyOf(\(String(c)._quoted))") + } + case let .scalar(s): - output(".anyOf(\"\\u{\(String(s.value, radix: 16))}\")") + + if wrap { + output("One(.anyOf(\"\\u{\(String(s.value, radix: 16, uppercase: true))}\"))") + } else { + output(".anyOf(\"\\u{\(String(s.value, radix: 16, uppercase: true))}\")") + } + case let .unconverted(a): - output(a.ast._patternBase) + let base = a.ast._patternBase + + if base.canBeWrapped, wrap { + output("One(\(base.0))") + } else { + output(base.0) + } default: print(" // TODO: Atom \(a)") } case .quotedLiteral(let s): - output(".anyOf(\(s._quoted))") + + if wrap { + output("One(.anyOf(\(s._quoted)))") + } else { + output(".anyOf(\(s._quoted))") + } case .trivia(_): // We never print trivia @@ -662,118 +738,118 @@ extension AST.Atom { /// caller, but we might want to be parameterized at that point. /// /// TODO: Some way to integrate this with conversion... - var _patternBase: String { + var _patternBase: (String, canBeWrapped: Bool) { if let anchor = self.assertionKind { - return anchor._patternBase + return (anchor._patternBase, false) } if isUnprintableAtom { - return _regexBase + return (_regexBase, false) } return _dslBase } - var _dslBase: String { + var _dslBase: (String, canBeWrapped: Bool) { func scalarLiteral(_ s: UnicodeScalar) -> String { let hex = String(s.value, radix: 16, uppercase: true) return "\\u{\(hex)}" } switch kind { case let .char(c): - return String(c) + return (String(c), false) case let .scalar(s): - return scalarLiteral(s.value) + return (scalarLiteral(s.value), false) case let .scalarSequence(seq): - return seq.scalarValues.map(scalarLiteral).joined() + return (seq.scalarValues.map(scalarLiteral).joined(), false) case let .property(p): - return p._dslBase + return (p._dslBase, true) case let .escaped(e): switch e { // Anchors case .wordBoundary: - return "Anchor.wordBoundary" + return ("Anchor.wordBoundary", false) case .notWordBoundary: - return "Anchor.wordBoundary.inverted" + return ("Anchor.wordBoundary.inverted", false) case .startOfSubject: - return "Anchor.startOfSubject" + return ("Anchor.startOfSubject", false) case .endOfSubject: - return "Anchor.endOfSubject" + return ("Anchor.endOfSubject", false) case .endOfSubjectBeforeNewline: - return "Anchor.endOfSubjectBeforeNewline" + return ("Anchor.endOfSubjectBeforeNewline", false) case .firstMatchingPositionInSubject: - return "Anchor.firstMatchingPositionInSubject" + return ("Anchor.firstMatchingPositionInSubject", false) case .textSegment: - return "Anchor.textSegmentBoundary" + return ("Anchor.textSegmentBoundary", false) case .notTextSegment: - return "Anchor.textSegmentBoundary.inverted" + return ("Anchor.textSegmentBoundary.inverted", false) // Character Classes case .decimalDigit: - return ".digit" + return (".digit", true) case .notDecimalDigit: - return ".digit.inverted" + return (".digit.inverted", true) case .horizontalWhitespace: - return ".horizontalWhitespace" + return (".horizontalWhitespace", true) case .notHorizontalWhitespace: - return ".horizontalWhitespace.inverted" + return (".horizontalWhitespace.inverted", true) case .whitespace: - return ".whitespace" + return (".whitespace", true) case .notWhitespace: - return ".whitespace.inverted" + return (".whitespace.inverted", true) case .wordCharacter: - return ".word" + return (".word", true) case .notWordCharacter: - return ".word.inverted" + return (".word.inverted", true) case .graphemeCluster: - return ".anyGraphemeCluster" + return (".anyGraphemeCluster", true) case .newlineSequence: - return ".newlineSequence" + return (".newlineSequence", true) case .notNewline: - return ".newlineSequence.inverted" + return (".newlineSequence.inverted", true) case .verticalTab: - return ".verticalWhitespace" + return (".verticalWhitespace", true) case .notVerticalTab: - return ".verticalWhitespace.inverted" + return (".verticalWhitespace.inverted", true) // Literal single characters all get converted into DSLTree.Atom.scalar default: - return "TODO: escaped \(e)" + return ("TODO: escaped \(e)", false) } case .namedCharacter: - return " /* TODO: named character */" + return (" /* TODO: named character */", false) case .any: - return ".any" + return (".any", true) case .startOfLine, .endOfLine: fatalError("unreachable") case .backreference: - return " /* TODO: back reference */" + return (" /* TODO: back reference */", false) case .subpattern: - return " /* TODO: subpattern */" + return (" /* TODO: subpattern */", false) case .callout: - return " /* TODO: callout */" + return (" /* TODO: callout */", false) case .backtrackingDirective: - return " /* TODO: backtracking directive */" + return (" /* TODO: backtracking directive */", false) case .changeMatchingOptions: - return "/* TODO: change matching options */" + return ("/* TODO: change matching options */", false) // Every other case we've already decided cannot be represented inside the // DSL. default: - return "" + return ("", false) } } @@ -881,6 +957,52 @@ extension DSLTree.CustomCharacterClass { $0.isUnprintableMember } } + + var isSimplePrint: Bool { + if members.count == 1 { + switch members[0] { + case .intersection(_, _): + return false + case .subtraction(_, _): + return false + case .symmetricDifference(_, _): + return false + default: + return true + } + } + + let nonCharMembers = members.filter { + switch $0 { + case let .atom(a): + switch a { + case .char(_): + return false + case .scalar(_): + return false + case .unconverted(_): + return true + default: + return true + } + + case .quotedLiteral(_): + return false + + case .trivia(_): + return false + + default: + return true + } + } + + if nonCharMembers.isEmpty { + return true + } + + return false + } } extension DSLTree.Atom { @@ -900,7 +1022,7 @@ extension DSLTree.Atom { if a.ast.isUnprintableAtom { return "#/\(a.ast._regexBase)/#" } else { - return a.ast._dslBase + return a.ast._dslBase.0 } case .assertion(let a): From 0d5ab8402827790d885e24296d41ccf72c6db44f Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 12 May 2022 15:35:46 -0700 Subject: [PATCH 2/8] fix intersection, subtraction, symmetricDiference --- .../_StringProcessing/PrintAsPattern.swift | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/Sources/_StringProcessing/PrintAsPattern.swift b/Sources/_StringProcessing/PrintAsPattern.swift index dd0d34f44..6bbf732f0 100644 --- a/Sources/_StringProcessing/PrintAsPattern.swift +++ b/Sources/_StringProcessing/PrintAsPattern.swift @@ -441,31 +441,58 @@ extension PrettyPrinter { break case .intersection(let first, let second): - printAsPattern(first) + if wrap, first.isSimplePrint { + indent() + output("One(") + } + + printAsPattern(first, wrap: false) printIndented { printer in printer.indent() printer.output(".intersection(") - printer.printAsPattern(second, terminateLine: false) + printer.printAsPattern(second, wrap: false, terminateLine: false) printer.output(")") } + if wrap, first.isSimplePrint { + output(")") + } + case .subtraction(let first, let second): - printAsPattern(first) + if wrap, first.isSimplePrint { + indent() + output("One(") + } + + printAsPattern(first, wrap: false) printIndented { printer in printer.indent() printer.output(".subtracting(") - printer.printAsPattern(second, terminateLine: false) + printer.printAsPattern(second, wrap: false, terminateLine: false) printer.output(")") } + if wrap, first.isSimplePrint { + output(")") + } + case .symmetricDifference(let first, let second): - printAsPattern(first) + if wrap, first.isSimplePrint { + indent() + output("One(") + } + + printAsPattern(first, wrap: false) printIndented { printer in printer.indent() printer.output(".symmetricDifference(") - printer.printAsPattern(second, terminateLine: false) + printer.printAsPattern(second, wrap: false, terminateLine: false) printer.output(")") } + + if wrap, first.isSimplePrint { + output(")") + } } } From 78bd8124d609873a4811fd0d058ea72439840f1b Mon Sep 17 00:00:00 2001 From: Nate Cook Date: Thu, 19 May 2022 01:11:46 -0500 Subject: [PATCH 3/8] Add test fixtures for renderAsBuilderDSL --- Tests/RegexTests/RenderDSLTests.swift | 117 ++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 Tests/RegexTests/RenderDSLTests.swift diff --git a/Tests/RegexTests/RenderDSLTests.swift b/Tests/RegexTests/RenderDSLTests.swift new file mode 100644 index 000000000..4d244e2cc --- /dev/null +++ b/Tests/RegexTests/RenderDSLTests.swift @@ -0,0 +1,117 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2021-2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import XCTest +import _RegexParser +@_spi(PatternConverter) @testable +import _StringProcessing + +class RenderDSLTests: XCTestCase {} + +func testConversion( + _ regex: String, + _ expectedDSL: String, + file: StaticString = #file, line: UInt = #line +) throws { + let ast = try _RegexParser.parse(regex, .semantic, .traditional) + let actualDSL = renderAsBuilderDSL(ast: ast)._trimmingSuffix(while: \.isWhitespace) + XCTAssertEqual(actualDSL, expectedDSL[...], file: file, line: line) +} + +extension RenderDSLTests { + func testSimpleConversions() throws { + try testConversion(#"ab+c"#, """ + Regex { + "a" + OneOrMore { + "b" + } + "c" + } + """) + + try testConversion(#"(?:a*)b?(c+)"#, """ + Regex { + ZeroOrMore { + "a" + } + Optionally { + "b" + } + Capture { + OneOrMore { + "c" + } + } + } + """) + + try testConversion(#"\d+"#, """ + Regex { + OneOrMore { + .digit + } + } + """) + try XCTExpectFailure("Invalid leading dot syntax in non-initial position") { + try testConversion(#":\d:"#, """ + Regex { + ":" + CharacterClass.digit + ":" + } + """) + } + } + + func testOptions() throws { + try XCTExpectFailure("Options like '(?i)' aren't converted") { + try testConversion(#"(?i)abc"#, """ + Regex { + "abc" + }.ignoresCase() + """) + } + + try XCTExpectFailure("Options like '(?i:...)' aren't converted") { + try testConversion(#"(?i:abc)"#, """ + Regex { + "abc" + }.ignoresCase() + """) + } + } + + func testAlternations() throws { + try testConversion(#"a|b"#, """ + Regex { + ChoiceOf { + "a" + "b" + } + } + """) + + try XCTExpectFailure("Concatenations in alternations aren't grouped") { + try testConversion(#"\da|b"#, """ + Regex { + ChoiceOf { + Regex { + .digit + "a" + } + "bc" + } + } + """) + } + } +} From 870adfae76c80a35854c25c692331cebdc81027a Mon Sep 17 00:00:00 2001 From: David Ewing Date: Tue, 24 May 2022 12:24:18 -0400 Subject: [PATCH 4/8] Fix an issue where we weren't properly escaping backslashes in quoted strings when converting to the DSL. --- Sources/_StringProcessing/PrintAsPattern.swift | 2 +- Tests/RegexTests/RenderDSLTests.swift | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Sources/_StringProcessing/PrintAsPattern.swift b/Sources/_StringProcessing/PrintAsPattern.swift index 6bbf732f0..232f3df55 100644 --- a/Sources/_StringProcessing/PrintAsPattern.swift +++ b/Sources/_StringProcessing/PrintAsPattern.swift @@ -576,7 +576,7 @@ extension PrettyPrinter { extension String { // TODO: Escaping? fileprivate var _quoted: String { - "\"\(self._replacing("\"", with: "\\\""))\"" + "\"\(self._replacing("\\", with: "\\\\")._replacing("\"", with: "\\\""))\"" } } diff --git a/Tests/RegexTests/RenderDSLTests.swift b/Tests/RegexTests/RenderDSLTests.swift index 4d244e2cc..cd230d87f 100644 --- a/Tests/RegexTests/RenderDSLTests.swift +++ b/Tests/RegexTests/RenderDSLTests.swift @@ -114,4 +114,12 @@ extension RenderDSLTests { """) } } + + func testQuoting() throws { + try testConversion(#"\\\"a\""#, #""" + Regex { + "\\\"a\"" + } + """#) + } } From cd2af50676c997b3a4517a601c5419a5d58b5202 Mon Sep 17 00:00:00 2001 From: David Ewing Date: Wed, 25 May 2022 16:30:15 -0400 Subject: [PATCH 5/8] From review: use raw strings for find/replace when quoting a string. --- Sources/_StringProcessing/PrintAsPattern.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/_StringProcessing/PrintAsPattern.swift b/Sources/_StringProcessing/PrintAsPattern.swift index 232f3df55..03fca2e1f 100644 --- a/Sources/_StringProcessing/PrintAsPattern.swift +++ b/Sources/_StringProcessing/PrintAsPattern.swift @@ -576,7 +576,7 @@ extension PrettyPrinter { extension String { // TODO: Escaping? fileprivate var _quoted: String { - "\"\(self._replacing("\\", with: "\\\\")._replacing("\"", with: "\\\""))\"" + "\"\(self._replacing(#"\"#, with: #"\\"#)._replacing(#"""#, with: #"\""#))\"" } } From 25ba6d3eaf679e4ec1ec3a2f9bfb87681f8a7fb8 Mon Sep 17 00:00:00 2001 From: David Ewing Date: Wed, 25 May 2022 16:32:22 -0400 Subject: [PATCH 6/8] From review: remove an extra `\` from the regex pattern. (The regex parser ignores it in front of a `"`, but it's confusing.) --- Tests/RegexTests/RenderDSLTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/RegexTests/RenderDSLTests.swift b/Tests/RegexTests/RenderDSLTests.swift index cd230d87f..016b32f4a 100644 --- a/Tests/RegexTests/RenderDSLTests.swift +++ b/Tests/RegexTests/RenderDSLTests.swift @@ -116,7 +116,7 @@ extension RenderDSLTests { } func testQuoting() throws { - try testConversion(#"\\\"a\""#, #""" + try testConversion(#"\\"a""#, #""" Regex { "\\\"a\"" } From 19a1e06b16a56868851fc37e47734e2bdc7ff7b0 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 13 May 2022 11:55:47 -0700 Subject: [PATCH 7/8] Handle atoms as things to be wrapped oops Repeat does not get to participate in inline fix tests --- .../_StringProcessing/PrintAsPattern.swift | 110 +++++++++++++----- Tests/RegexTests/RenderDSLTests.swift | 21 ++-- 2 files changed, 88 insertions(+), 43 deletions(-) diff --git a/Sources/_StringProcessing/PrintAsPattern.swift b/Sources/_StringProcessing/PrintAsPattern.swift index 03fca2e1f..5e4347f3b 100644 --- a/Sources/_StringProcessing/PrintAsPattern.swift +++ b/Sources/_StringProcessing/PrintAsPattern.swift @@ -127,7 +127,7 @@ extension PrettyPrinter { print("/* TODO: conditional */") case let .quantification(amount, kind, child): - let amount = amount.ast._patternBase + let amountStr = amount.ast._patternBase var kind = kind.ast?._patternBase ?? "" // If we've updated our quantification behavior, then use that. This @@ -137,10 +137,10 @@ extension PrettyPrinter { kind = quantificationBehavior._patternBase } - var blockName = "\(amount)(\(kind))" + var blockName = "\(amountStr)(\(kind))" if kind == ".eager" { - blockName = "\(amount)" + blockName = "\(amountStr)" } // Special case single child character classes for repetition nodes. @@ -152,6 +152,20 @@ extension PrettyPrinter { // One(.digit) // } // + func printAtom(_ pattern: String) { + indent() + + if kind != ".eager" { + blockName.removeLast() + output("\(blockName), ") + } else { + output("\(blockName)(") + } + + output("\(pattern))") + terminateLine() + } + func printSimpleCCC( _ ccc: DSLTree.CustomCharacterClass ) { @@ -169,23 +183,42 @@ extension PrettyPrinter { terminateLine() } - switch child { - case let .customCharacterClass(ccc): - if ccc.isSimplePrint { - printSimpleCCC(ccc) - return - } - - break - case let .convertedRegexLiteral(.customCharacterClass(ccc), _): - if ccc.isSimplePrint { - printSimpleCCC(ccc) - return + // We can only do this for Optionally, ZeroOrMore, and OneOrMore. Cannot + // do it right now for Repeat. + if amount.ast.supportsInlineComponent { + switch child { + case let .atom(a): + if let pattern = a._patternBase(&self), pattern.canBeWrapped { + printAtom(pattern.0) + return + } + + break + case let .customCharacterClass(ccc): + if ccc.isSimplePrint { + printSimpleCCC(ccc) + return + } + + break + + case let .convertedRegexLiteral(.atom(a), _): + if let pattern = a._patternBase(&self), pattern.canBeWrapped { + printAtom(pattern.0) + return + } + + break + case let .convertedRegexLiteral(.customCharacterClass(ccc), _): + if ccc.isSimplePrint { + printSimpleCCC(ccc) + return + } + + break + default: + break } - - break - default: - break } printBlock(blockName) { printer in @@ -199,7 +232,11 @@ extension PrettyPrinter { } if let pattern = a._patternBase(&self) { - print(pattern) + if pattern.canBeWrapped { + print("One(\(pattern.0))") + } else { + print(pattern.0) + } } case .trivia: @@ -391,9 +428,9 @@ extension PrettyPrinter { if let lhs = lhs._patternBase(&self), let rhs = rhs._patternBase(&self) { indent() output("(") - output(lhs) + output(lhs.0) output("...") - output(rhs) + output(rhs.0) output(")") } @@ -939,6 +976,15 @@ extension AST.Quantification.Amount { case let .range(n, m): return "Repeat(\(n.value)...\(m.value))" } } + + var supportsInlineComponent: Bool { + switch self { + case .zeroOrMore: return true + case .oneOrMore: return true + case .zeroOrOne: return true + default: return false + } + } } extension AST.Quantification.Kind { @@ -1033,33 +1079,35 @@ extension DSLTree.CustomCharacterClass { } extension DSLTree.Atom { - func _patternBase(_ printer: inout PrettyPrinter) -> String? { + func _patternBase( + _ printer: inout PrettyPrinter + ) -> (String, canBeWrapped: Bool)? { switch self { case .any: - return ".any" + return (".any", true) case let .char(c): - return String(c)._quoted + return (String(c)._quoted, false) case let .scalar(s): let hex = String(s.value, radix: 16, uppercase: true) - return "\\u{\(hex)}"._quoted + return ("\\u{\(hex)}"._quoted, false) case let .unconverted(a): if a.ast.isUnprintableAtom { - return "#/\(a.ast._regexBase)/#" + return ("#/\(a.ast._regexBase)/#", false) } else { - return a.ast._dslBase.0 + return a.ast._dslBase } case .assertion(let a): - return a.ast._patternBase + return (a.ast._patternBase, false) case .backreference(_): - return "/* TOOD: backreferences */" + return ("/* TOOD: backreferences */", false) case .symbolicReference: - return "/* TOOD: symbolic references */" + return ("/* TOOD: symbolic references */", false) case .changeMatchingOptions(let matchingOptions): for add in matchingOptions.ast.adding { diff --git a/Tests/RegexTests/RenderDSLTests.swift b/Tests/RegexTests/RenderDSLTests.swift index 016b32f4a..75f6d2974 100644 --- a/Tests/RegexTests/RenderDSLTests.swift +++ b/Tests/RegexTests/RenderDSLTests.swift @@ -56,20 +56,17 @@ extension RenderDSLTests { try testConversion(#"\d+"#, """ Regex { - OneOrMore { - .digit - } + OneOrMore(.digit) + } + """) + + try testConversion(#":\d:"#, """ + Regex { + ":" + One(.digit) + ":" } """) - try XCTExpectFailure("Invalid leading dot syntax in non-initial position") { - try testConversion(#":\d:"#, """ - Regex { - ":" - CharacterClass.digit - ":" - } - """) - } } func testOptions() throws { From d9a3627d2e6b5d60b9f92fab7ba052a5220a3690 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 21 Jun 2022 10:01:18 -0700 Subject: [PATCH 8/8] Unconditionally print a regex block for concatenations fix test --- .../_StringProcessing/PrintAsPattern.swift | 17 ++++++++++++---- Tests/RegexTests/RenderDSLTests.swift | 20 +++++++++---------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Sources/_StringProcessing/PrintAsPattern.swift b/Sources/_StringProcessing/PrintAsPattern.swift index 5e4347f3b..2fe7c6ccc 100644 --- a/Sources/_StringProcessing/PrintAsPattern.swift +++ b/Sources/_StringProcessing/PrintAsPattern.swift @@ -71,8 +71,15 @@ extension PrettyPrinter { print("let \(namedCapture) = Reference(Substring.self)") } - printBlock("Regex") { printer in - printer.printAsPattern(convertedFromAST: node) + switch node { + case .concatenation(_): + printAsPattern(convertedFromAST: node) + case .convertedRegexLiteral(.concatenation(_), _): + printAsPattern(convertedFromAST: node) + default: + printBlock("Regex") { printer in + printer.printAsPattern(convertedFromAST: node) + } } } @@ -99,8 +106,10 @@ extension PrettyPrinter { } case let .concatenation(c): - c.forEach { - printAsPattern(convertedFromAST: $0) + printBlock("Regex") { printer in + c.forEach { + printer.printAsPattern(convertedFromAST: $0) + } } case let .nonCapturingGroup(kind, child): diff --git a/Tests/RegexTests/RenderDSLTests.swift b/Tests/RegexTests/RenderDSLTests.swift index 75f6d2974..7bf8ba412 100644 --- a/Tests/RegexTests/RenderDSLTests.swift +++ b/Tests/RegexTests/RenderDSLTests.swift @@ -97,19 +97,17 @@ extension RenderDSLTests { } """) - try XCTExpectFailure("Concatenations in alternations aren't grouped") { - try testConversion(#"\da|b"#, """ - Regex { - ChoiceOf { - Regex { - .digit - "a" - } - "bc" + try testConversion(#"\da|bc"#, """ + Regex { + ChoiceOf { + Regex { + One(.digit) + "a" } + "bc" } - """) - } + } + """) } func testQuoting() throws {