@@ -712,6 +712,22 @@ extension Source {
712
712
return . init( caretLoc: nil , adding: adding, minusLoc: nil , removing: [ ] )
713
713
}
714
714
715
+ /// A matching option changing atom.
716
+ ///
717
+ /// '(?' MatchingOptionSeq ')'
718
+ ///
719
+ mutating func lexChangeMatchingOptionAtom(
720
+ context: ParsingContext
721
+ ) throws -> AST . MatchingOptionSequence ? {
722
+ try tryEating { src in
723
+ guard src. tryEat ( sequence: " (? " ) ,
724
+ let seq = try src. lexMatchingOptionSequence ( context: context)
725
+ else { return nil }
726
+ try src. expect ( " ) " )
727
+ return seq
728
+ }
729
+ }
730
+
715
731
/// Try to consume explicitly spelled-out PCRE2 group syntax.
716
732
mutating func lexExplicitPCRE2GroupStart( ) -> AST . Group . Kind ? {
717
733
tryEating { src in
@@ -852,7 +868,7 @@ extension Source {
852
868
// otherwise a matching option specifier. Conversely, '(?P' can be the
853
869
// start of a matching option sequence, or a reference if it is followed
854
870
// by '=' or '<'.
855
- guard !src. shouldLexGroupLikeAtom ( ) else { return nil }
871
+ guard !src. shouldLexGroupLikeAtom ( context : context ) else { return nil }
856
872
857
873
guard src. tryEat ( " ( " ) else { return nil }
858
874
if src. tryEat ( " ? " ) {
@@ -877,22 +893,13 @@ extension Source {
877
893
878
894
// Matching option changing group (?iJmnsUxxxDPSWy{..}-iJmnsUxxxDPSW:).
879
895
if let seq = try src. lexMatchingOptionSequence ( context: context) {
880
- if src. tryEat ( " : " ) {
881
- return . changeMatchingOptions( seq, isIsolated: false )
882
- }
883
- // If this isn't start of an explicit group, we should have an
884
- // implicit group that covers the remaining elements of the current
885
- // group.
886
- // TODO: This implicit scoping behavior matches Oniguruma, but PCRE
887
- // also does it across alternations, which will require additional
888
- // handling.
889
- guard src. tryEat ( " ) " ) else {
896
+ guard src. tryEat ( " : " ) else {
890
897
if let next = src. peek ( ) {
891
898
throw ParseError . invalidMatchingOption ( next)
892
899
}
893
900
throw ParseError . expected ( " ) " )
894
901
}
895
- return . changeMatchingOptions( seq, isIsolated : true )
902
+ return . changeMatchingOptions( seq)
896
903
}
897
904
898
905
guard let next = src. peek ( ) else {
@@ -1041,18 +1048,8 @@ extension Source {
1041
1048
context: ParsingContext
1042
1049
) throws -> Located < AST . Group . Kind > ? {
1043
1050
try tryEating { src in
1044
- guard src. tryEat ( sequence: " (? " ) ,
1045
- let group = try src. lexGroupStart ( context: context)
1046
- else { return nil }
1047
-
1048
- // Implicitly scoped groups are not supported here.
1049
- guard !group. value. hasImplicitScope else {
1050
- throw LocatedError (
1051
- ParseError . unsupportedCondition ( " implicitly scoped group " ) ,
1052
- group. location
1053
- )
1054
- }
1055
- return group
1051
+ guard src. tryEat ( sequence: " (? " ) else { return nil }
1052
+ return try src. lexGroupStart ( context: context)
1056
1053
}
1057
1054
}
1058
1055
@@ -1239,17 +1236,19 @@ extension Source {
1239
1236
allowWholePatternRef: Bool = false , allowRecursionLevel: Bool = false
1240
1237
) throws -> AST . Reference ? {
1241
1238
let kind = try recordLoc { src -> AST . Reference . Kind ? in
1242
- // Note this logic should match canLexNumberedReference.
1243
- if src. tryEat ( " + " ) {
1244
- return . relative( try src. expectNumber ( ) . value)
1245
- }
1246
- if src. tryEat ( " - " ) {
1247
- return . relative( try - src. expectNumber ( ) . value)
1248
- }
1249
- if let num = try src. lexNumber ( ) {
1250
- return . absolute( num. value)
1239
+ try src. tryEating { src in
1240
+ // Note this logic should match canLexNumberedReference.
1241
+ if src. tryEat ( " + " ) , let num = try src. lexNumber ( ) {
1242
+ return . relative( num. value)
1243
+ }
1244
+ if src. tryEat ( " - " ) , let num = try src. lexNumber ( ) {
1245
+ return . relative( - num. value)
1246
+ }
1247
+ if let num = try src. lexNumber ( ) {
1248
+ return . absolute( num. value)
1249
+ }
1250
+ return nil
1251
1251
}
1252
- return nil
1253
1252
}
1254
1253
guard let kind = kind else { return nil }
1255
1254
guard allowWholePatternRef || kind. value != . recurseWholePattern else {
@@ -1478,8 +1477,21 @@ extension Source {
1478
1477
return src. canLexNumberedReference ( )
1479
1478
}
1480
1479
1480
+ private func canLexMatchingOptionsAsAtom( context: ParsingContext ) -> Bool {
1481
+ var src = self
1482
+
1483
+ // See if we can lex a matching option sequence that terminates in ')'. Such
1484
+ // a sequence is an atom. If an error is thrown, there are invalid elements
1485
+ // of the matching option sequence. In such a case, we can lex as a group
1486
+ // and diagnose the invalid group kind.
1487
+ guard ( try ? src. lexMatchingOptionSequence ( context: context) ) != nil else {
1488
+ return false
1489
+ }
1490
+ return src. tryEat ( " ) " )
1491
+ }
1492
+
1481
1493
/// Whether a group specifier should be lexed as an atom instead of a group.
1482
- private func shouldLexGroupLikeAtom( ) -> Bool {
1494
+ private func shouldLexGroupLikeAtom( context : ParsingContext ) -> Bool {
1483
1495
var src = self
1484
1496
guard src. tryEat ( " ( " ) else { return false }
1485
1497
@@ -1493,6 +1505,9 @@ extension Source {
1493
1505
// The start of an Oniguruma 'of-contents' callout.
1494
1506
if src. tryEat ( " { " ) { return true }
1495
1507
1508
+ // A matching option atom (?x), (?i), ...
1509
+ if src. canLexMatchingOptionsAsAtom ( context: context) { return true }
1510
+
1496
1511
return false
1497
1512
}
1498
1513
// The start of a backreference directive or Oniguruma named callout.
@@ -1753,13 +1768,20 @@ extension Source {
1753
1768
///
1754
1769
/// GroupLikeAtom -> GroupLikeReference | Callout | BacktrackingDirective
1755
1770
///
1756
- mutating func expectGroupLikeAtom( ) throws -> AST . Atom . Kind {
1771
+ mutating func expectGroupLikeAtom(
1772
+ context: ParsingContext
1773
+ ) throws -> AST . Atom . Kind {
1757
1774
try recordLoc { src in
1758
1775
// References that look like groups, e.g (?R), (?1), ...
1759
1776
if let ref = try src. lexGroupLikeReference ( ) {
1760
1777
return ref. value
1761
1778
}
1762
1779
1780
+ // Change matching options atom (?i), (?x-i), ...
1781
+ if let seq = try src. lexChangeMatchingOptionAtom ( context: context) {
1782
+ return . changeMatchingOptions( seq)
1783
+ }
1784
+
1763
1785
// (*ACCEPT), (*FAIL), (*MARK), ...
1764
1786
if let b = try src. lexBacktrackingDirective ( ) {
1765
1787
return . backtrackingDirective( b)
@@ -1828,8 +1850,8 @@ extension Source {
1828
1850
1829
1851
// If we have group syntax that was skipped over in lexGroupStart, we
1830
1852
// need to handle it as an atom, or throw an error.
1831
- if !customCC && src. shouldLexGroupLikeAtom ( ) {
1832
- return try src. expectGroupLikeAtom ( )
1853
+ if !customCC && src. shouldLexGroupLikeAtom ( context : context ) {
1854
+ return try src. expectGroupLikeAtom ( context : context )
1833
1855
}
1834
1856
1835
1857
// A quantifier here is invalid.
0 commit comments