Skip to content

Instantly share code, notes, and snippets.

@JadenGeller
Last active December 20, 2021 02:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JadenGeller/ae9629b23cfd128c39fa3dd0e4d346a5 to your computer and use it in GitHub Desktop.
Save JadenGeller/ae9629b23cfd128c39fa3dd0e4d346a5 to your computer and use it in GitHub Desktop.
Parses outermost paired delimiters, gracefully handling mismatches
// #import "https://gist.githubusercontent.com/JadenGeller/7a4ae11be08a6798002775bbd07887e3/raw/7187bf2c72cd8ab29d64a0eea54a90e7c979dc41/Abort.swift"
extension String {
enum GroupedString {
enum Element {
case character(Character)
case group(Substring)
}
case balanced([Element])
case hanging(Int, [Element], Int)
init(leadingDepth: Int, elements: [Element], trailingDepth: Int) {
if leadingDepth == 0 && trailingDepth == 0 {
self = .balanced(elements)
} else {
self = .hanging(leadingDepth, elements, trailingDepth)
}
}
}
func grouping(between openingDelimiter: Character, _ closingDelimiter: Character) -> GroupedString {
var leadingDepth = 0
var currentDepth = 0
let components = try! Abort.retry { () -> [Substring] in
currentDepth = leadingDepth
return try split(omittingEmptySubsequences: false) {
switch $0 {
case openingDelimiter:
defer { currentDepth += 1 }
return currentDepth == 0
case closingDelimiter:
guard currentDepth > 0 else {
leadingDepth += 1
throw Abort()
}
currentDepth -= 1
return currentDepth == 0
default:
return false
}
}
}
let elements = components.enumerated().flatMap { index, component -> [GroupedString.Element] in
if index % 2 == (leadingDepth > 0 ? 1 : 0) {
return component.map(GroupedString.Element.character)
} else {
return [GroupedString.Element.group(component)]
}
}
return .init(leadingDepth: leadingDepth, elements: elements, trailingDepth: currentDepth)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment