1 module stringbuffer; 2 3 /** A super simple string buffer that will free its string if its gets out of 4 scope. To use it as a OutputRange use the value returned from the writer 5 method. getData will return a string that points to the data stored in the 6 StringBuffer. If the StringBuffer gets cleared up the string returned by 7 getData can no longer be used. 8 */ 9 struct StringBufferImpl(int stackLen) { 10 import core.memory : GC; 11 char[stackLen] stack; 12 char* overflow = null; 13 size_t capacity = stackLen; 14 size_t length; 15 bool copied; 16 17 ~this() @safe { 18 if(this.overflow !is null) { 19 () @trusted { 20 GC.free(cast(void*)this.overflow); 21 }(); 22 } 23 } 24 25 struct OutputRange { 26 StringBufferImpl!(stackLen)* buf; 27 28 void put(const(char) c) @safe { 29 this.buf.insertBack(c); 30 } 31 32 void put(dchar c) @safe { 33 this.buf.insertBack(c); 34 } 35 36 void put(const(char)[] s) @safe { 37 this.buf.insertBack(s); 38 } 39 40 void put(string s) @safe { 41 this.buf.insertBack(s); 42 } 43 } 44 45 OutputRange writer() { 46 return OutputRange(&this); 47 } 48 49 private void putImpl(const(char) c) @trusted { 50 if(this.length < stackLen) { 51 this.stack[this.length++] = c; 52 } else { 53 if(this.length + 1 >= this.capacity || this.overflow is null) { 54 this.grow(); 55 assert(this.overflow !is null); 56 } 57 this.overflow[this.length++] = c; 58 } 59 } 60 61 private void grow() @trusted { 62 this.capacity *= 2; 63 this.overflow = cast(char*)GC.realloc(this.overflow, this.capacity); 64 this.copy(); 65 } 66 67 private void copy() @trusted { 68 if(!this.copied) { 69 for(size_t i = 0; i < stackLen; ++i) { 70 this.overflow[i] = this.stack[i]; 71 } 72 this.copied = true; 73 } 74 } 75 76 void insertBack(const(char) c) @safe { 77 this.putImpl(c); 78 } 79 80 void insertBack(dchar c) @safe { 81 import std.utf : encode; 82 char[4] encoded; 83 size_t len = encode(encoded, c); 84 this.insertBack(encoded[0 .. len]); 85 } 86 87 void insertBack(const(char)[] s) @trusted { 88 if(s.length + this.length < stackLen) { 89 for(size_t i = 0; i < s.length; ++i) { 90 this.stack[this.length++] = s[i]; 91 } 92 } else { 93 while(s.length + this.length >= this.capacity 94 || this.overflow is null) 95 { 96 this.grow(); 97 assert(this.overflow !is null); 98 } 99 this.copy(); 100 for(size_t i = 0; i < s.length; ++i) { 101 this.overflow[this.length++] = s[i]; 102 } 103 } 104 } 105 106 void insertBack(string s) @safe { 107 this.insertBack(cast(const(char)[])s); 108 } 109 110 void removeAll() @safe { 111 this.length = 0; 112 this.copied = false; 113 } 114 115 T getData(T = string)() @system { 116 if(this.length >= stackLen) { 117 return cast(T)this.overflow[0 .. this.length]; 118 } else { 119 return cast(T)this.stack[0 .. this.length]; 120 } 121 } 122 } 123 124 /// 125 alias StringBuffer = StringBufferImpl!512; 126 127 unittest { 128 import std.meta : AliasSeq; 129 130 void localTest(int Size)() { 131 alias SmallStringBuf = StringBufferImpl!Size; 132 SmallStringBuf buf; 133 buf.insertBack('c'); 134 buf.insertBack("c"); 135 136 assert(buf.getData() == "cc"); 137 138 for(int i = 0; i < 2048; ++i) { 139 buf.insertBack(cast(dchar)'c'); 140 } 141 142 for(int i = 0; i < 2050; ++i) { 143 assert(buf.getData()[i] == 'c'); 144 } 145 } 146 147 foreach(s; AliasSeq!(1,2,3,7,8,9,10,11,127,255,256,500,512)) { 148 localTest!(s)(); 149 } 150 } 151 152 unittest { 153 StringBuffer buf; 154 buf.insertBack('ö'); 155 assert(buf.getData() == "ö"); 156 157 buf.writer().put('ö'); 158 assert(buf.getData() == "öö"); 159 } 160 161 unittest { 162 StringBuffer buf; 163 for(int i = 0; i < 1028; ++i) { 164 buf.insertBack('a'); 165 } 166 167 auto s = buf.getData(); 168 for(int i = 0; i < s.length; ++i) { 169 assert(s[i] == 'a'); 170 } 171 } 172 173 unittest { 174 import std.range.primitives : isOutputRange; 175 static assert(isOutputRange!(typeof(StringBuffer.writer()), char)); 176 } 177 178 unittest { 179 import std.format : formattedWrite; 180 181 StringBuffer buf; 182 formattedWrite(buf.writer(), "%d", 42); 183 assert(buf.getData() == "42"); 184 } 185 186 unittest { 187 import std.format : formattedWrite; 188 189 StringBuffer buf; 190 auto w = buf.writer(); 191 formattedWrite(w, "foobar %d", 10); 192 assert(buf.getData() == "foobar 10"); 193 } 194 195 unittest { 196 StringBuffer buf; 197 auto w = buf.writer(); 198 w.put('a'); 199 assert(buf.getData() == "a"); 200 assert(buf.getData!(byte[])() == cast(byte[])"a"); 201 202 w.put("fo"); 203 assert(buf.getData!(ubyte[])() == cast(ubyte[])"afo"); 204 } 205 206 unittest { 207 string s = "0123456789"; 208 string l; 209 for(int i = 0; i < 1026; ++i) { 210 l ~= s; 211 } 212 213 StringBuffer buf; 214 buf.insertBack(l); 215 216 string ls = buf.getData(); 217 for(int i = 0; i < ls.length; ++i) { 218 assert(ls[i] == ('0' + (i % 10))); 219 } 220 } 221 222 unittest { 223 import std.meta : AliasSeq; 224 225 void localTest(int Size)() { 226 alias SmallStringBuf = StringBufferImpl!Size; 227 228 SmallStringBuf buf; 229 assert(buf.overflow is null); 230 buf.insertBack("0123456789"); 231 buf.insertBack("0123456789"); 232 assert(buf.overflow !is null); 233 buf.removeAll(); 234 assert(buf.overflow !is null); 235 assert(buf.length == 0); 236 assert(buf.copied == false); 237 238 buf.insertBack("543210"); 239 assert(buf.length == 6); 240 assert(buf.overflow !is null); 241 //assert(buf.overflow[0 .. 20] == "01234567890123456789", 242 // cast(string)buf.overflow[0 .. 20]); 243 buf.insertBack("98765432109876543210"); 244 245 assert(buf.overflow !is null); 246 assert(buf.length == 26); 247 assert(buf.copied == true); 248 assert(buf.getData() == "54321098765432109876543210", buf.getData()); 249 } 250 251 foreach(s; AliasSeq!(1,2,3,4,5,6,7,8,9,10,11,12)) { 252 localTest!(s)(); 253 } 254 }