in Sources/_MatchingEngine/Regex/Parse/LexicalAnalysis.swift [763:836]
mutating func lexGroupStart(
) throws -> Located<AST.Group.Kind>? {
try recordLoc { src in
try src.tryEating { src in
// Explicitly spelled out PRCE2 syntax for some groups. This needs to be
// done before group-like atoms, as it uses the '(*' syntax, which is
// otherwise a group-like atom.
if let g = src.lexExplicitPCRE2GroupStart() { return g }
// There are some atoms that syntactically look like groups, bail here
// if we see any. Care needs to be taken here as e.g a group starting
// with '(?-' is a subpattern if the next character is a digit,
// otherwise a matching option specifier. Conversely, '(?P' can be the
// start of a matching option sequence, or a reference if it is followed
// by '=' or '<'.
guard !src.shouldLexGroupLikeAtom() else { return nil }
guard src.tryEat("(") else { return nil }
if src.tryEat("?") {
if src.tryEat(":") { return .nonCapture }
if src.tryEat("|") { return .nonCaptureReset }
if src.tryEat(">") { return .atomicNonCapturing }
if src.tryEat("=") { return .lookahead }
if src.tryEat("!") { return .negativeLookahead }
if src.tryEat("*") { return .nonAtomicLookahead }
if src.tryEat(sequence: "<=") { return .lookbehind }
if src.tryEat(sequence: "<!") { return .negativeLookbehind }
if src.tryEat(sequence: "<*") { return .nonAtomicLookbehind }
// Named
if src.tryEat("<") || src.tryEat(sequence: "P<") {
return try src.expectNamedGroup(endingWith: ">")
}
if src.tryEat("'") {
return try src.expectNamedGroup(endingWith: "'")
}
// Matching option changing group (?iJmnsUxxxDPSWy{..}-iJmnsUxxxDPSW:).
if let seq = try src.lexMatchingOptionSequence() {
if src.tryEat(":") {
return .changeMatchingOptions(seq, isIsolated: false)
}
// If this isn't start of an explicit group, we should have an
// implicit group that covers the remaining elements of the current
// group.
// TODO: This implicit scoping behavior matches Oniguruma, but PCRE
// also does it across alternations, which will require additional
// handling.
guard src.tryEat(")") else {
if let next = src.peek() {
throw ParseError.invalidMatchingOption(next)
}
throw ParseError.expected(")")
}
return .changeMatchingOptions(seq, isIsolated: true)
}
guard let next = src.peek() else {
throw ParseError.expectedGroupSpecifier
}
throw ParseError.unknownGroupKind("?\(next)")
}
// (_:)
if src.experimentalCaptures && src.tryEat(sequence: "_:") {
return .nonCapture
}
// TODO: (name:)
return .capture
}
}
}