Skip to content

Commit c24458a

Browse files
committed
Introduce ASTStage parameter to parse
This allows specifying whether or not to perform semantic checks on the AST. Some clients, e.g syntax coloring, only care about the syntactic structure. But other clients want errors to be emitted for e.g unsupported constructs.
1 parent 6d833aa commit c24458a

File tree

9 files changed

+47
-19
lines changed

9 files changed

+47
-19
lines changed

Sources/PatternConverter/PatternConverter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ struct PatternConverter: ParsableCommand {
5050
print("Converting '\(delim)\(regex)\(delim)'")
5151

5252
let ast = try _RegexParser.parse(
53-
regex,
53+
regex, .semantic,
5454
experimentalSyntax ? .experimental : .traditional)
5555

5656
// Show rendered source ranges

Sources/_RegexParser/Regex/Parse/CompilerInterface.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public func swiftCompilerParseRegexLiteral(
9696
_ input: String, captureBufferOut: UnsafeMutableRawBufferPointer
9797
) throws -> (regexToEmit: String, version: Int) {
9898
do {
99-
let ast = try parseWithDelimiters(input)
99+
let ast = try parseWithDelimiters(input, .semantic)
100100
// Serialize the capture structure for later type inference.
101101
assert(captureBufferOut.count >= input.utf8.count)
102102
ast.captureStructure.encode(to: captureBufferOut)

Sources/_RegexParser/Regex/Parse/Parse.swift

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -558,15 +558,34 @@ extension Parser {
558558
}
559559
}
560560

561+
public enum ASTStage {
562+
/// The regex is parsed, and a syntactically valid AST is returned. Otherwise
563+
/// an error is thrown. This is useful for e.g syntax coloring.
564+
case syntactic
565+
566+
/// The regex is parsed, and a syntactically and semantically valid AST is
567+
/// returned. Otherwise an error is thrown. A semantically valid AST has been
568+
/// checked for e.g unsupported constructs and invalid backreferences.
569+
case semantic
570+
}
571+
561572
public func parse<S: StringProtocol>(
562-
_ regex: S, _ syntax: SyntaxOptions
573+
_ regex: S, _ stage: ASTStage, _ syntax: SyntaxOptions
563574
) throws -> AST where S.SubSequence == Substring
564575
{
565576
let source = Source(String(regex))
566577
var parser = Parser(source, syntax: syntax)
567578
return try parser.parse()
568579
}
569580

581+
@available(*, deprecated, renamed: "parse(_:_:_:)")
582+
public func parse<S: StringProtocol>(
583+
_ regex: S, _ syntax: SyntaxOptions
584+
) throws -> AST where S.SubSequence == Substring
585+
{
586+
try parse(regex, .syntactic, syntax)
587+
}
588+
570589
/// Retrieve the default set of syntax options that a delimiter and literal
571590
/// contents indicates.
572591
fileprivate func defaultSyntaxOptions(
@@ -588,14 +607,23 @@ fileprivate func defaultSyntaxOptions(
588607
}
589608
}
590609

610+
@available(*, deprecated, renamed: "parseWithDelimiters(_:_:)")
611+
public func parseWithDelimiters<S: StringProtocol>(
612+
_ regex: S
613+
) throws -> AST where S.SubSequence == Substring
614+
{
615+
try parseWithDelimiters(regex, .syntactic)
616+
}
617+
591618
/// Parses a given regex string with delimiters, inferring the syntax options
592619
/// from the delimiters used.
593620
public func parseWithDelimiters<S: StringProtocol>(
594-
_ regex: S
621+
_ regex: S, _ stage: ASTStage
595622
) throws -> AST where S.SubSequence == Substring {
596623
let (contents, delim) = droppingRegexDelimiters(String(regex))
597624
do {
598-
return try parse(contents, defaultSyntaxOptions(delim, contents: contents))
625+
let syntax = defaultSyntaxOptions(delim, contents: contents)
626+
return try parse(contents, stage, syntax)
599627
} catch let error as LocatedErrorProtocol {
600628
// Convert the range in 'contents' to the range in 'regex'.
601629
let delimCount = delim.opening.count

Sources/_StringProcessing/Compiler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class Compiler {
3838
func _compileRegex(
3939
_ regex: String, _ syntax: SyntaxOptions = .traditional
4040
) throws -> Executor {
41-
let ast = try parse(regex, syntax)
41+
let ast = try parse(regex, .semantic, syntax)
4242
let program = try Compiler(ast: ast).emit()
4343
return Executor(program: program)
4444
}

Sources/_StringProcessing/Regex/AnyRegexOutput.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ extension Regex where Output == AnyRegexOutput {
1717
///
1818
/// - Parameter pattern: The regular expression.
1919
public init(_ pattern: String) throws {
20-
self.init(ast: try parse(pattern, .traditional))
20+
self.init(ast: try parse(pattern, .semantic, .traditional))
2121
}
2222
}
2323

@@ -31,7 +31,7 @@ extension Regex {
3131
_ pattern: String,
3232
as: Output.Type = Output.self
3333
) throws {
34-
self.init(ast: try parse(pattern, .traditional))
34+
self.init(ast: try parse(pattern, .semantic, .traditional))
3535
}
3636
}
3737

Sources/_StringProcessing/Regex/Core.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public struct Regex<Output>: RegexComponent {
4444
// Compiler interface. Do not change independently.
4545
@usableFromInline
4646
init(_regexString pattern: String) {
47-
self.init(ast: try! parse(pattern, .traditional))
47+
self.init(ast: try! parse(pattern, .semantic, .traditional))
4848
}
4949

5050
// Compiler interface. Do not change independently.
@@ -53,7 +53,7 @@ public struct Regex<Output>: RegexComponent {
5353
assert(version == currentRegexLiteralFormatVersion)
5454
// The version argument is passed by the compiler using the value defined
5555
// in libswiftParseRegexLiteral.
56-
self.init(ast: try! parseWithDelimiters(pattern))
56+
self.init(ast: try! parseWithDelimiters(pattern, .semantic))
5757
}
5858

5959
public var regex: Regex<Output> {

Tests/RegexTests/CaptureTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ func captureTest(
150150
file: StaticString = #file,
151151
line: UInt = #line
152152
) {
153-
let ast = try! parse(regex, .traditional)
153+
let ast = try! parse(regex, .semantic, .traditional)
154154
let capList = ast.root._captureList
155155
guard capList == expected else {
156156
XCTFail("""

Tests/RegexTests/DiagnosticTests.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ extension RegexTests {
2020
XCTAssert(SourceLocation.fake.isFake)
2121
XCTAssert(group(.capture, "a").location.isFake)
2222

23-
let ast = try! parse("(a)", .traditional).root
23+
let ast = try! parse("(a)", .semantic, .traditional).root
2424
XCTAssert(ast.location.isReal)
2525
}
2626

@@ -31,7 +31,7 @@ extension RegexTests {
3131
//
3232
// Input should be a concatenation or alternation
3333
func flatTest(_ str: String, _ expected: [String]) {
34-
guard let ast = try? parse(str, .traditional).root else {
34+
guard let ast = try? parse(str, .semantic, .traditional).root else {
3535
XCTFail("Fail to parse: \(str)")
3636
return
3737
}
@@ -54,7 +54,7 @@ extension RegexTests {
5454

5555
func renderTest(_ str: String, _ expected: [String]) {
5656
let lines = try! parse(
57-
str, .traditional
57+
str, .semantic, .traditional
5858
)._render(in: str)
5959
func fail() {
6060
XCTFail("""

Tests/RegexTests/ParseTests.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ func parseNotEqualTest(
170170
syntax: SyntaxOptions = .traditional,
171171
file: StaticString = #file, line: UInt = #line
172172
) {
173-
let lhsAST = try! parse(lhs, syntax)
174-
let rhsAST = try! parse(rhs, syntax)
173+
let lhsAST = try! parse(lhs, .syntactic, syntax)
174+
let rhsAST = try! parse(rhs, .syntactic, syntax)
175175
if lhsAST == rhsAST || lhsAST._dump() == rhsAST._dump() {
176176
XCTFail("""
177177
AST: \(lhsAST._dump())
@@ -187,7 +187,7 @@ func rangeTest(
187187
at locFn: (AST.Node) -> SourceLocation = \.location,
188188
file: StaticString = #file, line: UInt = #line
189189
) {
190-
let ast = try! parse(input, syntax).root
190+
let ast = try! parse(input, .syntactic, syntax).root
191191
let range = input.offsets(of: locFn(ast).range)
192192
let expected = expectedRange(input)
193193

@@ -207,7 +207,7 @@ func diagnosticTest(
207207
file: StaticString = #file, line: UInt = #line
208208
) {
209209
do {
210-
let ast = try parse(input, syntax)
210+
let ast = try parse(input, .semantic, syntax)
211211
XCTFail("""
212212
213213
Passed \(ast)
@@ -236,7 +236,7 @@ func diagnosticWithDelimitersTest(
236236
input, ignoreTrailing: ignoreTrailing, file: file, line: line)
237237

238238
do {
239-
let orig = try parseWithDelimiters(literal)
239+
let orig = try parseWithDelimiters(literal, .semantic)
240240
let ast = orig.root
241241
XCTFail("""
242242

0 commit comments

Comments
 (0)