@@ -73,7 +73,7 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
73
73
case enumCase = 0x3 // A concrete enum case (with payload): syntax e.g. `e4'
74
74
case classField = 0x4 // A concrete class field: syntax e.g. `c1`
75
75
case indexedElement = 0x5 // A constant offset into an array of elements: syntax e.g. 'i2'
76
- // The index must not be 0 and there must not be two successive element indices in the path.
76
+ // The index must be greater than 0 and there must not be two successive element indices in the path.
77
77
78
78
// "Large" kinds: starting from here the low 3 bits must be 1.
79
79
// This and all following kinds (we'll add in the future) cannot have a field index.
@@ -200,12 +200,18 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
200
200
public func push( _ kind: FieldKind , index: Int = 0 ) -> SmallProjectionPath {
201
201
assert ( kind != . anything || bytes == 0 , " 'anything' only allowed in last path component " )
202
202
if ( kind. isIndexedElement) {
203
- if kind == . indexedElement && index == 0 {
204
- // Ignore zero indices
205
- return self
203
+ let ( k, i, numBits) = top
204
+ if kind == . indexedElement {
205
+ if index == 0 {
206
+ // Ignore zero indices
207
+ return self
208
+ }
209
+ if k == . indexedElement {
210
+ // "Merge" two constant successive indexed elements
211
+ return pop ( numBits: numBits) . push ( . indexedElement, index: index + i)
212
+ }
206
213
}
207
- // "Merge" two successive indexed elements
208
- let ( k, _, numBits) = top
214
+ // "Merge" two successive indexed elements which doesn't have a constant result
209
215
if ( k. isIndexedElement) {
210
216
return pop ( numBits: numBits) . push ( . anyIndexedElement)
211
217
}
@@ -474,26 +480,26 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
474
480
return false
475
481
}
476
482
477
- /// Return true if this path is a sub- path of `rhs` or is equivalent to `rhs` .
483
+ /// Subtracts this path from a larger path if this path is a prefix of the other path .
478
484
///
479
485
/// For example:
480
- /// `s0` is a sub-path of `s0.s1`
481
- /// `s0` is not a sub-path of `s1`
482
- /// `s0.s1` is a sub-path of `s0.s1`
483
- /// `i*.s1` is not a sub-path of `i*.s1` because the actual field is unknown on both sides
484
- public func isSubPath ( of rhs: SmallProjectionPath ) -> Bool {
486
+ /// subtracting `s0` from `s0.s1` yields ` s1`
487
+ /// subtracting `s0` from `s1` yields nil, because `s0` is not a prefix of `s1`
488
+ /// subtracting `s0.s1` from `s0.s1` yields an empty path
489
+ /// subtracting `i*.s1` from `i*.s1` yields nil, because the actual index is unknown on both sides
490
+ public func subtract ( from rhs: SmallProjectionPath ) -> SmallProjectionPath ? {
485
491
let ( lhsKind, lhsIdx, lhsBits) = top
486
492
switch lhsKind {
487
493
case . root:
488
- return true
494
+ return rhs
489
495
case . classField, . tailElements, . structField, . tupleField, . enumCase, . existential, . indexedElement:
490
496
let ( rhsKind, rhsIdx, rhsBits) = rhs. top
491
497
if lhsKind == rhsKind && lhsIdx == rhsIdx {
492
- return pop ( numBits: lhsBits) . isSubPath ( of : rhs. pop ( numBits: rhsBits) )
498
+ return pop ( numBits: lhsBits) . subtract ( from : rhs. pop ( numBits: rhsBits) )
493
499
}
494
- return false
500
+ return nil
495
501
case . anything, . anyValueFields, . anyClassField, . anyIndexedElement:
496
- return false
502
+ return nil
497
503
}
498
504
}
499
505
}
@@ -632,9 +638,9 @@ extension SmallProjectionPath {
632
638
basicPushPop ( )
633
639
parsing ( )
634
640
merging ( )
641
+ subtracting ( )
635
642
matching ( )
636
643
overlapping ( )
637
- subPathTesting ( )
638
644
predicates ( )
639
645
path2path ( )
640
646
@@ -652,9 +658,15 @@ extension SmallProjectionPath {
652
658
assert ( p5. pop ( ) . path. isEmpty)
653
659
let p6 = SmallProjectionPath ( . indexedElement, index: 1 ) . push ( . indexedElement, index: 2 )
654
660
let ( k6, i6, p7) = p6. pop ( )
655
- assert ( k6 == . anyIndexedElement && i6 == 0 && p7. isEmpty)
661
+ assert ( k6 == . indexedElement && i6 == 3 && p7. isEmpty)
656
662
let p8 = SmallProjectionPath ( . indexedElement, index: 0 )
657
663
assert ( p8. isEmpty)
664
+ let p9 = SmallProjectionPath ( . indexedElement, index: 1 ) . push ( . anyIndexedElement)
665
+ let ( k9, i9, p10) = p9. pop ( )
666
+ assert ( k9 == . anyIndexedElement && i9 == 0 && p10. isEmpty)
667
+ let p11 = SmallProjectionPath ( . anyIndexedElement) . push ( . indexedElement, index: 1 )
668
+ let ( k11, i11, p12) = p11. pop ( )
669
+ assert ( k11 == . anyIndexedElement && i11 == 0 && p12. isEmpty)
658
670
}
659
671
660
672
func parsing( ) {
@@ -727,6 +739,35 @@ extension SmallProjectionPath {
727
739
assert ( result2 == expect)
728
740
}
729
741
742
+ func subtracting( ) {
743
+ testSubtract ( " s0 " , " s0.s1 " , expect: " s1 " )
744
+ testSubtract ( " s0 " , " s1 " , expect: nil )
745
+ testSubtract ( " s0.s1 " , " s0.s1 " , expect: " " )
746
+ testSubtract ( " i*.s1 " , " i*.s1 " , expect: nil )
747
+ testSubtract ( " ct.s1.0.i3.x " , " ct.s1.0.i3.x " , expect: " " )
748
+ testSubtract ( " c0.s1.0.i3 " , " c0.s1.0.i3.x " , expect: " x " )
749
+ testSubtract ( " s1.0.i3.x " , " s1.0.i3 " , expect: nil )
750
+ testSubtract ( " v**.s1 " , " v**.s1 " , expect: nil )
751
+ testSubtract ( " i* " , " i* " , expect: nil )
752
+ }
753
+
754
+ func testSubtract( _ lhsStr: String , _ rhsStr: String , expect expectStr: String ? ) {
755
+ var lhsParser = StringParser ( lhsStr)
756
+ let lhs = try ! lhsParser. parseProjectionPathFromSIL ( )
757
+ var rhsParser = StringParser ( rhsStr)
758
+ let rhs = try ! rhsParser. parseProjectionPathFromSIL ( )
759
+
760
+ let result = lhs. subtract ( from: rhs)
761
+
762
+ if let expectStr = expectStr {
763
+ var expectParser = StringParser ( expectStr)
764
+ let expect = try ! expectParser. parseProjectionPathFromSIL ( )
765
+ assert ( result! == expect)
766
+ } else {
767
+ assert ( result == nil )
768
+ }
769
+ }
770
+
730
771
func matching( ) {
731
772
testMatch ( " ct " , " c* " , expect: true )
732
773
testMatch ( " c1 " , " c* " , expect: true )
@@ -799,27 +840,6 @@ extension SmallProjectionPath {
799
840
assert ( reversedResult == expect)
800
841
}
801
842
802
- func subPathTesting( ) {
803
- testSubPath ( " s0 " , " s0.s1 " , expect: true )
804
- testSubPath ( " s0 " , " s1 " , expect: false )
805
- testSubPath ( " s0.s1 " , " s0.s1 " , expect: true )
806
- testSubPath ( " i*.s1 " , " i*.s1 " , expect: false )
807
- testSubPath ( " ct.s1.0.i3.x " , " ct.s1.0.i3.x " , expect: true )
808
- testSubPath ( " c0.s1.0.i3 " , " c0.s1.0.i3.x " , expect: true )
809
- testSubPath ( " s1.0.i3.x " , " s1.0.i3 " , expect: false )
810
- testSubPath ( " v**.s1 " , " v**.s1 " , expect: false )
811
- testSubPath ( " i* " , " i* " , expect: false )
812
- }
813
-
814
- func testSubPath( _ lhsStr: String , _ rhsStr: String , expect: Bool ) {
815
- var lhsParser = StringParser ( lhsStr)
816
- let lhs = try ! lhsParser. parseProjectionPathFromSIL ( )
817
- var rhsParser = StringParser ( rhsStr)
818
- let rhs = try ! rhsParser. parseProjectionPathFromSIL ( )
819
- let result = lhs. isSubPath ( of: rhs)
820
- assert ( result == expect)
821
- }
822
-
823
843
func predicates( ) {
824
844
testPredicate ( " v** " , \. hasClassProjection, expect: false )
825
845
testPredicate ( " v**.c0.s1.v** " , \. hasClassProjection, expect: true )
0 commit comments