Swift Regex crashes when trying to get a simple string

I'm trying to create a simple Regex Builder to parse a timestamp in hh:mm:ss or mm:ss format, a " - " separator, and then a string. I'm trying to use the new Swift RegexBuilder however it doesn't seem to work. It compiles but crashes when running when trying to get the text portion.

Example code in a single playground preview file for reference:

import SwiftUI
import RegexBuilder

struct RegexTestView: View {
    
    var string: String {
		let timecode = Regex {
			Optionally {
				OneOrMore(.whitespace)
			}
			// timecode section
			Optionally {
				Capture {
					OneOrMore(.digit)
				} transform: { match in
					Int(match)
				}
				":"
			}
			TryCapture {
				OneOrMore(.digit)
			} transform: { match in
				Int(match)
			}
			":"
			// seconds
			TryCapture {
				OneOrMore(.digit)
				Optionally { // miliseconds
					"."
					OneOrMore(.digit)
				}
			} transform: { match in
				TimeInterval(match)
			}
			// separator
			" - "
			Capture { // WHY CAN'T I GET THE REMAINING TEXT????  Any attempts I make to get this result in a crash.
				OneOrMore(.any)
			} transform: { match in
				String(match)
			}
		}

		let tests = [" 222:45:23.2 - foo","2:34:22 - bar","  2:08 - baz","2:32.18","2:45:23.2 - what"]
		var results = ""
		for test in tests {
			guard let match = test.wholeMatch(of: timecode) else {
				results += "MATCH NOT FOUND"
				continue
			}
			results += """
			••••
			\(match.0)
			\(String(describing: match.1))
			\(String(describing: match.2))
			\(String(describing: match.3))
		"""
			/*
			 // Adding this line to the above crashes
			 \(String(describing: match.4))\n
			 */
        }
		return results
    }
    
    var body: some View {
        Text(string)
    }
}

#Preview("Regex") {
    RegexTestView()
}

Replies

In situations like this I find it useful to put the code into a simple command-line tool project. That isolates it from the vagaries of SwiftUI, playgrounds, and so on.

When I did this with your code (main.swift is pasted in below) it didn’t crash. This is with Xcode 15.0 running on macOS 13.6.1. Rather, it prints:

 ••••
  222:45:23.2 - foo
 Optional(Optional(222))
 45
 23.2 ••••
 2:34:22 - bar
 Optional(Optional(2))
 34
 22.0 ••••
   2:08 - baz
 Optional(nil)
 2
 8.0MATCH NOT FOUND ••••
 2:45:23.2 - what
 Optional(Optional(2))
 45
 23.2

If you do the same, what do you see?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"


import Foundation
import RegexBuilder

func main() {
    
    var string: String {
        let timecode = Regex {
            Optionally {
                OneOrMore(.whitespace)
            }
            // timecode section
            Optionally {
                Capture {
                    OneOrMore(.digit)
                } transform: { match in
                    Int(match)
                }
                ":"
            }
            TryCapture {
                OneOrMore(.digit)
            } transform: { match in
                Int(match)
            }
            ":"
            // seconds
            TryCapture {
                OneOrMore(.digit)
                Optionally { // miliseconds
                    "."
                    OneOrMore(.digit)
                }
            } transform: { match in
                TimeInterval(match)
            }
            // separator
            " - "
            Capture { // WHY CAN'T I GET THE REMAINING TEXT????  Any attempts I make to get this result in a crash.
                OneOrMore(.any)
            } transform: { match in
                String(match)
            }
        }
        
        let tests = [" 222:45:23.2 - foo","2:34:22 - bar","  2:08 - baz","2:32.18","2:45:23.2 - what"]
        var results = ""
        for test in tests {
            guard let match = test.wholeMatch(of: timecode) else {
                results += "MATCH NOT FOUND"
                continue
            }
            results += """
   ••••
   \(match.0)
   \(String(describing: match.1))
   \(String(describing: match.2))
   \(String(describing: match.3))
  """
            /*
             // Adding this line to the above crashes
             \(String(describing: match.4))\n
             */
        }
        return results
    }

    print(string)
}

main()