1 /++
2 `@nogc` exceptions and errors definitions.
3 
4 Most of the API Requires DIP1008.
5 +/
6 module mir.exception;
7 
8 version(D_Exceptions):
9 
10 version(D_Ddoc)
11     private enum _version_D_Ddoc = true;
12 else
13     private enum _version_D_Ddoc = false;
14 
15 private enum NOGCEXP = __traits(compiles, (()@nogc {throw new Exception("");})());
16 private enum HASFORMAT = __traits(compiles, (()@nogc {import mir.format;})());
17 
18 package template staticException(string fmt, string file, int line)
19 {
20     static immutable staticException = new Exception(fmt, file, line);
21 }
22 
23 ///
24 auto ref enforce(string fmt, string file = __FILE__, int line = __LINE__, Expr)(scope auto return ref Expr arg) @trusted
25 {
26     version(LDC) pragma(inline, true);
27     import core.lifetime: forward;
28     import mir.utility: _expect;
29     static if (__traits(compiles, arg !is null))
30     {
31         if (_expect(arg !is null, true))
32             return forward!arg;
33     }
34     else
35     {
36         if (_expect(cast(bool)arg, true))
37             return forward!arg;
38     }
39     throw staticException!(fmt, file, line);
40 }
41 
42 ///
43 @safe pure nothrow @nogc
44 version (mir_core_test) unittest
45 {
46     import mir.exception;
47     try enforce!"Msg"(false);
48     catch(Exception e) assert(e.msg == "Msg");
49 }
50 
51 /++
52 +/
53 class MirException : Exception
54 {
55     ///
56     mixin MirThrowableImpl;
57 }
58 
59 /// Generic style
60 version (mir_test) static if (NOGCEXP && HASFORMAT)
61 @safe pure nothrow @nogc
62 unittest
63 {
64     static if (__traits(compiles, (()@nogc {import mir.format;})()))
65     {
66         import mir.exception;
67         try throw new MirException("Hi D", 2, "!");
68         catch(Exception e) assert(e.msg == "Hi D2!");
69     }
70 }
71 
72 /// C++ style
73 version (mir_test) static if (NOGCEXP && HASFORMAT)
74 @safe pure nothrow @nogc
75 unittest
76 {
77     static if (__traits(compiles, (()@nogc {import mir.format;})()))
78     {
79         import mir.exception;
80         import mir.format;
81         try throw new MirException(stringBuf() << "Hi D" << 2 << "!" << getData);
82         catch(Exception e) assert(e.msg == "Hi D2!");
83     }
84 }
85 
86 /// Low-level style
87 version (mir_test) static if (NOGCEXP && HASFORMAT)
88 @safe pure nothrow @nogc
89 unittest
90 {
91     static if (__traits(compiles, (()@nogc {import mir.format;})()))
92     {
93         import mir.exception;
94         import mir.format;
95         stringBuf buf;
96         try throw new MirException(buf.print( "Hi D", 2, "!").data);
97         catch(Exception e) assert(e.msg == "Hi D2!");
98     }
99 }
100 
101 ///
102 version (mir_core_test) static if (NOGCEXP)
103 @safe pure nothrow @nogc
104 unittest
105 {
106     @safe pure nothrow @nogc 
107     bool func(scope const(char)[] msg)
108     {
109         /// scope messages are copied
110         try throw new MirException(msg);
111         catch(Exception e) assert(e.msg == msg);
112 
113         /// immutable strings are not copied
114         static immutable char[] gmsg = "global msg";
115         try throw new MirException(gmsg);
116         catch(Exception e) assert(e.msg is gmsg);
117 
118         return __ctfe;
119     }
120 
121     assert(func("runtime-time check") == 0);
122 
123     static assert(func("compile-time check") == 1);
124 }
125 
126 // ///
127 // auto ref enforce(T, Args...)(scope auto return ref T arg, lazy @nogc Args args, string file = __FILE__, int line = __LINE__) @nogc
128 //     if (Args.length)
129 // {
130 //     import mir.utility: _expect;
131 //     static if (__traits(compiles, arg !is null))
132 //     {
133 //         if (_expect(arg !is null, true))
134 //             return arg;
135 //     }
136 //     else
137 //     {
138 //         if (_expect(cast(bool)arg, true))
139 //             return arg;
140 //     }
141 //     import mir.format;
142 //     stringBuf buf;
143 //     throw new MirException(buf.print(args).data, file, line);
144 // }
145 
146 // ///
147 // @safe pure nothrow @nogc
148 // version (mir_core_test) unittest static if (NOGCEXP && HASFORMAT)
149 // {
150 //     import mir.exception;
151 //     try enforce(false, "Hi D", 2, "!");
152 //     catch(Exception e) assert(e.msg == "Hi D2!");
153 // }
154 
155 // ///
156 // auto ref enforce(T)(scope auto return ref T arg, lazy scope const(char)[] msg, string file = __FILE__, int line = __LINE__) @nogc
157 // {
158 //     import core.lifetime: forward;
159 //     import mir.utility: _expect;
160 //     static if (__traits(compiles, arg !is null))
161 //     {
162 //         if (_expect(arg !is null, true))
163 //             return forward!arg[0];
164 //     }
165 //     else
166 //     {
167 //         if (_expect(cast(bool)arg, true))
168 //             return forward!arg[0];
169 //     }
170 //     throw new MirException(msg, file, line);
171 // }
172 
173 // ///
174 // @safe pure nothrow @nogc
175 // version (mir_core_test) unittest static if (NOGCEXP && HASFORMAT)
176 // {
177 //     import mir.exception;
178 //     try enforce(false, "Msg");
179 //     catch(Exception e) assert(e.msg == "Msg");
180 // }
181 
182 
183 /++
184 +/
185 class MirError : Error
186 {
187     ///
188     mixin MirThrowableImpl;
189 }
190 
191 ///
192 @system pure nothrow @nogc
193 version (mir_test) static if (NOGCEXP)
194 unittest
195 {
196     @system pure nothrow @nogc 
197     bool func(scope const(char)[] msg)
198     {
199         /// scope messages are copied
200         try throw new MirException(msg);
201         catch(Exception e) assert(e.msg == msg);
202     
203         /// immutable strings are not copied
204         static immutable char[] gmsg = "global msg";
205         try throw new MirError(gmsg);
206         catch(Error e) assert(e.msg is gmsg);
207     
208         return __ctfe;
209     }
210 
211     assert(func("runtime-time check") == 0);
212 
213     static assert(func("compile-time check") == 1);
214 }
215 
216 /++
217 +/
218 mixin template MirThrowableImpl()
219 {
220     private bool _global;
221     private char[maxMirExceptionMsgLen] _payload = void;
222     import mir.exception: maxMirExceptionMsgLen, mirExceptionInitilizePayloadImpl;
223 
224     /++
225     Params:
226         msg = message. No-scope `msg` is assumed to have the same lifetime as the throwable. scope strings are copied to internal buffer.
227         file = file name, zero terminated global string
228         line = line number
229         nextInChain = next exception in the chain (optional)
230     +/
231     @nogc @safe pure nothrow this(scope const(char)[] msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null)
232     {
233         super((() @trusted => cast(immutable) mirExceptionInitilizePayloadImpl(_payload, msg))(), file, line, nextInChain);
234     }
235 
236     /// ditto
237     @nogc @safe pure nothrow this(scope const(char)[] msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__)
238     {
239         super((() @trusted => cast(immutable) mirExceptionInitilizePayloadImpl(_payload, msg))(), file, line, nextInChain);
240     }
241 
242     /// ditto
243     @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null)
244     {
245         this._global = true;
246         super(msg, file, line, nextInChain);
247     }
248 
249     /// ditto
250     @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__)
251     {
252         this._global = true;
253         super(msg, file, line, nextInChain);
254     }
255 
256     ///
257     ~this() @trusted
258     {
259         import mir.internal.memory: free;
260         if (!_global && msg.ptr != _payload.ptr)
261             free(cast(void*)msg.ptr);
262     }
263 
264     /++
265     Generic multiargument overload.
266     Constructs a string using the `print` function.
267     +/
268     @nogc @safe pure nothrow this(Args...)(scope auto ref Args args, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null)
269         if (Args.length > 1)
270     {
271         static assert (__traits(compiles, {import mir.format;}), "MirThrowableImpl needs mir-algorithm for mir.format and exception formatting.");
272         import mir.format;
273         stringBuf buf;
274         this(buf.print(args).data, file, line, nextInChain);
275     }
276 }
277 
278 ///
279 enum maxMirExceptionMsgLen = 447;
280 
281 pragma(inline, false)
282 pure nothrow @nogc @safe
283 const(char)[] mirExceptionInitilizePayloadImpl(ref return char[maxMirExceptionMsgLen] payload, scope const(char)[] msg)
284 {
285     import mir.internal.memory: malloc;
286     import core.stdc..string: memcpy;
287     if (msg.length > payload.length)
288     {
289         if (auto ret = (() @trusted
290             {
291                 if (__ctfe)
292                     return null;
293                 if (auto ptr = malloc(msg.length))
294                 {
295                     memcpy(ptr, msg.ptr, msg.length);
296                     return cast(const(char)[]) ptr[0 .. msg.length];
297                 }
298                 return null;
299             })())
300             return ret;
301         msg = msg[0 .. payload.length];
302         // remove tail UTF-8 symbol chunk if any
303         uint c = msg[$-1];
304         if (c > 0b_0111_1111)
305         {
306             do {
307                 c = msg[$-1];
308                 msg = msg[0 .. $ - 1];
309             }
310             while (msg.length && c < 0b_1100_0000);
311         }
312     }
313     if (__ctfe)
314         payload[][0 .. msg.length] = msg;
315     else
316         (() @trusted => memcpy(payload.ptr, msg.ptr, msg.length))();
317     return payload[0 .. msg.length];
318 }