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