1 module composer.writer; 2 3 import std.traits: isSomeChar, isSomeString, isArray, isIntegral, isPointer; 4 5 version(unittest) import unit_threaded; 6 else enum ShouldFail; 7 8 struct Buffer(Char) { 9 Char[] buffer; 10 alias buffer this; 11 12 size_t numCharsWritten; 13 } 14 15 /** 16 Writes a value to the buffer. 17 18 Supported types: 19 * bool 20 * char 21 * string 22 * byte/short/int/long and their unsigned counterparts 23 * structs (todo) 24 * classes (todo) 25 * arrays 26 * floating-point values (todo) 27 * pointers (in hexadecimal) 28 29 Params: 30 buffer = The buffer to write to 31 value = The value to write to the buffer 32 33 Returns: 34 The remainder of the buffer that has not been written to 35 yet, and the number of characters that were written by 36 the function. In the event of error, `numCharsWritten` 37 will be 0. 38 */ 39 Buffer!Char write(Char, A...)(auto ref Buffer!Char buffer, A args) 40 { 41 auto activeBuffer = buffer; 42 43 foreach(ref arg; args) 44 { 45 activeBuffer = write(activeBuffer, arg); 46 47 if (activeBuffer.numCharsWritten == 0) 48 return result(buffer, 0); 49 } 50 51 return activeBuffer; 52 } 53 54 @("write.all") 55 @safe @nogc pure nothrow unittest 56 { 57 char[128] chars; 58 auto result = write(Buffer!char(chars[]), "testMSG ", 20, 'c', " alo ", false); 59 60 assert(chars[0 .. result.numCharsWritten] == "testMSG 20c alo false"); 61 } 62 63 ///ditto 64 @safe @nogc 65 Buffer!Char write(Char, T)(ref Buffer!Char buffer, T integral) 66 if (isSomeChar!Char && isIntegral!T && !is(T == enum)) 67 { 68 import std.traits: Unqual, Unsigned; 69 70 enum charBufferLength = 32; 71 Char[charBufferLength] buf = void; 72 size_t charIndex = charBufferLength - 1; 73 74 const negative = integral < 0; 75 Unqual!(Unsigned!T) value = negative ? -integral : integral; 76 77 while (value >= 10) 78 { 79 buf[charIndex] = cast(Char) ((value % 10) + '0'); 80 value /= 10; 81 charIndex--; 82 } 83 84 buf[charIndex] = cast(char) (value + '0'); 85 86 if (negative) 87 { 88 charIndex--; 89 buf[charIndex] = '-'; 90 } 91 92 const strLength = charBufferLength - charIndex; 93 buffer[0 .. strLength] = buf[charIndex .. $]; 94 95 return result(buffer, strLength); 96 } 97 98 ///ditto 99 @safe @nogc 100 Buffer!Char write(Char, T)(ref Buffer!Char buffer, T boolValue) 101 if (isSomeChar!Char && is(T == bool)) 102 { 103 import std.utf: byUTF; 104 import std.array: array; 105 106 static immutable strings = ["false".byUTF!Char.array, "true".byUTF!Char.array]; 107 108 const stringLength = strings[boolValue].length; 109 if (buffer.length >= stringLength) 110 buffer[0 .. stringLength] = strings[boolValue]; 111 112 return result(buffer, stringLength); 113 } 114 115 @("write.bool") 116 @safe @nogc pure nothrow unittest 117 { 118 char[32] chars; 119 auto result = Buffer!char(chars[]).write(true); 120 assert(chars[0..result.numCharsWritten] == "true"); 121 122 result = Buffer!char(chars[]).write(false); 123 assert(chars[0..result.numCharsWritten] == "false"); 124 } 125 126 ///ditto 127 @safe @nogc 128 Buffer!Char write(Char, T)(ref Buffer!Char buffer, T character) 129 if (isSomeChar!Char && isSomeChar!T) 130 { 131 import std.utf: byUTF, codeLength; 132 133 const numRequiredChars = character.codeLength!Char; 134 if (numRequiredChars > buffer.length) 135 return result(buffer, 0); 136 137 T[1] slice = [character]; 138 139 size_t index; 140 foreach(c; slice[].byUTF!Char) 141 { 142 buffer[index] = c; 143 index++; 144 } 145 146 assert(index == numRequiredChars); 147 148 return result(buffer, numRequiredChars); 149 } 150 151 ///ditto 152 @safe @nogc 153 Buffer!Char write(Char, T)(ref Buffer!Char buffer, T pointer) 154 if (isSomeChar!Char && isPointer!T) 155 { 156 import std.traits: PointerTarget, fullyQualifiedName; 157 import std.conv: toChars, LetterCase; 158 159 //to allow for @nogc annotation 160 static immutable string tName = fullyQualifiedName!T; 161 162 auto activeBuffer = buffer.write(tName, '('); 163 const value = cast(size_t) pointer; 164 165 if (pointer is null) 166 activeBuffer = activeBuffer.write("null"); 167 else 168 {//write address as hexadecimal 169 activeBuffer = activeBuffer.write("0x"); 170 foreach(hexChar; value.toChars!(16, Char, LetterCase.upper)) 171 { 172 activeBuffer = activeBuffer.write(hexChar); 173 174 if (activeBuffer.numCharsWritten == 0) 175 return result(buffer, 0); 176 } 177 } 178 179 activeBuffer = activeBuffer.write(')'); 180 181 if (activeBuffer.numCharsWritten == 0) 182 return result(buffer, 0); 183 184 return activeBuffer; 185 } 186 187 @("write.pointer") 188 @safe @nogc pure nothrow 189 unittest 190 { 191 char[32] chars; 192 auto buffer = Buffer!char(chars[]); 193 auto result = buffer.write(()@trusted{return cast(char*) 0xDEADBEEF;}()); 194 auto resultStr = chars[0..result.numCharsWritten]; 195 196 assert(resultStr == "char*(0xDEADBEEF)"); 197 } 198 199 ///ditto 200 @safe @nogc 201 Buffer!Char write(Char, T)(ref Buffer!Char buffer, T array) 202 if (isSomeChar!Char && isArray!T && !isSomeString!T) 203 { 204 import std.traits: fullyQualifiedName; 205 static immutable name = fullyQualifiedName!T; 206 auto activeBuffer = buffer.write(name); 207 208 //take of the trailing `]` character 209 activeBuffer.buffer = buffer[fullyQualifiedName!T.length - 1 .. $]; 210 activeBuffer.numCharsWritten -= 1; 211 212 // if the array is not empty, print contents in comma-separated list 213 if (array.length > 0) 214 { 215 activeBuffer = activeBuffer.write(array[0]); 216 217 foreach(value; array[1 .. $]) 218 { 219 activeBuffer = activeBuffer.write(", ", value); 220 221 if (activeBuffer.numCharsWritten == 0) 222 return result(buffer, 0); 223 } 224 } 225 226 activeBuffer = activeBuffer.write(']'); 227 228 if (activeBuffer.numCharsWritten == 0) 229 return result(buffer, 0); 230 231 return activeBuffer; 232 } 233 234 @("write.array") 235 @safe pure nothrow unittest 236 { 237 static immutable array = [0, 1, -2, 3, 400]; 238 239 char[32] chars; 240 auto buffer = Buffer!char(chars[]); 241 auto result = buffer.write(array); 242 const str = chars[0 .. result.numCharsWritten]; 243 assert(chars[0 .. result.numCharsWritten] == "immutable(int)[0, 1, -2, 3, 400]"); 244 } 245 246 ///ditto 247 @safe @nogc 248 Buffer!Char write(Char, T)(ref Buffer!Char buffer, T str) 249 if (isSomeChar!Char && isSomeString!T) 250 { 251 import std.range: ElementEncodingType; 252 253 static if (is(ElementEncodingType!T == Char)) 254 { 255 import core.stdc..string: memcpy; 256 257 if (str.length > buffer.length) 258 return result(buffer, 0); 259 260 memcpy(&buffer[0], &str[0], str.length * Char.sizeof); 261 262 return result(buffer, str.length); 263 } 264 else 265 { 266 import std.utf: byUTF; 267 268 size_t numCharsWritten; 269 270 foreach(strChar; str.byUTF!Char) 271 { 272 assert(numCharsWritten < buffer.length); 273 buffer[numCharsWritten] = strChar; 274 numCharsWritten++; 275 276 if (numCharsWritten >= buffer.length) 277 return result(buffer, 0); 278 } 279 280 return result(buffer, numCharsWritten); 281 } 282 } 283 284 version(unittest) 285 @Types!(char, wchar, dchar) 286 @safe writeString(T)() { 287 import std.utf: byUTF; 288 import std.array: array; 289 290 static immutable testString = "this is a string".byUTF!T.array; 291 292 @safe void testWrite(Char)() 293 { 294 Char[32] chars; 295 auto buffer = Buffer!Char(chars[]); 296 auto result = buffer.write(testString); 297 298 result.numCharsWritten.shouldEqual(testString.length); 299 result.buffer.length.shouldEqual(chars.length - testString.length); 300 chars[0..result.numCharsWritten].shouldEqual(testString); 301 } 302 303 testWrite!char; 304 testWrite!wchar; 305 testWrite!dchar; 306 } 307 308 ///ditto 309 @safe @nogc 310 Buffer!Char write(Char, T)(ref Buffer!Char buffer, T enumeration) 311 if (isSomeChar!Char && is(T == enum)) 312 { 313 import std.traits: fullyQualifiedName, EnumMembers; 314 import std.meta: NoDuplicates; 315 import std.conv: to; 316 317 auto activeBuffer = buffer.write(fullyQualifiedName!T, '.'); 318 319 if (activeBuffer.numCharsWritten == 0) 320 return result(buffer, 0); 321 322 //write enum name 323 //generate if statements 324 switch(enumeration) 325 { 326 foreach(member; NoDuplicates!(EnumMembers!T)) 327 { 328 case member: 329 enum memberName = "name_" ~ member.to!string; 330 mixin("static immutable " ~ memberName ~ " = member.to!string;"); 331 mixin("activeBuffer = activeBuffer.write(" ~ memberName ~ ");"); 332 333 if (activeBuffer.numCharsWritten == 0) 334 return result(buffer, 0); 335 return activeBuffer; 336 } 337 default: 338 } 339 340 return activeBuffer; 341 } 342 343 @("write.enum") 344 @safe @nogc pure nothrow unittest 345 { 346 import std.traits: fullyQualifiedName; 347 enum Enum { value1, value2 } 348 349 char[128] chars; 350 auto result = Buffer!char(chars[]).write(Enum.value1); 351 assert(chars[0..result.numCharsWritten] == fullyQualifiedName!(Enum.value1)); 352 } 353 354 ///ditto 355 Buffer!Char write(Char, T)(ref Buffer!Char buffer, T object) 356 if (isSomeChar!Char && (is(T == class) || is(T == interface))) 357 { 358 import std.traits: hasMember; 359 360 if (object is null) 361 return buffer.write("null"); 362 363 static if (hasMember!(T, "toString")) 364 return buffer.write(object.toString()); 365 else 366 return buffer.dumpObjectContents(structure); 367 } 368 369 ///ditto 370 Buffer!Char write(Char, T)(ref Buffer!Char buffer, auto ref T structure) 371 if (isSomeChar!Char && is(T == struct)) 372 { 373 import std.traits: hasMember; 374 375 static if (hasMember!(T, "toString")) 376 return buffer.write(structure.toString()); 377 else 378 return buffer.dumpObjectContents(structure); 379 } 380 381 @("write.struct") 382 @safe @nogc pure nothrow unittest 383 { 384 struct TestStruct 385 { 386 int value; 387 bool flag; 388 string msg; 389 } 390 391 char[128] chars; 392 auto buffer = Buffer!char(chars[]); 393 auto result = buffer.write(TestStruct(-129873, false, "Hello!")); 394 auto resultStr = chars[0..result.numCharsWritten]; 395 // writelnUt(resultStr); 396 } 397 398 @safe @nogc 399 Buffer!Char dumpObjectContents(Char, T)(ref Buffer!Char buffer, auto ref T object) pure nothrow 400 { 401 import std.traits: FieldNameTuple, fullyQualifiedName; 402 403 auto activeBuffer = buffer; 404 405 activeBuffer = activeBuffer.write(fullyQualifiedName!T, "{ "); 406 407 if (activeBuffer.numCharsWritten == 0) 408 return result(buffer, 0); 409 410 enum fields = FieldNameTuple!T; 411 412 if (fields.length > 0) 413 { 414 mixin("activeBuffer = activeBuffer.write(fields[0], \": \", object." ~ fields[0] ~ ");"); 415 416 if (activeBuffer.numCharsWritten == 0) 417 return result(buffer, 0); 418 } 419 420 static foreach(field; fields) 421 { 422 activeBuffer = activeBuffer.write(", "); 423 mixin("activeBuffer = activeBuffer.write(field, \": \", object." ~ field ~ ");"); 424 425 if (activeBuffer.numCharsWritten == 0) 426 return result(buffer, 0); 427 } 428 429 activeBuffer = activeBuffer.write(" }"); 430 431 return activeBuffer; 432 } 433 434 pragma(inline) 435 private auto result(Char)(ref Buffer!Char buffer, size_t numCharsWritten) 436 { 437 return Buffer!Char(buffer[numCharsWritten .. $], buffer.numCharsWritten + numCharsWritten); 438 }