mirror of https://github.com/adamdruppe/arsd.git
292 lines
9.1 KiB
D
292 lines
9.1 KiB
D
/++
|
|
A draft of a better way to do exceptions
|
|
|
|
History:
|
|
Originally written in May 2015 as a demo, but I never used it inside arsd.
|
|
|
|
Deprecated in March 2023 (dub v11.0), with the successful parts moved to [arsd.core]. It is unlikely to get any future updates.
|
|
+/
|
|
deprecated("This was just a proof of concept demo, the actual concepts are now implemented inside arsd.core")
|
|
module arsd.exception;
|
|
/*
|
|
Exceptions 2.0
|
|
*/
|
|
|
|
interface ThrowableBase {
|
|
void fly(string file = __FILE__, size_t line = __LINE__); // should be built into the compiler's throw statement
|
|
|
|
// override these as needed
|
|
void printMembers(scope void delegate(in char[]) sink) const; // Tip: use mixin PrintMembers; instead of doing it yourself
|
|
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
|
|
void printName(scope void delegate(in char[]) sink) const; // only need to override this if you aren't happy with RTTI's name field
|
|
|
|
// just call this when you are ready
|
|
void toString(scope void delegate(in char[]) sink) const;
|
|
}
|
|
|
|
mixin template ThrowableBaseImplementation() {
|
|
// This sets file and line at the throw point instead of in the ctor
|
|
// thereby separating allocation from error information - call this and
|
|
// file+line will be set then allowing you to reuse exception objects easier
|
|
void fly(string file = __FILE__, size_t line = __LINE__) {
|
|
this.file = file;
|
|
this.line = line;
|
|
throw this;
|
|
}
|
|
|
|
// You don't really need this - the class name and members should give all the
|
|
// necessary info, but it can be nice in cases like a Windows or errno exception
|
|
// where the code isn't necessarily as at-a-glance easy as the string from GetLastError.
|
|
/* virtual */ void getHumanReadableMessage(scope void delegate(in char[]) sink) const {
|
|
sink(msg); // for backward compatibility
|
|
}
|
|
|
|
// This prints the really useful info to the user, the members' values.
|
|
// You don't have to write this typically, instead use the mixin below.
|
|
/* virtual */ void printMembers(scope void delegate(in char[]) sink) const {
|
|
// this is done with the mixin from derived classes
|
|
}
|
|
|
|
/* virtual */ void printName(scope void delegate(in char[]) sink) const {
|
|
sink(typeid(this).name); // FIXME: would be nice if eponymous templates didn't spew the name twice
|
|
}
|
|
|
|
override void toString(scope void delegate(in char[]) sink) const {
|
|
char[32] tmpBuff = void;
|
|
printName(sink);
|
|
sink("@"); sink(file);
|
|
sink("("); sink(line.sizeToTempString(tmpBuff[])); sink(")");
|
|
sink(": "); getHumanReadableMessage(sink);
|
|
sink("\n");
|
|
printMembers(sink);
|
|
if (info) {
|
|
try {
|
|
sink("----------------");
|
|
foreach (t; info) {
|
|
sink("\n"); sink(t);
|
|
}
|
|
}
|
|
catch (Throwable) {
|
|
// ignore more errors
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
class ExceptionBase : Exception, ThrowableBase {
|
|
// Hugely simplified ctor - nothing is even needed
|
|
this() {
|
|
super("");
|
|
}
|
|
|
|
mixin ThrowableBaseImplementation;
|
|
}
|
|
|
|
class ErrorBase : Error, ThrowableBase {
|
|
this() { super(""); }
|
|
mixin ThrowableBaseImplementation;
|
|
}
|
|
|
|
// Mix this into your derived class to print all its members automatically for easier debugging!
|
|
mixin template PrintMembers() {
|
|
override void printMembers(scope void delegate(in char[]) sink) const {
|
|
foreach(memberName; __traits(derivedMembers, typeof(this))) {
|
|
static if(is(typeof(__traits(getMember, this, memberName))) && !is(typeof(__traits(getMember, typeof(this), memberName)) == function)) {
|
|
sink("\t");
|
|
sink(memberName);
|
|
sink(" = ");
|
|
static if(is(typeof(__traits(getMember, this, memberName)) : const(char)[]))
|
|
sink(__traits(getMember, this, memberName));
|
|
else static if(is(typeof(__traits(getMember, this, memberName)) : long)) {
|
|
char[32] tmpBuff = void;
|
|
sink(sizeToTempString(__traits(getMember, this, memberName), tmpBuff));
|
|
} // else pragma(msg, typeof(__traits(getMember, this, memberName)));
|
|
sink("\n");
|
|
}
|
|
}
|
|
|
|
super.printMembers(sink);
|
|
}
|
|
}
|
|
|
|
// The class name SHOULD obviate this but you can also add another message if you like.
|
|
// You can also just override the getHumanReadableMessage yourself in cases like calling strerror
|
|
mixin template StaticHumanReadableMessage(string s) {
|
|
override void getHumanReadableMessage(scope void delegate(in char[]) sink) const {
|
|
sink(s);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
Enforce 2.0
|
|
*/
|
|
|
|
interface DynamicException {
|
|
/*
|
|
TypeInfo getArgumentType(size_t idx);
|
|
void* getArgumentData(size_t idx);
|
|
string getArgumentAsString(size_t idx);
|
|
*/
|
|
}
|
|
|
|
template enforceBase(ExceptionBaseClass, string failureCondition = "ret is null") {
|
|
auto enforceBase(alias func, string file = __FILE__, size_t line = __LINE__, T...)(T args) {
|
|
auto ret = func(args);
|
|
if(mixin(failureCondition)) {
|
|
class C : ExceptionBaseClass, DynamicException {
|
|
T args;
|
|
this(T args) {
|
|
this.args = args;
|
|
}
|
|
|
|
override void printMembers(scope void delegate(in char[]) sink) const {
|
|
import std.traits;
|
|
import std.conv;
|
|
foreach(idx, arg; args) {
|
|
sink("\t");
|
|
sink(ParameterIdentifierTuple!func[idx]);
|
|
sink(" = ");
|
|
sink(to!string(arg));
|
|
sink("\n");
|
|
}
|
|
sink("\treturn value = ");
|
|
sink(to!string(ret));
|
|
sink("\n");
|
|
}
|
|
|
|
override void printName(scope void delegate(in char[]) sink) const {
|
|
sink(__traits(identifier, ExceptionBaseClass));
|
|
}
|
|
|
|
override void getHumanReadableMessage(scope void delegate(in char[]) sink) const {
|
|
sink(__traits(identifier, func));
|
|
sink(" call failed");
|
|
}
|
|
}
|
|
|
|
auto exception = new C(args);
|
|
exception.file = file;
|
|
exception.line = line;
|
|
throw exception;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/// Raises an exception given a set of local variables to print out
|
|
void raise(ExceptionBaseClass, T...)(string file = __FILE__, size_t line = __LINE__) {
|
|
class C : ExceptionBaseClass, DynamicException {
|
|
override void printMembers(scope void delegate(in char[]) sink) const {
|
|
import std.conv;
|
|
foreach(idx, arg; T) {
|
|
sink("\t");
|
|
sink(__traits(identifier, T[idx]));
|
|
sink(" = ");
|
|
sink(to!string(arg));
|
|
sink("\n");
|
|
}
|
|
}
|
|
|
|
override void printName(scope void delegate(in char[]) sink) const {
|
|
sink(__traits(identifier, ExceptionBaseClass));
|
|
}
|
|
}
|
|
|
|
auto exception = new C();
|
|
exception.file = file;
|
|
exception.line = line;
|
|
throw exception;
|
|
}
|
|
|
|
const(char)[] sizeToTempString(long size, char[] buffer) {
|
|
size_t pos = buffer.length - 1;
|
|
bool negative = size < 0;
|
|
if(size < 0)
|
|
size = -size;
|
|
while(size) {
|
|
buffer[pos] = size % 10 + '0';
|
|
size /= 10;
|
|
pos--;
|
|
}
|
|
if(negative) {
|
|
buffer[pos] = '-';
|
|
pos--;
|
|
}
|
|
return buffer[pos + 1 .. $];
|
|
}
|
|
|
|
/////////////////////////////
|
|
/* USAGE EXAMPLE FOLLOWS */
|
|
/////////////////////////////
|
|
|
|
|
|
// Make sure there's sane base classes for things that take
|
|
// various types. For example, RangeError might be thrown for
|
|
// any type of index, but we might just catch any kind of range error.
|
|
//
|
|
// The base class gives us an easy catch point for the category.
|
|
class MyRangeError : ErrorBase {
|
|
// unnecessary but kinda nice to have static error message
|
|
mixin StaticHumanReadableMessage!"Index out of bounds";
|
|
}
|
|
|
|
// Now, we do a new class for each error condition that can happen
|
|
// inheriting from a convenient catch-all base class for our error type
|
|
// (which might be ExceptionBase itself btw)
|
|
class TypedRangeError(T) : MyRangeError {
|
|
// Error details are stored as DATA MEMBERS
|
|
// do NOT convert them to a string yourself
|
|
this(T index) {
|
|
this.index = index;
|
|
}
|
|
|
|
mixin StaticHumanReadableMessage!(T.stringof ~ " index out of bounds");
|
|
|
|
// The data members can be easily inspected to learn more
|
|
// about the error, perhaps even to retry it programmatically
|
|
// and this also avoids the need to do something like call to!string
|
|
// and string concatenation functions at the construction point.
|
|
//
|
|
// Yea, this gives more info AND is allocation-free. What's not to love?
|
|
//
|
|
// Templated ones can be a pain just because of the need to specify it to
|
|
// catch or cast, but it will always at least be available in the printed string.
|
|
T index;
|
|
|
|
// Then, mixin PrintMembers uses D's reflection to do all the messy toString
|
|
// data sink nonsense for you. Do this in each subclass where you add more
|
|
// data members (which out to be generally all of them, more info is good.
|
|
mixin PrintMembers;
|
|
}
|
|
|
|
version(exception_2_example) {
|
|
|
|
// We can pass pre-constructed exceptions to functions and get good file/line and stacktrace info!
|
|
void stackExample(ThrowableBase exception) {
|
|
// throw it now (custom function cuz I change the behavior a wee bit)
|
|
exception.fly(); // ideally, I'd change the throw statement to call this function for you to set up line and file
|
|
}
|
|
|
|
void main() {
|
|
int a = 230;
|
|
string file = "lol";
|
|
static class BadValues : ExceptionBase {}
|
|
//raise!(BadValues, a, file);
|
|
|
|
alias enforce = enforceBase!ExceptionBase;
|
|
|
|
import core.stdc.stdio;
|
|
auto fp = enforce!fopen("nofile.txt".ptr, "rb".ptr);
|
|
|
|
|
|
// construct, passing it error details as data, not strings.
|
|
auto exception = new TypedRangeError!int(4); // exception construction is separated from file/line setting
|
|
stackExample(exception); // so you can allocate/construct in one place, then set and throw somewhere else
|
|
}
|
|
}
|