1 module composer.composer; 2 3 version(unittest) import unit_threaded; 4 5 /** 6 An overflow policy controls what the composer will do when 7 it runs out of room. 8 */ 9 enum OverflowPolicy 10 { 11 assertFalse, 12 callback, 13 exception 14 } 15 16 enum runtime = size_t.max; 17 18 /** 19 The `Composer` is a configurable string joiner. 20 21 It was originally designed to be a way to allow assert 22 messages with runtime parameters in functions marked @nogc. 23 */ 24 struct Composer(Char, size_t buffSize = runtime, OverflowPolicy policy = OverflowPolicy.assertFalse) 25 { 26 alias OverflowCallback = void function(ref Composer, size_t numCharsAttempted); 27 28 public: 29 static if (buffSize == runtime && policy == OverflowPolicy.callback) 30 @safe @nogc this(Char[] buffer, OverflowCallback callback) nothrow 31 { 32 _buffer = buffer; 33 _overflowCallback = callback; 34 } 35 else static if (buffSize != runtime && policy == OverflowPolicy.callback) 36 @safe @nogc this(OverflowCallback callback) nothrow 37 { 38 _overflowCallback = callback; 39 } 40 else static if (buffSize == runtime) 41 @safe @nogc this(Char[] buffer) nothrow 42 { 43 _buffer = buffer; 44 } 45 46 /** 47 Returns: The length of the message that has been written 48 into the buffer so far 49 */ 50 @safe @nogc @property size_t messageLength() const nothrow 51 { 52 return _nextCharIndex; 53 } 54 55 /** 56 Returns: The message so far 57 */ 58 @trusted @nogc @property immutable(Char[]) message() const nothrow 59 { 60 return cast(immutable(Char[])) _buffer[0 .. _nextCharIndex]; 61 } 62 63 @safe @nogc @property size_t bufferSize() const nothrow 64 { 65 return _buffer.length; 66 } 67 68 /** 69 Clears the composer of any previously written characters. 70 */ 71 @safe @nogc void clear() nothrow 72 { 73 _nextCharIndex = 0; 74 } 75 76 /** 77 Set the composer's buffer to `buffer`. 78 This is to enable the creation of dynamic buffers. 79 80 Params: 81 buffer = The new buffer 82 numCharsWritten = The number of characters already in the buffer 83 */ 84 @safe @nogc void setBuffer(Char[] buffer, size_t numCharsWritten = 0) nothrow 85 { 86 _buffer = buffer; 87 _nextCharIndex = numCharsWritten + 1; 88 } 89 90 /** 91 Composes a message from arguments. Anything that was 92 previously written to the composer will be cleared. 93 94 If the buffer reaches capacity before the composition 95 can be completed, the composer will enact the selected 96 overflow policy. 97 98 The `callback` policy is special, in that it will retry 99 the failed composition after the callback has been 100 called. This is to allow callbacks to grow a composer as 101 needed. If the retry fails, the function exits. 102 103 Params: 104 args = The components of the message to join 105 */ 106 ref Composer write(A...)(A args) 107 { 108 static import composer.writer; 109 110 clear(); 111 112 auto buffer = composer.writer.Buffer!Char(_buffer); 113 const result = composer.writer.write(buffer, args); 114 115 if (result.numCharsWritten > 0) 116 { 117 _nextCharIndex = result.numCharsWritten; 118 } 119 else 120 { 121 static if (policy == OverflowPolicy.callback) 122 _overflowCallback(this); 123 else static if (policy == OverflowPolicy.exception) 124 throw new Exception("Composer buffer at capacity"); 125 else static if (policy == OverflowPolicy.assertFalse) 126 assert(false, "Composer buffer at capacity"); 127 } 128 129 return this; 130 } 131 132 private: 133 static if (buffSize == runtime) 134 Char[] _buffer; 135 else 136 Char[buffSize] _buffer; 137 138 ///The index of the next available character 139 size_t _nextCharIndex; 140 141 static if (policy == OverflowPolicy.callback) 142 OverflowCallback _overflowCallback; 143 } 144 145 @("Composer.dynamic.write") 146 @safe @nogc pure nothrow unittest 147 { 148 char[1024] msgBuffer; 149 150 auto composer = Composer!char(msgBuffer); 151 152 auto ptr = () @trusted { return cast(void*) 0xDEADBEEF; } (); 153 auto cmp = composer.write("This is a message with numbers: ", 12, ", and pointers: ", ptr); 154 155 assert(cmp.message == "This is a message with numbers: 12, and pointers: void*(0xDEADBEEF)"); 156 } 157 158 @("Composer.static.write") 159 @safe @nogc pure nothrow unittest 160 { 161 auto composer = Composer!(char, 2014)(); 162 163 auto ptr = () @trusted { return cast(uint*) 0xDEADBEEF; } (); 164 auto cmp = composer.write("This is a message with numbers: ", 12, ", and pointers: ", ptr); 165 166 assert(cmp.message == "This is a message with numbers: 12, and pointers: uint*(0xDEADBEEF)"); 167 }