1 module exceptionhandling;
2 
3 private {
4 	import std.math : approxEqual;
5 	bool cmpFloat(T)(T tt, T tc) {
6 		return approxEqual(tt, tc);
7 	}
8 
9 	bool cmpFloatNot(T)(T tt, T tc) {
10 		return !approxEqual(tt, tc);
11 	}
12 
13 	bool cmpRest(T)(T tt, T tc) {
14 		return tt == tc;
15 	}
16 
17 	bool cmpRestNot(T)(T tt, T tc) {
18 		return tt == tc;
19 	}
20 }
21 
22 /** Assert that `toTest` is equal to `toCompareAgainst`.
23 If `T` is a floating point `approxEqual` is used to compare the values.
24 `toTest` is returned if the comparision is correct.
25 If the comparision is incorrect an Exception is thrown. If assertEqual is used
26 in a unittest block an AssertError is thrown an Exception otherwise.
27 */
28 ref T assertEqual(T)(auto ref T toTest, auto ref T toCompareAgainst, 
29 		const string file = __FILE__, const int line = __LINE__) 
30 {
31 	import std.traits : isFloatingPoint;
32 	static if(isFloatingPoint!T) {
33 		return AssertImpl!(T, cmpFloat)(toTest, toCompareAgainst, file, line);
34 	} else {
35 		return AssertImpl!(T, cmp)(toTest, toCompareAgainst, file, line);
36 	}
37 }
38 
39 /// ditto
40 ref T assertNotEqual(T)(auto ref T toTest, auto ref T toCompareAgainst, 
41 		const string file = __FILE__, const int line = __LINE__) 
42 {
43 	import std.traits : isFloatingPoint;
44 	static if(isFloatingPoint!T) {
45 		return AssertImpl!(T, cmpFloatNot)(toTest, toCompareAgainst, file, line);
46 	} else {
47 		return AssertImpl!(T, cmpNot)(toTest, toCompareAgainst, file, line);
48 	}
49 }
50 
51 private ref T AssertImpl(T,alias Cmp)(auto ref T toTest, auto ref T toCompareAgainst, 
52 		const string file, const int line) 
53 {
54 	import std.format : format;
55 	version(unittest) {
56 		import core.exception : AssertError;
57 		alias ExceptionType = AssertError;
58 	} else {
59 		alias ExceptionType = Exception;
60 	}
61 
62 	bool cmpRslt = false;
63 	try {
64 		cmpRslt = Cmp(toTest, toCompareAgainst);
65 	} catch(ExceptionType e) {
66 		throw new ExceptionType(
67 			format("Exception thrown while \"toTest(%s) != toCompareAgainst(%s)\"",
68 			toTest, toCompareAgainst), file, line, e
69 		);
70 	}
71 
72 	if(!cmpRslt) {
73 		throw new ExceptionType(format("toTest(%s) != toCompareAgainst(%s)",
74 			toTest, toCompareAgainst), file, line
75 		);
76 	}
77 	return toTest;
78 }
79 
80 unittest {
81 	import core.exception : AssertError;
82 	import std.exception : assertThrown;
83 	assertEqual(1.0, 1.0);
84 
85 	assertThrown!AssertError(assertEqual(1.0, 0.0));
86 }
87 
88 /** Calls `exp` if `exp` does not throw the return value from `exp` is
89 returned, if `exp` throws the Exception is cought, a new Exception is
90 constructed with a message made of `args` space seperated and the previously
91 cought exception is nested in the newly created exception.
92 */
93 auto chain(ET = Exception, F, int line = __LINE__, string file = __FILE__, Args...)
94 		(lazy F exp, lazy Args args)
95 {
96 	try {
97 		return exp();
98 	} catch(Exception e) {
99 		throw new ET(joinElem(args), file, line, e);
100 	}
101 }
102 
103 auto expect(ET = Exception, F, int line = __LINE__, string file = __FILE__, Args...)
104 		(lazy F exp, lazy Args args)
105 {
106 	try {
107 		return exp();
108 	} catch(Exception e) {
109 		throw new ET(joinElem(args), file, line, e);
110 	}
111 }
112 
113 private string joinElem(Args...)(lazy Args args) {
114 	import std.array : appender;
115 	import std.format : formattedWrite;	
116 
117 	auto app = appender!string();
118 	foreach(arg; args) {
119 		formattedWrite(app, "%s ", arg);
120 	}
121 	return app.data;
122 }