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 }