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 }