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 if (str.length > buffer.length) 256 return result(buffer, 0); 257 258 buffer[0..str.length] = str[]; 259 260 return result(buffer, str.length); 261 } 262 else 263 { 264 import std.utf: byUTF; 265 266 size_t numCharsWritten = 0; 267 foreach(strChar; str.byUTF!Char) 268 { 269 if (numCharsWritten == buffer.length) 270 return result(buffer, 0); 271 272 buffer[numCharsWritten] = strChar; 273 numCharsWritten++; 274 } 275 276 return result(buffer, numCharsWritten); 277 } 278 } 279 280 version(unittest) 281 @Types!(char, wchar, dchar) 282 @safe writeString(T)() { 283 import std.utf: byUTF; 284 import std.array: array; 285 286 static immutable testString = "this is a string".byUTF!T.array; 287 288 @safe void testWrite(Char)() 289 { 290 Char[32] chars; 291 auto buffer = Buffer!Char(chars[]); 292 auto result = buffer.write(testString); 293 294 result.numCharsWritten.shouldEqual(testString.length); 295 result.buffer.length.shouldEqual(chars.length - testString.length); 296 chars[0..result.numCharsWritten].shouldEqual(testString); 297 } 298 299 testWrite!char; 300 testWrite!wchar; 301 testWrite!dchar; 302 } 303 304 ///ditto 305 @safe @nogc 306 Buffer!Char write(Char, T)(ref Buffer!Char buffer, T enumeration) 307 if (isSomeChar!Char && is(T == enum)) 308 { 309 import std.traits: fullyQualifiedName, EnumMembers; 310 import std.meta: NoDuplicates; 311 import std.conv: to; 312 313 static immutable fqn = fullyQualifiedName!T; 314 auto activeBuffer = buffer.write(fqn, '.'); 315 316 if (activeBuffer.numCharsWritten == 0) 317 return result(buffer, 0); 318 319 //write enum name 320 //generate if statements 321 switch(enumeration) 322 { 323 foreach(member; NoDuplicates!(EnumMembers!T)) 324 { 325 case member: 326 enum memberName = "name_" ~ member.to!string; 327 mixin("static immutable " ~ memberName ~ " = member.to!string;"); 328 mixin("activeBuffer = activeBuffer.write(" ~ memberName ~ ");"); 329 330 if (activeBuffer.numCharsWritten == 0) 331 return result(buffer, 0); 332 return activeBuffer; 333 } 334 default: 335 } 336 337 return activeBuffer; 338 } 339 340 @("write.enum") 341 @safe @nogc pure nothrow unittest 342 { 343 import std.traits: fullyQualifiedName; 344 enum Enum { value1, value2 } 345 346 char[128] chars; 347 auto result = Buffer!char(chars[]).write(Enum.value1); 348 assert(chars[0..result.numCharsWritten] == fullyQualifiedName!(Enum.value1)); 349 } 350 351 ///ditto 352 Buffer!Char write(Char, T)(ref Buffer!Char buffer, T object) 353 if (isSomeChar!Char && (is(T == class) || is(T == interface))) 354 { 355 import std.traits: hasMember; 356 357 if (object is null) 358 return buffer.write("null"); 359 360 static if (hasMember!(T, "toString")) 361 return buffer.write(object.toString()); 362 else 363 return buffer.dumpObjectContents(structure); 364 } 365 366 ///ditto 367 Buffer!Char write(Char, T)(ref Buffer!Char buffer, auto ref T structure) 368 if (isSomeChar!Char && is(T == struct)) 369 { 370 import std.traits: hasMember; 371 372 static if (hasMember!(T, "toString")) 373 return buffer.write(structure.toString()); 374 else 375 return buffer.dumpObjectContents(structure); 376 } 377 378 @("write.struct") 379 @safe @nogc pure nothrow unittest 380 { 381 struct TestStruct 382 { 383 int value; 384 bool flag; 385 string msg; 386 } 387 388 char[128] chars; 389 auto buffer = Buffer!char(chars[]); 390 auto result = buffer.write(TestStruct(-129873, false, "Hello!")); 391 auto resultStr = chars[0..result.numCharsWritten]; 392 // writelnUt(resultStr); 393 } 394 395 @safe @nogc 396 Buffer!Char dumpObjectContents(Char, T)(ref Buffer!Char buffer, auto ref T object) pure nothrow 397 { 398 import std.traits: FieldNameTuple, fullyQualifiedName; 399 400 auto activeBuffer = buffer; 401 402 activeBuffer = activeBuffer.write(fullyQualifiedName!T, "{ "); 403 404 if (activeBuffer.numCharsWritten == 0) 405 return result(buffer, 0); 406 407 enum fields = FieldNameTuple!T; 408 409 if (fields.length > 0) 410 { 411 mixin("activeBuffer = activeBuffer.write(fields[0], \": \", object." ~ fields[0] ~ ");"); 412 413 if (activeBuffer.numCharsWritten == 0) 414 return result(buffer, 0); 415 } 416 417 static foreach(field; fields) 418 { 419 activeBuffer = activeBuffer.write(", "); 420 mixin("activeBuffer = activeBuffer.write(field, \": \", object." ~ field ~ ");"); 421 422 if (activeBuffer.numCharsWritten == 0) 423 return result(buffer, 0); 424 } 425 426 activeBuffer = activeBuffer.write(" }"); 427 428 return activeBuffer; 429 } 430 431 pragma(inline) 432 private auto result(Char)(ref Buffer!Char buffer, size_t numCharsWritten) 433 { 434 return Buffer!Char(buffer[numCharsWritten .. $], buffer.numCharsWritten + numCharsWritten); 435 }