mutating func lexGroupStart()

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
      }
    }
  }