1 /**
2 Copyright: Copyright Thomas Stuart Bockman 2015
3 License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
4 Authors: Thomas Stuart Bockman
5 */
6 
7 module checkedint;
8 import checkedint.flags;
9 
10 import std.algorithm, std.format, future.traits;
11 static import std.math;
12 
13 @safe:
14 
15 // traits
16 enum isSafeInt(T)    = isInstanceOf!(SafeInt, T);
17 enum isSmartInt(T)   = isInstanceOf!(SmartInt, T);
18 enum isCheckedInt(T) = isSafeInt!T || isSmartInt!T;
19 
20 template hasBitOps(T) {
21     static if(isCheckedInt!T)
22         enum hasBitOps = TemplateArgsOf!T[1];
23     else
24         enum hasBitOps = isFixedPoint!T;
25 }
26 template isThrowingCInt(T) {
27     static if(isCheckedInt!T)
28         enum isThrowingCInt = TemplateArgsOf!T[2];
29     else
30         enum isThrowingCInt = false;
31 }
32 
33 template BasicScalar(T) {
34     static if(isScalarType!T)
35         alias BasicScalar = Unqual!T;
36     else
37     static if(isCheckedInt!T)
38         alias BasicScalar = TemplateArgsOf!T[0];
39     else
40         alias BasicScalar = void;
41 }
42 
43 // conv
44 /+pragma(inline, true) {+/
45     T to(T, bool throws, S)(const S value)
46         if(isScalarType!(BasicScalar!T) && isScalarType!(BasicScalar!S))
47     {
48         import checkedint.internal : toImpl;
49         const T ret = toImpl!(BasicScalar!T, throws)(value.bscal);
50 
51         return ret;
52     }
53     T to(T, S)(S value) {
54         static if(isScalarType!(BasicScalar!T) && isScalarType!(BasicScalar!S))
55             return to!(T, true, S)(value);
56         else {
57             import std.conv : stdTo = to;
58             return stdTo!T(value);
59         }
60     }
61 
62     @property {
63       /+ref N bscal(N)(return ref N num)
64             if(isScalarType!N)
65         {
66             return num;
67         }
68         ref N bscal(N)(return ref N num)
69             if(isCheckedInt!N)
70         {
71             return num.bscal;
72         }+/
73         N bscal(N)(const N num)
74             if(isScalarType!N)
75         {
76             return num;
77         }
78         N bscal(N)(const N num)
79             if(isCheckedInt!N)
80         {
81             return num.bscal;
82         }
83 
84       /+ref N bits(N)(return ref N num)
85             if(isFixedPoint!N)
86         {
87             return num;
88         }
89         ref N bits(N)(return ref N num)
90             if(isCheckedInt!N)
91         {
92             return num.bits;
93         }+/
94         N bits(N)(const N num)
95             if(isFixedPoint!N)
96         {
97             return num;
98         }
99         N bits(N)(const N num)
100             if(isCheckedInt!N)
101         {
102             return num.bits;
103         }
104 
105         Select!(isSigned!N, ptrdiff_t, size_t) idx(N)(const N num)
106             if(isScalarType!N)
107         {
108             return cast(typeof(return))num;
109         }
110         Select!(isSigned!(BasicScalar!N), ptrdiff_t, size_t) idx(N)(const N num)
111             if(isCheckedInt!N)
112         {
113             return num.idx;
114         }
115     }
116 /+}+/
117 
118 // ops
119 public import safeOp = checkedint.safeop;
120 public import smartOp = checkedint.smartop;
121 
122 // checked types
123 /+pragma(inline, true) public {+/
124     // TODO: Convert bitops and throws to std.typecons.Flags?
125 
126     template SafeInt(N, bool bitOps = true, bool throws = true)
127         if(isCheckedInt!N || (isIntegral!N && !isUnqual!N))
128     {
129         alias SafeInt = SafeInt!(BasicScalar!N, bitOps, throws);
130     }
131     struct SafeInt(N, bool bitOps = true, bool throws = true)
132         if(isIntegral!N && isUnqual!N)
133     {
134         /+pure+/ nothrow @nogc {
135             static if(bitOps) {
136                 N bscal;
137                 @property ref typeof(this) bits() {
138                     return this; }
139                 @property ref const(typeof(this)) bits() const {
140                     return this; }
141             } else {
142                 @property ref N bscal() {
143                     return bits.bscal; }
144                 @property ref const(N) bscal() const {
145                     return bits.bscal; }
146                 SafeInt!(N, true, throws) bits;
147             }
148 
149             static if(__VERSION__ >= 2067) {
150                 version(GNU) { static assert(false); }
151                 enum min = typeof(this)(N.min);
152                 enum max = typeof(this)(N.max);
153             } else {
154                 static @property pure nothrow @nogc {
155                     auto min() {
156                         return typeof(this)(N.min); }
157                     auto max() {
158                         return typeof(this)(N.max); }
159                 }
160             }
161 
162             // Construction, assignment, and casting
163             private void checkImplicit(M)()
164                 if(isScalarType!M || isCheckedInt!M)
165             {
166                 alias MB = BasicScalar!M;
167                 static assert(isImplicitlyConvertible!(MB, N),
168                     "SafeInt does not support implicit conversions from " ~
169                     MB.stringof ~ " to " ~ N.stringof ~
170                     ", because they are unsafe when unchecked. Use the explicit checkedint.conv.to()."
171                 );
172             }
173 
174             this(const N bscal) {
175                 this.bscal = bscal; }
176             ref typeof(this) opAssign(const N bscal) {
177                 this.bscal = bscal;
178                 return this;
179             }
180             this(M)(const M that)
181                 if(isScalarType!M)
182             {
183                 checkImplicit!M();
184                 this.bscal = that.bscal;
185             }
186             ref typeof(this) opAssign(M)(const M that)
187                 if(isScalarType!M)
188             {
189                 checkImplicit!M();
190                 this.bscal = that.bscal;
191                 return this;
192             }
193 
194             M opCast(M)() const
195                 if(isFloatingPoint!M)
196             {
197                 return cast(M)bscal;
198             }
199             M opCast(M)() const
200                 if(is(M == bool))
201             {
202                 return bscal != 0;
203             }
204             size_t toHash() const {
205                 return cast(size_t)bscal; }
206 
207             // Comparison
208             bool opEquals(M)(const M right) const
209                 if(isSafeInt!M || isScalarType!M)
210             {
211                 return safeOp.cmp!"=="(this.bscal, right.bscal);
212             }
213             auto opCmp(M)(const M right) const
214                 if(isFloatingPoint!M)
215             {
216                 return
217                     (left <  right)? -1 :
218                     (left >  right)?  1 :
219                     (left == right)?  0 : float.nan;
220             }
221             int opCmp(M)(const M right) const
222                 if(isSafeInt!M || isFixedPoint!M)
223             {
224                 return safeOp.cmp(this.bscal, right.bscal);
225             }
226 
227             // Unary
228             SafeInt!(int, bitOps, throws) popcnt() const {
229                 static assert(bitOps, "Bitwise operations are disabled.");
230 
231                 import future.bitop : stdPC = popcnt;
232                 return typeof(return)(stdPC(bscal));
233             }
234 
235             // Binary
236             M opBinaryRight(string op, M)(const M left) const
237                 if(isFloatingPoint!M)
238             {
239                 return mixin("left " ~ op ~ " bscal");
240             }
241             M opBinary(string op, M)(const M right) const
242                 if(isFloatingPoint!M)
243             {
244                 return mixin("bscal " ~ op ~ " right");
245             }
246 
247             M pow(M)(const M exp) const
248                 if(isFloatingPoint!M)
249             {
250                 return std.math.pow(bscal, exp);
251             }
252         }
253         /+might throw or be impure+/ public {
254             // Construction, assignment, and casting
255             M opCast(M)() const
256                 if(isCheckedInt!M || isIntegral!M || isSomeChar!M)
257             {
258                 return to!(M, throws)(bscal);
259             }
260             @property Select!(isSigned!N, ptrdiff_t, size_t) idx() const {
261                 return to!(typeof(return), throws)(bscal);
262             }
263 
264             void toString(Writer, Char)(Writer sink, FormatSpec!Char fmt = (FormatSpec!Char).init) const @trusted {
265                 formatValue(sink, bscal, fmt); }
266             string toString() const {
267                 return to!string(bscal); }
268 
269             // Unary
270             typeof(this) opUnary(string op)() const
271                 if(op.among!("-", "+", "~"))
272             {
273                 static assert(bitOps || (op != "~"),
274                     "Bitwise operations are disabled.");
275 
276                 return typeof(return)(safeOp.unary!(op, throws)(bscal));
277             }
278             ref typeof(this) opUnary(string op)()
279                 if(op.among!("++", "--"))
280             {
281                 safeOp.unary!(op, throws)(bscal);
282                 return this;
283             }
284 
285             typeof(this) abs() const {
286                 return typeof(return)(safeOp.abs!throws(bscal)); }
287 
288             SafeInt!(int, bitOps, throws) bsf() const {
289                 static assert(bitOps, "Bitwise operations are disabled.");
290 
291                 return typeof(return)(safeOp.bsf!throws(bscal));
292             }
293             SafeInt!(int, bitOps, throws) bsr() const {
294                 static assert(bitOps, "Bitwise operations are disabled.");
295 
296                 return typeof(return)(safeOp.bsr!throws(bscal));
297             }
298 
299             SafeInt!(int, bitOps, throws) ilogb() const {
300                 return typeof(return)(safeOp.ilogb!throws(bscal)); }
301 
302             // Binary
303             SafeInt!(OpType!(M, op, N), bitOps, throws) opBinaryRight(string op, M)(const M left) const
304                 if(isFixedPoint!M)
305             {
306                 static assert(bitOps || !op.among!("<<", ">>", ">>>", "&", "|", "^"),
307                     "Bitwise operations are disabled.");
308 
309                 return typeof(return)(safeOp.binary!(op, throws)(left, bscal));
310             }
311             SafeInt!(OpType!(N, op, BasicScalar!M), bitOps && hasBitOps!M, throws || isThrowingCInt!M) opBinary(string op, M)(const M right) const
312                 if(isSafeInt!M || isFixedPoint!M)
313             {
314                 static assert((bitOps && hasBitOps!M) || !op.among!("<<", ">>", ">>>", "&", "|", "^"),
315                     "Bitwise operations are disabled.");
316 
317                 return typeof(return)(safeOp.binary!(op, throws || isThrowingCInt!M)(this.bscal, right.bscal));
318             }
319 
320             ref typeof(this) opOpAssign(string op, M)(const M right)
321                 if(isSafeInt!M || isFixedPoint!M)
322             {
323                 static assert((bitOps && hasBitOps!M) || !op.among!("<<", ">>", ">>>", "&", "|", "^"),
324                     "Bitwise operations are disabled.");
325                 checkImplicit!(OpType!(N, op, M))();
326 
327                 safeOp.binary!(op ~ "=", throws || isThrowingCInt!M)(this.bscal, right.bscal);
328                 return this;
329             }
330 
331             SafeInt!(CallType!(std.math.pow, N, BasicScalar!M), bitOps && hasBitOps!M, throws || isThrowingCInt!M) pow(M)(const M exp) const
332                 if(isSafeInt!M || isFixedPoint!M)
333             {
334                 return typeof(return)(safeOp.pow!(throws || isThrowingCInt!M)(this.bscal, exp.bscal));
335             }
336         }
337     }
338 
339     template SmartInt(N, bool bitOps = true, bool throws = true)
340         if(isCheckedInt!N || (isIntegral!N && !isUnqual!N))
341     {
342         alias SmartInt = SmartInt!(BasicScalar!N, bitOps, throws);
343     }
344     struct SmartInt(N, bool bitOps = true, bool throws = true)
345         if(isIntegral!N && isUnqual!N)
346     {
347         /+pure+/ nothrow @nogc {
348             static if(bitOps) {
349                 N bscal;
350                 @property ref typeof(this) bits() {
351                     return this; }
352                 @property ref const(typeof(this)) bits() const {
353                     return this; }
354             } else {
355                 @property ref N bscal() {
356                     return bits.bscal; }
357                 @property ref const(N) bscal() const {
358                     return bits.bscal; }
359                 SmartInt!(N, true, throws) bits;
360             }
361 
362             static if(__VERSION__ >= 2067) {
363                 version(GNU) { static assert(false); }
364                 enum min = typeof(this)(N.min);
365                 enum max = typeof(this)(N.max);
366             } else {
367                 static @property pure nothrow @nogc {
368                     auto min() {
369                         return typeof(this)(N.min); }
370                     auto max() {
371                         return typeof(this)(N.max); }
372                 }
373             }
374 
375             // Construction, assignment, and casting
376             this(const N bscal) {
377                 this.bscal = bscal; }
378             ref typeof(this) opAssign(const N bscal) {
379                 this.bscal = bscal;
380                 return this;
381             }
382             this(M)(const M that)
383                 if(isScalarType!M || isCheckedInt!M)
384             {
385                 this.bscal = to!(N, throws)(that);
386             }
387             ref typeof(this) opAssign(M)(const M that)
388                 if(isScalarType!M || isCheckedInt!M)
389             {
390                 this.bscal = to!(N, throws)(that);
391                 return this;
392             }
393 
394             M opCast(M)() const
395                 if(isFloatingPoint!M)
396             {
397                 return cast(M)bscal;
398             }
399             M opCast(M)() const
400                 if(is(M == bool))
401             {
402                 return bscal != 0;
403             }
404             size_t toHash() const {
405                 return cast(size_t)bscal; }
406 
407             // Comparison
408             bool opEquals(M)(const M right) const
409                 if(isScalarType!M || isCheckedInt!M)
410             {
411                 return smartOp.cmp!"=="(this.bscal, right.bscal);
412             }
413             auto opCmp(M)(const M right) const
414                 if(isFloatingPoint!M)
415             {
416                 return
417                     (bscal <  right)? -1 :
418                     (bscal >  right)?  1 :
419                     (bscal == right)?  0 : float.nan;
420             }
421             int opCmp(M)(const M right) const
422                 if(isScalarType!M || isCheckedInt!M)
423             {
424                 return smartOp.cmp(this.bscal, right.bscal);
425             }
426 
427             // Unary
428             typeof(this) opUnary(string op)() const
429                 if(op == "~")
430             {
431                 static assert(bitOps,
432                     "Bitwise operations are disabled.");
433 
434                 return typeof(return)(smartOp.unary!(op, throws)(bscal));
435             }
436 
437             SmartInt!(Unsigned!N, bitOps, throws) abs() const {
438                 return typeof(return)(smartOp.abs(bscal));
439             }
440 
441             SmartInt!(int, bitOps, throws) popcnt() const {
442                 static assert(bitOps, "Bitwise operations are disabled.");
443 
444                 import future.bitop : stdPC = popcnt;
445                 return typeof(return)(stdPC(bscal));
446             }
447 
448             // Binary
449             auto opBinaryRight(string op, M)(const M left) const
450                 if(isFloatingPoint!M)
451             {
452                 return smartOp.binary!op(left, bscal);
453             }
454             auto opBinary(string op, M)(const M right) const
455                 if(isFloatingPoint!M)
456             {
457                 return smartOp.binary!op(bscal, right);
458             }
459 
460             auto mulPow2(M)(const M exp) const
461                 if(isFloatingPoint!M)
462             {
463                 return smartOp.mulPow2(bscal, exp);
464             }
465             auto divPow2(M)(const M exp) const
466                 if(isFloatingPoint!M)
467             {
468                 return smartOp.divPow2(bscal, exp);
469             }
470 
471             auto pow(M)(const M exp) const
472                 if(isFloatingPoint!M)
473             {
474                 return std.math.pow(bscal, exp);
475             }
476         }
477         /+might throw or be impure+/ public {
478             // Construction, assignment, and casting
479             M opCast(M)() const
480                 if(isCheckedInt!M || isIntegral!M || isSomeChar!M)
481             {
482                 return to!(M, throws)(bscal);
483             }
484             @property Select!(isSigned!N, ptrdiff_t, size_t) idx() const {
485                 return to!(typeof(return), throws)(bscal);
486             }
487 
488             void toString(Writer, Char)(Writer sink, FormatSpec!Char fmt = (FormatSpec!Char).init) const @trusted {
489                 formatValue(sink, bscal, fmt); }
490             string toString() const {
491                 return to!string(bscal); }
492 
493             // Unary
494             SmartInt!(Signed!N, bitOps, throws) opUnary(string op)() const
495                 if(op == "+" || op == "-")
496             {
497                 return typeof(return)(smartOp.unary!(op, throws)(bscal));
498             }
499             ref typeof(this) opUnary(string op)()
500                 if(op.among!("++", "--"))
501             {
502                 smartOp.unary!(op, throws)(bscal);
503                 return this;
504             }
505 
506             SmartInt!(ubyte, bitOps, throws) bsf() const {
507                 static assert(bitOps, "Bitwise operations are disabled.");
508 
509                 return typeof(return)(smartOp.bsf!throws(bscal));
510             }
511             SmartInt!(ubyte, bitOps, throws) bsr() const {
512                 static assert(bitOps, "Bitwise operations are disabled.");
513 
514                 return typeof(return)(smartOp.bsr!throws(bscal));
515             }
516 
517             SmartInt!(ubyte, bitOps, throws) ilogb() const {
518                 return typeof(return)(smartOp.ilogb!throws(bscal)); }
519 
520             // Binary
521             auto opBinaryRight(string op, M)(const M left) const
522                 if(isFixedPoint!M || isSafeInt!M)
523             {
524                 static assert((bitOps && hasBitOps!M) || !op.among!("<<", ">>", ">>>", "&", "|", "^"),
525                     "Bitwise operations are disabled.");
526 
527                 const wret = smartOp.binary!(op, throws || isThrowingCInt!M)(left.bscal, this.bscal);
528                 return SmartInt!(typeof(wret), bitOps && hasBitOps!M, throws || isThrowingCInt!M)(wret);
529             }
530             auto opBinary(string op, M)(const M right) const
531                 if(isFixedPoint!M || isCheckedInt!M)
532             {
533                 static assert((bitOps && hasBitOps!M) || !op.among!("<<", ">>", ">>>", "&", "|", "^"),
534                     "Bitwise operations are disabled.");
535 
536                 const wret = smartOp.binary!(op, throws || isThrowingCInt!M)(this.bscal, right.bscal);
537                 return SmartInt!(typeof(wret), bitOps && hasBitOps!M, throws || isThrowingCInt!M)(wret);
538             }
539             ref typeof(this) opOpAssign(string op, M)(const M right)
540                 if(isScalarType!M || isCheckedInt!M)
541             {
542                 static assert((bitOps && hasBitOps!M) || !op.among!("<<", ">>", ">>>", "&", "|", "^"),
543                     "Bitwise operations are disabled.");
544 
545                 return opAssign(smartOp.binary!(op, throws || isThrowingCInt!M)(this.bscal, right.bscal));
546               /+smartOp.binary!(op ~ "=", throws || isThrowingCInt!M)(this.bscal, right.bscal);
547                 return this;+/
548             }
549 
550             auto mulPow2(M)(const M exp) const
551                 if(isFixedPoint!M || isCheckedInt!M)
552             {
553                 const wret = smartOp.mulPow2!throws(this.bscal, exp.bscal);
554                 return SmartInt!(typeof(wret), bitOps && hasBitOps!M, throws || isThrowingCInt!M)(wret);
555             }
556             auto divPow2(M)(const M exp) const
557                 if(isFixedPoint!M || isCheckedInt!M)
558             {
559                 const wret = smartOp.divPow2!throws(this.bscal, exp.bscal);
560                 return SmartInt!(typeof(wret), bitOps && hasBitOps!M, throws || isThrowingCInt!M)(wret);
561             }
562 
563             auto pow(M)(const M exp) const
564                 if(isFixedPoint!M || isCheckedInt!M)
565             {
566                 const wret = smartOp.pow!throws(this.bscal, exp.bscal);
567                 return SmartInt!(typeof(wret), bitOps && hasBitOps!M, throws || isThrowingCInt!M)(wret);
568             }
569         }
570     }
571 /+}+/