@@ -42,7 +42,7 @@ public struct SmallProjectionPath : CustomStringConvertible, CustomReflectable,
4242 /// The physical representation of the path. The path components are stored in
4343 /// reverse order: the first path component is stored in the lowest bits (LSB),
4444 /// the last component is stored in the highest bits (MSB).
45- /// Each pass component consists of zero or more "index-overflow" bytes followed
45+ /// Each path component consists of zero or more "index-overflow" bytes followed
4646 /// by the "index-kind" main byte (from LSB to MSB).
4747 ///
4848 /// index overflow byte: bit-nr: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
@@ -70,10 +70,10 @@ public struct SmallProjectionPath : CustomStringConvertible, CustomReflectable,
7070 case classField = 0x4 // A concrete class field: syntax e.g. `c1`
7171 case tailElements = 0x5 // A tail allocated element of a class: syntax `ct`
7272 case anyValueFields = 0x6 // Any number of any value fields (struct, tuple, enum): syntax `v**`
73- case anyClassField = 0x7 // Any class field, including tail elements: syntax `c*`
74-
73+
7574 // "Large" kinds: starting from here the low 3 bits must be 1.
7675 // This and all following kinds (we'll add in the future) cannot have a field index.
76+ case anyClassField = 0x7 // Any class field, including tail elements: syntax `c*`
7777 case anything = 0xf // Any number of any fields: syntax `**`
7878
7979 public var isValueField : Bool {
@@ -180,7 +180,10 @@ public struct SmallProjectionPath : CustomStringConvertible, CustomReflectable,
180180 assert ( kind != . anything || bytes == 0 , " 'anything' only allowed in last path component " )
181181 var idx = index
182182 var b = bytes
183- if ( b >> 56 ) != 0 { return Self ( . anything) }
183+ if ( b >> 56 ) != 0 {
184+ // Overflow
185+ return Self ( . anything)
186+ }
184187 b = ( b << 8 ) | UInt64 ( ( ( idx & 0xf ) << 4 ) | ( kind. rawValue << 1 ) )
185188 idx >>= 4
186189 while idx != 0 {
@@ -242,13 +245,37 @@ public struct SmallProjectionPath : CustomStringConvertible, CustomReflectable,
242245 /// returns true for `v**.c3`
243246 /// returns true for `**`
244247 /// returns false for `s0.c3` (because e.g. `s1` would not match)
245- public var matchesAllValueFields : Bool {
248+ public var topMatchesAnyValueField : Bool {
246249 switch top. kind {
247250 case . anyValueFields, . anything: return true
248251 default : return false
249252 }
250253 }
251254
255+ /// Returns true if the path does not have any class projections.
256+ /// For example:
257+ /// returns true for `v**`
258+ /// returns false for `c0`
259+ /// returns false for `**` (because '**' can have any number of class projections)
260+ public var hasNoClassProjection : Bool {
261+ return matches ( pattern: Self ( . anyValueFields) )
262+ }
263+
264+ /// Returns true if the path has at least one class projection.
265+ /// For example:
266+ /// returns false for `v**`
267+ /// returns true for `v**.c0.s1.v**`
268+ /// returns false for `**` (because '**' can have zero class projections)
269+ public var hasClassProjection : Bool {
270+ var p = self
271+ while true {
272+ let ( k, _, numBits) = p. top
273+ if k == . root { return false }
274+ if k. isClassField { return true }
275+ p = p. pop ( numBits: numBits)
276+ }
277+ }
278+
252279 /// Pops all value field components from the beginning of the path.
253280 /// For example:
254281 /// `s0.e2.3.c4.s1` -> `c4.s1`
@@ -262,7 +289,35 @@ public struct SmallProjectionPath : CustomStringConvertible, CustomReflectable,
262289 p = p. pop ( numBits: numBits)
263290 }
264291 }
265-
292+
293+ /// Pops the last class projection and all following value fields from the tail of the path.
294+ /// For example:
295+ /// `s0.e2.3.c4.s1` -> `s0.e2.3`
296+ /// `v**.c1.c4.s1` -> `v**.c1`
297+ /// `c1.**` -> `c1.**` (because it's unknown how many class projections are in `**`)
298+ public func popLastClassAndValuesFromTail( ) -> SmallProjectionPath {
299+ var p = self
300+ var totalBits = 0
301+ var neededBits = 0
302+ while true {
303+ let ( k, _, numBits) = p. top
304+ if k == . root { break }
305+ if k. isClassField {
306+ neededBits = totalBits
307+ totalBits += numBits
308+ } else {
309+ totalBits += numBits
310+ if !k. isValueField {
311+ // k is `anything`
312+ neededBits = totalBits
313+ }
314+ }
315+ p = p. pop ( numBits: numBits)
316+ }
317+ if neededBits == 64 { return self }
318+ return SmallProjectionPath ( bytes: bytes & ( ( 1 << neededBits) - 1 ) )
319+ }
320+
266321 /// Returns true if this path matches a pattern path.
267322 ///
268323 /// Formally speaking:
@@ -460,6 +515,8 @@ extension SmallProjectionPath {
460515 parsing ( )
461516 merging ( )
462517 matching ( )
518+ predicates ( )
519+ path2path ( )
463520
464521 func basicPushPop( ) {
465522 let p1 = SmallProjectionPath ( . structField, index: 3 )
@@ -569,5 +626,52 @@ extension SmallProjectionPath {
569626 let result = lhs. matches ( pattern: rhs)
570627 precondition ( result == expect)
571628 }
629+
630+ func predicates( ) {
631+ testPredicate ( " v**.c3 " , \. topMatchesAnyValueField, expect: true )
632+ testPredicate ( " ** " , \. topMatchesAnyValueField, expect: true )
633+ testPredicate ( " s0.c3 " , \. topMatchesAnyValueField, expect: false )
634+
635+ testPredicate ( " v** " , \. hasNoClassProjection, expect: true )
636+ testPredicate ( " c0 " , \. hasNoClassProjection, expect: false )
637+ testPredicate ( " 1 " , \. hasNoClassProjection, expect: true )
638+ testPredicate ( " ** " , \. hasNoClassProjection, expect: false )
639+
640+ testPredicate ( " v** " , \. hasClassProjection, expect: false )
641+ testPredicate ( " v**.c0.s1.v** " , \. hasClassProjection, expect: true )
642+ testPredicate ( " c0.** " , \. hasClassProjection, expect: true )
643+ testPredicate ( " c0.c1 " , \. hasClassProjection, expect: true )
644+ testPredicate ( " ct " , \. hasClassProjection, expect: true )
645+ testPredicate ( " s0 " , \. hasClassProjection, expect: false )
646+ }
647+
648+ func testPredicate( _ pathStr: String , _ property: ( SmallProjectionPath ) -> Bool , expect: Bool ) {
649+ var parser = StringParser ( pathStr)
650+ let path = try ! parser. parseProjectionPathFromSIL ( )
651+ let result = property ( path)
652+ precondition ( result == expect)
653+ }
654+
655+ func path2path( ) {
656+ testPath2Path ( " s0.e2.3.c4.s1 " , { $0. popAllValueFields ( ) } , expect: " c4.s1 " )
657+ testPath2Path ( " v**.c4.s1 " , { $0. popAllValueFields ( ) } , expect: " c4.s1 " )
658+ testPath2Path ( " ** " , { $0. popAllValueFields ( ) } , expect: " ** " )
659+
660+ testPath2Path ( " s0.e2.3.c4.s1.e2.v**.** " , { $0. popLastClassAndValuesFromTail ( ) } , expect: " s0.e2.3.c4.s1.e2.v**.** " )
661+ testPath2Path ( " s0.c2.3.c4.s1 " , { $0. popLastClassAndValuesFromTail ( ) } , expect: " s0.c2.3 " )
662+ testPath2Path ( " v**.c*.s1 " , { $0. popLastClassAndValuesFromTail ( ) } , expect: " v** " )
663+ testPath2Path ( " s1.ct.v** " , { $0. popLastClassAndValuesFromTail ( ) } , expect: " s1 " )
664+ testPath2Path ( " c0.c1.c2 " , { $0. popLastClassAndValuesFromTail ( ) } , expect: " c0.c1 " )
665+ testPath2Path ( " ** " , { $0. popLastClassAndValuesFromTail ( ) } , expect: " ** " )
666+ }
667+
668+ func testPath2Path( _ pathStr: String , _ transform: ( SmallProjectionPath ) -> SmallProjectionPath , expect: String ) {
669+ var parser = StringParser ( pathStr)
670+ let path = try ! parser. parseProjectionPathFromSIL ( )
671+ var expectParser = StringParser ( expect)
672+ let expectPath = try ! expectParser. parseProjectionPathFromSIL ( )
673+ let result = transform ( path)
674+ precondition ( result == expectPath)
675+ }
572676 }
573677}
0 commit comments