1 /// A draft of a better way to do exceptions
2 module arsd.exception;
3 /*
4 	Exceptions 2.0
5 */
6 
7 interface ThrowableBase {
8 	void fly(string file = __FILE__, size_t line = __LINE__); // should be built into the compiler's throw statement
9 
10 	// override these as needed
11 	void printMembers(scope void delegate(in char[]) sink) const; // Tip: use mixin PrintMembers; instead of doing it yourself
12 	void getHumanReadableMessage(scope void delegate(in char[]) sink) const; // the exception name should give this generally but it is nice if you have an error code that needs translation or something else that isn't obvious from the name
13 	void printName(scope void delegate(in char[]) sink) const; // only need to override this if you aren't happy with RTTI's name field
14 
15 	// just call this when you are ready
16 	void toString(scope void delegate(in char[]) sink) const;
17 }
18 
19 mixin template ThrowableBaseImplementation() {
20 	// This sets file and line at the throw point instead of in the ctor
21 	// thereby separating allocation from error information - call this and
22 	// file+line will be set then allowing you to reuse exception objects easier
23 	void fly(string file = __FILE__, size_t line = __LINE__) {
24 		this.file = file;
25 		this.line = line;
26 		throw this;
27 	}
28 
29 	// You don't really need this - the class name and members should give all the
30 	// necessary info, but it can be nice in cases like a Windows or errno exception
31 	// where the code isn't necessarily as at-a-glance easy as the string from GetLastError.
32 	/* virtual */ void getHumanReadableMessage(scope void delegate(in char[]) sink) const {
33 		sink(msg); // for backward compatibility
34 	}
35 
36 	// This prints the really useful info to the user, the members' values.
37 	// You don't have to write this typically, instead use the mixin below.
38 	/* virtual */ void printMembers(scope void delegate(in char[]) sink) const {
39 		// this is done with the mixin from derived classes
40 	}
41 
42 	/* virtual */ void printName(scope void delegate(in char[]) sink) const {
43 		sink(typeid(this).name); // FIXME: would be nice if eponymous templates didn't spew the name twice
44 	}
45 
46 	override void toString(scope void delegate(in char[]) sink) const {
47 		char[32] tmpBuff = void; 
48 		printName(sink);
49 		sink("@"); sink(file); 
50 		sink("("); sink(line.sizeToTempString(tmpBuff[])); sink(")"); 
51 		sink(": "); getHumanReadableMessage(sink);
52 		sink("\n");
53 		printMembers(sink);
54 		if (info) { 
55 			try { 
56 				sink("----------------"); 
57 				foreach (t; info) { 
58 					sink("\n"); sink(t); 
59 				} 
60 			} 
61 			catch (Throwable) { 
62 				// ignore more errors 
63 			} 
64 		}  
65 	}
66 
67 }
68 
69 class ExceptionBase : Exception, ThrowableBase {
70 	// Hugely simplified ctor - nothing is even needed
71 	this() {
72 		super("");
73 	}
74 
75 	mixin ThrowableBaseImplementation;
76 }
77 
78 class ErrorBase : Error, ThrowableBase {
79 	this() { super(""); }
80 	mixin ThrowableBaseImplementation;
81 }
82 
83 // Mix this into your derived class to print all its members automatically for easier debugging!
84 mixin template PrintMembers() {
85 	override void printMembers(scope void delegate(in char[]) sink) const {
86 		foreach(memberName; __traits(derivedMembers, typeof(this))) {
87 			static if(is(typeof(__traits(getMember, this, memberName))) && !is(typeof(__traits(getMember, typeof(this), memberName)) == function)) {
88 				sink("\t");
89 				sink(memberName);
90 				sink(" = ");
91 				static if(is(typeof(__traits(getMember, this, memberName)) : const(char)[]))
92 					sink(__traits(getMember, this, memberName));
93 				else static if(is(typeof(__traits(getMember, this, memberName)) : long)) {
94 					char[32] tmpBuff = void; 
95 					sink(sizeToTempString(__traits(getMember, this, memberName), tmpBuff));
96 				} // else pragma(msg, typeof(__traits(getMember, this, memberName)));
97 				sink("\n");
98 			}
99 		}
100 
101 		super.printMembers(sink);
102 	}
103 }
104 
105 // The class name SHOULD obviate this but you can also add another message if you like.
106 // You can also just override the getHumanReadableMessage yourself in cases like calling strerror
107 mixin template StaticHumanReadableMessage(string s) {
108 	override void getHumanReadableMessage(scope void delegate(in char[]) sink) const {
109 		sink(s);
110 	}
111 }
112 
113 
114 
115 
116 /*
117 	Enforce 2.0
118 */
119 
120 interface DynamicException {
121 	/*
122 	TypeInfo getArgumentType(size_t idx);
123 	void*    getArgumentData(size_t idx);
124 	string   getArgumentAsString(size_t idx);
125 	*/
126 }
127 
128 template enforceBase(ExceptionBaseClass, string failureCondition = "ret is null") {
129 	auto enforceBase(alias func, string file = __FILE__, size_t line = __LINE__, T...)(T args) {
130 		auto ret = func(args);
131 		if(mixin(failureCondition)) {
132 			class C : ExceptionBaseClass, DynamicException {
133 				T args;
134 				this(T args) {
135 					this.args = args;
136 				}
137 
138 				override void printMembers(scope void delegate(in char[]) sink) const {
139 					import std.traits;
140 					import std.conv;
141 					foreach(idx, arg; args) {
142 						sink("\t");
143 						sink(ParameterIdentifierTuple!func[idx]);
144 						sink(" = ");
145 						sink(to!string(arg));
146 						sink("\n");
147 					}
148 					sink("\treturn value = ");
149 					sink(to!string(ret));
150 					sink("\n");
151 				}
152 
153 				override void printName(scope void delegate(in char[]) sink) const {
154 					sink(__traits(identifier, ExceptionBaseClass));
155 				}
156 
157 				override void getHumanReadableMessage(scope void delegate(in char[]) sink) const {
158 					sink(__traits(identifier, func));
159 					sink(" call failed");
160 				}
161 			}
162 			
163 			auto exception = new C(args);
164 			exception.file = file;
165 			exception.line = line;
166 			throw exception;
167 		}
168 
169 		return ret;
170 	}
171 }
172 
173 /// Raises an exception given a set of local variables to print out
174 void raise(ExceptionBaseClass, T...)(string file = __FILE__, size_t line = __LINE__) {
175 	class C : ExceptionBaseClass, DynamicException {
176 		override void printMembers(scope void delegate(in char[]) sink) const {
177 			import std.conv;
178 			foreach(idx, arg; T) {
179 				sink("\t");
180 				sink(__traits(identifier, T[idx]));
181 				sink(" = ");
182 				sink(to!string(arg));
183 				sink("\n");
184 			}
185 		}
186 
187 		override void printName(scope void delegate(in char[]) sink) const {
188 			sink(__traits(identifier, ExceptionBaseClass));
189 		}
190 	}
191 	
192 	auto exception = new C();
193 	exception.file = file;
194 	exception.line = line;
195 	throw exception;
196 }
197 
198 const(char)[] sizeToTempString(long size, char[] buffer) {
199 	size_t pos = buffer.length - 1;
200 	bool negative = size < 0;
201 	if(size < 0)
202 		size = -size;
203 	while(size) {
204 		buffer[pos] = size % 10 + '0';
205 		size /= 10;
206 		pos--;
207 	}
208 	if(negative) {
209 		buffer[pos] = '-';
210 		pos--;
211 	}
212 	return buffer[pos + 1 .. $];
213 }
214 
215 /////////////////////////////
216 /* USAGE EXAMPLE FOLLOWS */
217 /////////////////////////////
218 
219 
220 // Make sure there's sane base classes for things that take
221 // various types. For example, RangeError might be thrown for
222 // any type of index, but we might just catch any kind of range error.
223 //
224 // The base class gives us an easy catch point for the category.
225 class MyRangeError : ErrorBase {
226 	// unnecessary but kinda nice to have static error message
227 	mixin StaticHumanReadableMessage!"Index out of bounds";
228 }
229 
230 // Now, we do a new class for each error condition that can happen
231 // inheriting from a convenient catch-all base class for our error type
232 // (which might be ExceptionBase itself btw)
233 class TypedRangeError(T) : MyRangeError {
234 	// Error details are stored as DATA MEMBERS
235 	// do NOT convert them to a string yourself
236 	this(T index) {
237 		this.index = index;
238 	}
239 
240 	mixin StaticHumanReadableMessage!(T.stringof ~ " index out of bounds");
241 
242 	// The data members can be easily inspected to learn more
243 	// about the error, perhaps even to retry it programmatically
244 	// and this also avoids the need to do something like call to!string
245 	// and string concatenation functions at the construction point.
246 	//
247 	// Yea, this gives more info AND is allocation-free. What's not to love?
248 	//
249 	// Templated ones can be a pain just because of the need to specify it to
250 	// catch or cast, but it will always at least be available in the printed string.
251 	T index;
252 
253 	// Then, mixin PrintMembers uses D's reflection to do all the messy toString
254 	// data sink nonsense for you. Do this in each subclass where you add more
255 	// data members (which out to be generally all of them, more info is good.
256 	mixin PrintMembers;
257 }
258 
259 version(exception_2_example) {
260 
261 	// We can pass pre-constructed exceptions to functions and get good file/line and stacktrace info!
262 	void stackExample(ThrowableBase exception) {
263 		// throw it now (custom function cuz I change the behavior a wee bit)
264 		exception.fly(); // ideally, I'd change the throw statement to call this function for you to set up line and file
265 	}
266 
267 	void main() {
268 		int a = 230;
269 		string file = "lol";
270 		static class BadValues : ExceptionBase {}
271 		//raise!(BadValues, a, file);
272 
273 		alias enforce = enforceBase!ExceptionBase;
274 
275 		import core.stdc.stdio;
276 		auto fp = enforce!fopen("nofile.txt".ptr, "rb".ptr);
277 
278 
279 		// construct, passing it error details as data, not strings.
280 		auto exception = new TypedRangeError!int(4); // exception construction is separated from file/line setting
281 		stackExample(exception); // so you can allocate/construct in one place, then set and throw somewhere else
282 	}
283 }