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 }