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 }