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; 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 StringBuffer* 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 if(!this.copied) { 63 for(size_t i = 0; i < stackLen; ++i) { 64 this.overflow[i] = this.stack[i]; 65 } 66 this.copied = true; 67 } 68 } 69 70 void insertBack(const(char) c) @safe { 71 this.putImpl(c); 72 } 73 74 void insertBack(dchar c) @safe { 75 import std.utf : encode; 76 char[4] encoded; 77 size_t len = encode(encoded, c); 78 this.insertBack(encoded[0 .. len]); 79 } 80 81 void insertBack(const(char)[] s) @trusted { 82 if(s.length + this.length < stackLen) { 83 for(size_t i = 0; i < s.length; ++i) { 84 this.stack[this.length++] = s[i]; 85 } 86 } else { 87 while(s.length + this.length >= this.capacity 88 || this.overflow is null) 89 { 90 this.grow(); 91 assert(this.overflow !is null); 92 } 93 for(size_t i = 0; i < s.length; ++i) { 94 this.overflow[this.length++] = s[i]; 95 } 96 } 97 } 98 99 void insertBack(string s) @safe { 100 this.insertBack(cast(const(char)[])s); 101 } 102 103 T getData(T = string)() @system { 104 if(this.length >= stackLen) { 105 return cast(T)this.overflow[0 .. this.length]; 106 } else { 107 return cast(T)this.stack[0 .. this.length]; 108 } 109 } 110 } 111 112 /// 113 alias StringBuffer = StringBufferImpl!512; 114 115 unittest { 116 StringBuffer buf; 117 buf.insertBack('c'); 118 buf.insertBack("c"); 119 120 assert(buf.getData() == "cc"); 121 122 for(int i = 0; i < 2048; ++i) { 123 buf.insertBack(cast(dchar)'c'); 124 } 125 126 for(int i = 0; i < 2050; ++i) { 127 assert(buf.getData()[i] == 'c'); 128 } 129 } 130 131 unittest { 132 StringBuffer buf; 133 buf.insertBack('ö'); 134 assert(buf.getData() == "ö"); 135 136 buf.writer().put('ö'); 137 assert(buf.getData() == "öö"); 138 } 139 140 unittest { 141 StringBuffer buf; 142 for(int i = 0; i < 1028; ++i) { 143 buf.insertBack('a'); 144 } 145 146 auto s = buf.getData(); 147 for(int i = 0; i < s.length; ++i) { 148 assert(s[i] == 'a'); 149 } 150 } 151 152 unittest { 153 import std.range.primitives : isOutputRange; 154 static assert(isOutputRange!(typeof(StringBuffer.writer()), char)); 155 } 156 157 unittest { 158 import std.format : formattedWrite; 159 160 StringBuffer buf; 161 formattedWrite(buf.writer(), "%d", 42); 162 assert(buf.getData() == "42"); 163 } 164 165 unittest { 166 import std.format : formattedWrite; 167 168 StringBuffer buf; 169 auto w = buf.writer(); 170 formattedWrite(w, "foobar %d", 10); 171 assert(buf.getData() == "foobar 10"); 172 } 173 174 unittest { 175 StringBuffer buf; 176 auto w = buf.writer(); 177 w.put('a'); 178 assert(buf.getData() == "a"); 179 assert(buf.getData!(byte[])() == cast(byte[])"a"); 180 181 w.put("fo"); 182 assert(buf.getData!(ubyte[])() == cast(ubyte[])"afo"); 183 } 184 185 unittest { 186 string s = "0123456789"; 187 string l; 188 for(int i = 0; i < 1026; ++i) { 189 l ~= s; 190 } 191 192 StringBuffer buf; 193 buf.insertBack(l); 194 195 string ls = buf.getData(); 196 for(int i = 0; i < ls.length; ++i) { 197 assert(ls[i] == ('0' + (i % 10))); 198 } 199 }