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