@@ -42,7 +42,7 @@ public struct SmallProjectionPath : CustomStringConvertible, CustomReflectable,
42
42
/// The physical representation of the path. The path components are stored in
43
43
/// reverse order: the first path component is stored in the lowest bits (LSB),
44
44
/// 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
46
46
/// by the "index-kind" main byte (from LSB to MSB).
47
47
///
48
48
/// index overflow byte: bit-nr: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
@@ -70,10 +70,10 @@ public struct SmallProjectionPath : CustomStringConvertible, CustomReflectable,
70
70
case classField = 0x4 // A concrete class field: syntax e.g. `c1`
71
71
case tailElements = 0x5 // A tail allocated element of a class: syntax `ct`
72
72
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
+
75
74
// "Large" kinds: starting from here the low 3 bits must be 1.
76
75
// 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*`
77
77
case anything = 0xf // Any number of any fields: syntax `**`
78
78
79
79
public var isValueField : Bool {
@@ -180,7 +180,10 @@ public struct SmallProjectionPath : CustomStringConvertible, CustomReflectable,
180
180
assert ( kind != . anything || bytes == 0 , " 'anything' only allowed in last path component " )
181
181
var idx = index
182
182
var b = bytes
183
- if ( b >> 56 ) != 0 { return Self ( . anything) }
183
+ if ( b >> 56 ) != 0 {
184
+ // Overflow
185
+ return Self ( . anything)
186
+ }
184
187
b = ( b << 8 ) | UInt64 ( ( ( idx & 0xf ) << 4 ) | ( kind. rawValue << 1 ) )
185
188
idx >>= 4
186
189
while idx != 0 {
@@ -242,13 +245,37 @@ public struct SmallProjectionPath : CustomStringConvertible, CustomReflectable,
242
245
/// returns true for `v**.c3`
243
246
/// returns true for `**`
244
247
/// returns false for `s0.c3` (because e.g. `s1` would not match)
245
- public var matchesAllValueFields : Bool {
248
+ public var topMatchesAnyValueField : Bool {
246
249
switch top. kind {
247
250
case . anyValueFields, . anything: return true
248
251
default : return false
249
252
}
250
253
}
251
254
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
+
252
279
/// Pops all value field components from the beginning of the path.
253
280
/// For example:
254
281
/// `s0.e2.3.c4.s1` -> `c4.s1`
@@ -262,7 +289,35 @@ public struct SmallProjectionPath : CustomStringConvertible, CustomReflectable,
262
289
p = p. pop ( numBits: numBits)
263
290
}
264
291
}
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
+
266
321
/// Returns true if this path matches a pattern path.
267
322
///
268
323
/// Formally speaking:
@@ -460,6 +515,8 @@ extension SmallProjectionPath {
460
515
parsing ( )
461
516
merging ( )
462
517
matching ( )
518
+ predicates ( )
519
+ path2path ( )
463
520
464
521
func basicPushPop( ) {
465
522
let p1 = SmallProjectionPath ( . structField, index: 3 )
@@ -569,5 +626,52 @@ extension SmallProjectionPath {
569
626
let result = lhs. matches ( pattern: rhs)
570
627
precondition ( result == expect)
571
628
}
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
+ }
572
676
}
573
677
}
0 commit comments