From 621fb991c9abc530b1e79a607c4828042bc10e58 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Sat, 16 May 2015 16:58:09 -0400 Subject: [PATCH] exception 2.0 draft (hopefully phobos-bound!) --- exception.d | 281 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 exception.d diff --git a/exception.d b/exception.d new file mode 100644 index 0000000..fd2e51f --- /dev/null +++ b/exception.d @@ -0,0 +1,281 @@ +/* + 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 + } +}