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 	T[] data() { 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 /// Default implementation of put for dchars
115 void put(S)(ref S sink, dchar c)
116 	if (isStringSink!S)
117 {
118 	import std.utf;
119 	char[4] buf;
120 	auto size = encode(buf, c);
121 	sink.put(buf[0..size]);
122 }
123 
124 unittest
125 {
126 	StringBuilder sb;
127 	put(sb, 'Я');
128 	assert(sb.get() == "Я");
129 }
130 
131 import std.traits;
132 import ae.utils.text : toDec, DecimalSize;
133 
134 /// Default implementation of put for numbers (uses decimal ASCII)
135 void put(S, N)(ref S sink, N n)
136 	if (isStringSink!S && is(N : long) && !isSomeChar!N)
137 {
138 	sink.putDecimal(n);
139 }
140 
141 void putDecimal(S, N)(ref S sink, N n)
142 {
143 	char[DecimalSize!N] buf = void;
144 	sink.put(toDec(n, buf));
145 }
146 
147 unittest
148 {
149 	void test(N)(N n)
150 	{
151 		import std.conv;
152 		StringBuilder sb;
153 		put(sb, n);
154 		assert(sb.get() == text(n), sb.get() ~ "!=" ~ text(n));
155 	}
156 
157 	test(0);
158 	test(1);
159 	test(-1);
160 	test(0xFFFFFFFFFFFFFFFFLU);
161 }