1 module more.builder;
2 
3 import core.stdc..string : memmove;
4 
5 import std.typecons : Flag, Yes, No;
6 import std.traits : isSomeChar, hasMember;
7 
8 alias StringBuilder(SizeExpander) = Builder!(char, SizeExpander);
9 
10 struct Builder(T, Expander)
11 {
12     static assert(hasMember!(Expander, "expand"), Expander.stringof~" does not have an expand function");
13 
14     T[] buffer;
15     size_t dataLength;
16 
17     @property T[] data() const { return cast(T[])buffer[0..dataLength]; }
18     @property T[] available() const { return cast(T[])buffer[dataLength..$]; }
19 
20     T* getRef(size_t index)
21     {
22         return &buffer[index];
23     }
24 
25     void ensureCapacity(size_t capacityNeeded)
26     {
27         if(capacityNeeded > buffer.length)
28         {
29             this.buffer = Expander.expand!T(buffer, dataLength, capacityNeeded);
30         }
31     }
32     void makeRoomFor(size_t newContentLength)
33     {
34         ensureCapacity(dataLength + newContentLength);
35     }
36     T* reserveOne(Flag!"initialize" initialize)
37     {
38         makeRoomFor(1);
39         if(initialize)
40         {
41             buffer[dataLength] = T.init;
42         }
43         return &buffer[dataLength++];
44     }
45     void shrink(size_t newLength) in { assert(newLength < dataLength); } body
46     {
47         dataLength = newLength;
48     }
49     void shrinkIfSmaller(size_t newLength)
50     {
51         if(newLength < dataLength)
52         {
53             dataLength = newLength;
54         }
55     }
56 
57     static if(__traits(compiles, { T t1; const(T) t2; t1 = t2; }))
58     {
59         void append(const(T) newElement)
60         {
61             makeRoomFor(1);
62             buffer[dataLength++] = newElement;
63         }
64         void append(const(T)[] newElements)
65         {
66             makeRoomFor(newElements.length);
67             buffer[dataLength..dataLength+newElements.length] = newElements[];
68             dataLength += newElements.length;
69         }
70     }
71     else
72     {
73         void append(T newElement)
74         {
75             makeRoomFor(1);
76             buffer[dataLength++] = newElement;
77         }
78         void append(T[] newElements)
79         {
80             makeRoomFor(newElements.length);
81             buffer[dataLength..dataLength+newElements.length] = newElements[];
82             dataLength += newElements.length;
83         }
84     }
85 
86     static if(isSomeChar!T)
87     {
88         void appendf(Args...)(const(char)[] fmt, Args args)
89         {
90             import std.format : formattedWrite;
91             formattedWrite(&append, fmt, args);
92         }
93         // Only call if the data in this builder will not be modified
94         string finalString()
95         {
96             return cast(string)buffer[0..dataLength];
97         }
98     }
99 
100     void removeAt(size_t index) in { assert(index < dataLength); } body
101     {
102         if(index < dataLength-1)
103         {
104             memmove(&buffer[index], &buffer[index+1], T.sizeof * (dataLength-(index+1)));
105         }
106         dataLength--;
107     }
108 }
109 
110 unittest
111 {
112     import more.test;
113     mixin(scopedTest!"builder");
114 
115     static import core.stdc.stdlib;
116     import more.alloc : GCDoubler, MallocDoubler;
117 
118     {
119         auto builder = Builder!(int, GCDoubler!100)();
120         assert(builder.buffer is null);
121         assert(builder.dataLength == 0);
122         assert(builder.data is null);
123         builder.append(1);
124         assert(builder.data == [1]);
125         builder.append(2);
126         assert(builder.data == [1,2]);
127     }
128     {
129         auto builder = Builder!(int, MallocDoubler!1)();
130         assert(builder.buffer is null);
131         assert(builder.dataLength == 0);
132         assert(builder.data is null);
133         builder.append(1);
134         assert(builder.data == [1]);
135         builder.append(2);
136         assert(builder.data == [1,2]);
137         builder.append(3);
138         assert(builder.data == [1,2,3]);
139         core.stdc.stdlib.free(builder.buffer.ptr);
140     }
141 
142     struct BadExpander1
143     {
144         // no expand function
145     }
146     assert(!__traits(compiles, {auto builder = Builder!(int, BadExpander1);}));
147 
148     struct BadExpander2
149     {
150         // invalid expand function
151         static void expand()
152         {
153         }
154     }
155     assert(!__traits(compiles, {auto builder = Builder!(int, BadExpander2);}));
156 }