@@ -19,15 +19,69 @@ public struct NumberTextBounds<Value: NumberTextValue>: Equatable {
1919 // MARK: State
2020 //=------------------------------------------------------------------------=
2121
22- @usableFromInline let min : Value
23- @usableFromInline let max : Value
24-
22+ @usableFromInline var bounds : ClosedRange < Value >
23+
2524 //=------------------------------------------------------------------------=
2625 // MARK: Initializers
2726 //=------------------------------------------------------------------------=
2827
29- @inlinable init ( unchecked: ( min: Value , max: Value ) ) {
30- ( self . min, self . max) = unchecked; precondition ( min <= max)
28+ @inlinable init ( unchecked: ( lower: Value , upper: Value ) ) {
29+ self . bounds = ClosedRange ( uncheckedBounds: unchecked)
30+ }
31+
32+ //=------------------------------------------------------------------------=
33+ // MARK: Accessors
34+ //=------------------------------------------------------------------------=
35+
36+ @inlinable var min : Value {
37+ bounds. lowerBound
38+ }
39+
40+ @inlinable var max : Value {
41+ bounds. upperBound
42+ }
43+
44+ //=------------------------------------------------------------------------=
45+ // MARK: Utilities
46+ //=------------------------------------------------------------------------=
47+
48+ @inlinable func location( of value: Value ) -> Location ? {
49+ //=--------------------------------------=
50+ // Value Is Not Maxed Out
51+ //=--------------------------------------=
52+ if min < value && value < max { return . body }
53+ //=--------------------------------------=
54+ // Value == Max
55+ //=--------------------------------------=
56+ if value == max { return Location ( edge: value > . zero || min == max) }
57+ //=--------------------------------------=
58+ // Value == Min
59+ //=--------------------------------------=
60+ if value == min { return Location ( edge: value < . zero) }
61+ //=--------------------------------------=
62+ // Value Is Out Of Bounds
63+ //=--------------------------------------=
64+ return nil
65+ }
66+
67+ //*========================================================================*
68+ // MARK: Declaration
69+ //*========================================================================*
70+
71+ @usableFromInline enum Location {
72+
73+ //=--------------------------------------------------------------------=
74+ // MARK: Instances
75+ //=--------------------------------------------------------------------=
76+
77+ case body
78+ case edge
79+
80+ //=--------------------------------------------------------------------=
81+ // MARK: Initializers
82+ //=--------------------------------------------------------------------=
83+
84+ @inlinable init ( edge: Bool ) { self = edge ? . edge : . body }
3185 }
3286}
3387
@@ -99,21 +153,28 @@ extension NumberTextBounds {
99153 //=------------------------------------------------------------------------=
100154
101155 @inlinable func autocorrect( _ value: inout Value ) {
102- value = Swift . min ( Swift . max ( min, value) , max)
156+ //=--------------------------------------=
157+ // Lower Bound
158+ //=--------------------------------------=
159+ if value < min {
160+ Info . print ( autocorrection: [ . mark( value) , " < " , . note( min) ] )
161+ value = min; return
162+ }
163+ //=--------------------------------------=
164+ // Upper Bound
165+ //=--------------------------------------=
166+ if value > max {
167+ Info . print ( autocorrection: [ . mark( value) , " > " , . note( max) ] )
168+ value = max; return
169+ }
103170 }
104171
105172 //=------------------------------------------------------------------------=
106173 // MARK: Number
107174 //=------------------------------------------------------------------------=
108175
109- @inlinable func autocorrect( _ number: inout Number ) {
110- autocorrect ( & number. sign)
111- }
112-
113- /// Toggles the sign if the opposite sign is the only sign allowed.
114- @inlinable func autocorrect( _ sign: inout Sign ) {
115- if ( sign == . positive && max <= . zero && min != . zero )
116- || ( sign == . negative && min >= . zero) { sign. toggle ( ) }
176+ @inlinable func autocorrect( _ number: inout Number ) {
177+ autocorrectSignOnUnambigiousBounds ( & number. sign)
117178 }
118179}
119180
@@ -128,40 +189,42 @@ extension NumberTextBounds {
128189 //=------------------------------------------------------------------------=
129190
130191 @inlinable func autovalidate( _ number: inout Number ) throws {
131- try autovalidate ( & number. sign)
132- }
133-
134- @inlinable func autovalidate( _ sign: inout Sign ) throws {
135- let autocorrected = sign. transformed ( autocorrect)
136- if autocorrected != sign {
137- Info . print ( autocorrection: [ . mark( sign) , " is not in " , . mark( self ) ] )
138- sign = autocorrected
139- }
192+ autocorrectSignOnUnambigiousBounds ( & number. sign)
140193 }
141194
142195 //=------------------------------------------------------------------------=
143196 // MARK: Value
144197 //=------------------------------------------------------------------------=
145198
146199 @inlinable func autovalidate( _ value: Value , _ number: inout Number ) throws {
147- if try edge ( value) , number. removeSeparatorAsSuffix ( ) {
148- Info . print ( autocorrection: [ . mark( number) , " does not fit a fraction separator " ] )
149- }
150- }
151-
152- @inlinable func edge( _ value: Value ) throws -> Bool {
153- if min < value && value < max { return false }
154- //=--------------------------------------=
155- // Value == Max
156200 //=--------------------------------------=
157- if value == max { return value > . zero || min == max }
201+ // Location
158202 //=--------------------------------------=
159- // Value == Min
160- //=--------------------------------------=
161- if value == min { return value < . zero }
203+ guard let location = location ( of : value ) else {
204+ throw Info ( [ . mark ( value ) , " is not in " , . note ( self ) ] )
205+ }
162206 //=--------------------------------------=
163- // Value Is Out Of Bounds
207+ // Remove Separator On Value Is Maxed Out
164208 //=--------------------------------------=
165- throw Info ( [ . mark( value) , " is not in " , . mark( self ) ] )
209+ if location == . edge, number. removeSeparatorAsSuffix ( ) {
210+ Info . print ( autocorrection: [ . mark( number) , " does not fit a fraction separator " ] )
211+ }
212+ }
213+ }
214+
215+ //=----------------------------------------------------------------------------=
216+ // MARK: Upstream, Downstream
217+ //=----------------------------------------------------------------------------=
218+
219+ extension NumberTextBounds {
220+
221+ //=------------------------------------------------------------------------=
222+ // MARK: Number
223+ //=------------------------------------------------------------------------=
224+
225+ @inlinable func autocorrectSignOnUnambigiousBounds( _ sign: inout Sign ) {
226+ guard let correct = Sign ( of: bounds) , sign != correct else { return }
227+ Info . print ( autocorrection: [ . mark( sign) , " is not in " , . note( self ) ] )
228+ sign = correct
166229 }
167230}
0 commit comments