1 /**
2  * Fast string building with minimum heap allocations.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.utils.textout;
15 
16 import ae.utils.appender;
17 
18 // **************************************************************************
19 
20 template isStringSink(T)
21 {
22 	enum isStringSink = is(typeof(T.init.put("Test")));
23 }
24 deprecated alias IsStringSink = isStringSink;
25 
26 // **************************************************************************
27 
28 alias FastAppender!(immutable(char)) StringBuilder;
29 alias FastAppender!           char   StringBuffer;
30 
31 static assert(isStringSink!StringBuilder);
32 static assert(isStringSink!StringBuffer);
33 
34 unittest
35 {
36 	StringBuilder sb;
37 	sb.put("Hello", ' ', "world!");
38 	assert(sb.get() == "Hello world!");
39 }
40 
41 unittest
42 {
43 	StringBuilder sb;
44 	foreach (n; 0..4096)
45 		sb.put("Hello", " ", "world!");
46 	string s;
47 	foreach (n; 0..4096)
48 		s ~= "Hello world!";
49 	assert(sb.get() == s);
50 }
51 
52 // **************************************************************************
53 
54 struct StaticBuf(T, size_t size)
55 {
56 	T[size] buf;
57 	size_t pos;
58 	void put(T v) { buf[pos++] = v; }
59 	void put(in T[] v) { buf[pos..pos+v.length] = v[]; pos+=v.length; }
60 	inout(T)[] data() inout { return buf[0..pos]; }
61 }
62 
63 // **************************************************************************
64 
65 /// Sink which simply counts how much data is written to it.
66 struct CountingWriter(T)
67 {
68 	size_t count;
69 
70 	void put(T v) { count++; }
71 	void put(in T[] v) { count += v.length; }
72 }
73 
74 // **************************************************************************
75 
76 /// Sink which simply copies data to a pointer and advances it.
77 /// No reallocation, no bounds check - unsafe.
78 struct BlindWriter(T)
79 {
80 	T* ptr;
81 
82 	void put(T v)
83 	{
84 		*ptr++ = v;
85 	}
86 
87 	void put(in T[] v)
88 	{
89 		import core.stdc.string;
90 		memcpy(ptr, v.ptr, v.length*T.sizeof);
91 		ptr += v.length;
92 	}
93 }
94 
95 static assert(isStringSink!(BlindWriter!char));
96 
97 version(unittest) import ae.utils.time;
98 
99 unittest
100 {
101 	import std.datetime;
102 
103 	char[64] buf;
104 	auto writer = BlindWriter!char(buf.ptr);
105 
106 	auto time = SysTime(DateTime(2010, 7, 4, 7, 6, 12), UTC());
107 	putTime(writer, time, TimeFormats.ISO8601);
108 	auto timeStr = buf[0..writer.ptr-buf.ptr];
109 	assert(timeStr == "2010-07-04T07:06:12+0000", timeStr.idup);
110 }
111 
112 // **************************************************************************
113 
114 /// Calls putter to count the length of the output, allocates a buffer of
115 /// that size, and then calls putter a second time to write to the buffer.
116 /// Returns the buffer.
117 
118 template countCopy(T)
119 {
120 	T[] countCopy(Putter)(Putter putter)
121 	{
122 		CountingWriter!T counter;
123 		putter(&counter);
124 
125 		T[] buf = new T[counter.count];
126 
127 		BlindWriter!T writer;
128 		writer.ptr = buf.ptr;
129 		putter(&writer);
130 		assert(writer.ptr == buf.ptr + buf.length);
131 
132 		return buf;
133 	}
134 }
135 
136 version(none) // Method alias binding
137 T[] countCopy(T, alias putter)()
138 {
139 	CountingWriter!T counter;
140 	putter(&counter);
141 
142 	T[] buf = new T[counter.count];
143 
144 	BlindWriter!T writer;
145 	writer.ptr = buf.ptr;
146 	putter(&writer);
147 	assert(writer.ptr = buf.ptr + buf.length);
148 
149 	return buf;
150 }
151 
152 // **************************************************************************
153 
154 /// Default implementation of put for dchars
155 void put(S)(ref S sink, dchar c)
156 	if (isStringSink!S)
157 {
158 	import std.utf;
159 	char[4] buf;
160 	auto size = encode(buf, c);
161 	sink.put(buf[0..size]);
162 }
163 
164 unittest
165 {
166 	StringBuilder sb;
167 	put(sb, 'Я');
168 	assert(sb.get() == "Я");
169 }
170 
171 import std.traits;
172 import ae.utils.text : toDec, decimalSize;
173 
174 /// Default implementation of put for numbers (uses decimal ASCII)
175 void put(S, N)(ref S sink, N n)
176 	if (isStringSink!S && is(N : long) && !isSomeChar!N)
177 {
178 	sink.putDecimal(n);
179 }
180 
181 void putDecimal(S, N)(ref S sink, N n)
182 {
183 	char[decimalSize!N] buf = void;
184 	sink.put(toDec(n, buf));
185 }
186 
187 unittest
188 {
189 	void test(N)(N n)
190 	{
191 		import std.conv;
192 		StringBuilder sb;
193 		put(sb, n);
194 		assert(sb.get() == text(n), sb.get() ~ "!=" ~ text(n));
195 	}
196 
197 	test(0);
198 	test(1);
199 	test(-1);
200 	test(0xFFFFFFFFFFFFFFFFLU);
201 }