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.flags;
8 
9 import core.bitop, std.algorithm, std.array, std.format, std.range/+.primitives+/;
10 
11 @safe:
12 
13 // flags
14 struct IntFlag {
15 pure: nothrow: @nogc: /+pragma(inline, true):+/
16 private:
17     uint index;
18     this(uint index) {
19         assert(index < strs.length);
20         this.index = index;
21     }
22 
23     private static immutable strs = [
24         "{NULL}",
25         "{undefined result}",
26         "{divide by zero}",
27         "{imaginary component}",
28         "{overflow}",
29         "{positive overflow}",
30         "{negative overflow}"
31     ];
32 public:
33     static if(__VERSION__ >= 2067) {
34         version(GNU) { static assert(false); }
35         enum {
36             undef   = IntFlag(1),
37             div0    = IntFlag(2),
38             imag    = IntFlag(3),
39             over    = IntFlag(4),
40             posOver = IntFlag(5),
41             negOver = IntFlag(6)
42         }
43     } else {
44         static @property {
45             auto undef() {
46                 return IntFlag(1); }
47             auto div0() {
48                 return IntFlag(2); }
49             auto imag() {
50                 return IntFlag(3); }
51             auto over() {
52                 return IntFlag(4); }
53             auto posOver() {
54                 return IntFlag(5); }
55             auto negOver() {
56                 return IntFlag(6); }
57         }
58     }
59 
60     @property {
61         bool isNull() const {
62             return index == 0; }
63         IntFlags mask() const {
64             return IntFlags(1u << index); }
65         alias mask this;
66 
67         string desc() const {
68             return strs[index][1 .. ($ - 1)]; }
69     }
70     void toString(Writer, Char)(Writer sink, FormatSpec!Char fmt = (FormatSpec!Char).init) const @trusted {
71         formatValue(sink, strs[index], fmt); }
72     string toString() const {
73         return strs[index]; }
74 }
75 
76 struct IntFlags {
77     pure nothrow @nogc /+pragma(inline, true)+/ {
78     private:
79         uint bits = 0;
80         this(uint bits) {
81             this.bits = bits;
82         }
83 
84     public:
85         this(IntFlags that) {
86             bits = that.bits; }
87         ref IntFlags opAssign(IntFlags that) {
88             bits = that.bits;
89             return this;
90         }
91         void clear() {
92             bits = 0; }
93 
94         IntFlags opBinary(string op)(IntFlags that) const
95             if(op.among!("&", "|", "-"))
96         {
97             IntFlags ret = this;
98             return ret.opOpAssign!op(that);
99         }
100         ref IntFlags opOpAssign(string op)(IntFlags that)
101             if(op.among!("&", "|", "-"))
102         {
103             static if(op == "&")
104                 bits &= that.bits;
105             else
106             static if(op == "|")
107                 bits |= that.bits;
108             else
109             static if(op == "-")
110                 bits &= ~(that.bits);
111 
112             return this;
113         }
114 
115         @property bool anySet() const {
116             return bits > 1; }
117         alias anySet this;
118 
119         @property bool empty() const {
120             return bits <= 1; }
121         @property IntFlag front() const {
122             // bsr() is undefined for 0.
123             return IntFlag(bsr(bits | 1));
124         }
125         ref IntFlags popFront() {
126             // bsr() is undefined for 0.
127             bits &= ~(1u << bsr(bits | 1));
128             return this;
129         }
130         @property IntFlags save() const {
131             return this; }
132         @property uint length() const {
133             return popcnt(bits); }
134 
135         unittest {
136             import std.range : isForwardRange, hasLength;
137             static assert(isForwardRange!IntFlags);
138             static assert(hasLength!IntFlags);
139         }
140 
141         static IntFlags local;
142         enum string pushPop = r"
143 IntFlags outerIntFlags = IntFlags.local;
144 scope(exit) {
145     assert(IntFlags.local.empty);
146     IntFlags.local = outerIntFlags;
147 }";
148     }
149     public {
150         void toString(Writer, Char)(Writer sink, FormatSpec!Char fmt) const @trusted {
151             put(sink, '{');
152 
153             bool first = true;
154             foreach(fd; this.save()) {
155                 if(first)
156                     first = false;
157                 else
158                     put(sink, ", ");
159                 put(sink, fd.desc);
160             }
161 
162             put(sink, '}');
163         }
164         void toString(Writer)(Writer sink) const {
165             toString(sink, (FormatSpec!char).init); }
166         string toString() const {
167             if(length == 1)
168                 return front.toString();
169             else {
170                 auto buff = appender!string();
171                 toString(buff);
172                 return cast(immutable)(buff.data);
173             }
174         }
175     }
176 }
177 
178 class CheckedIntException : Exception {
179     const IntFlags intFlags;
180 
181 private:
182     enum msg0 = "Integer math exception(s): ";
183     private this(IntFlag flag, string fn = __FILE__, size_t ln = __LINE__) pure nothrow {
184         intFlags = flag;
185         super(msg0 ~ flag.toString(), fn, ln);
186     }
187     private this(IntFlags flags, string fn = __FILE__, size_t ln = __LINE__) pure nothrow {
188         intFlags = flags;
189 
190         auto buff = appender(msg0);
191         flags.toString(buff);
192 
193         super(cast(immutable)(buff.data), fn, ln);
194     }
195 }
196 
197 /+pragma(inline, false) {+/
198     /* The throwing versions of raise() must not be inlined, as doing so tends to
199        prevent the caller from being inlined, at least on DMD. */
200 
201     void raise(bool throws)(IntFlag flag)
202         if(throws)
203     {
204         throw new CheckedIntException(flag);
205     }
206     void raise(bool throws)(IntFlags flags)
207         if(throws)
208     {
209         if(flags.anySet)
210             throw new CheckedIntException(flags);
211     }
212 /+}
213 pragma(inline, true) {+/
214     void raise(bool throws)(IntFlag flag)
215         if(!throws)
216     {
217         IntFlags.local |= flag;
218     }
219     void raise(IntFlag flag) {
220         raise!true(flag); }
221 
222     void raise(bool throws = true)(IntFlags flags)
223         if(!throws)
224     {
225         IntFlags.local |= flags;
226     }
227     void raise(IntFlags flags) {
228         raise!true(flags); }
229 /+}+/