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 }