1 // Written in the D programming language. 2 3 /** 4 Copyright: Copyright Digital Mars 2000-2013. 5 6 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 7 8 Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com, 9 Andrei Alexandrescu), and Kenji Hara 10 11 Source: $(PHOBOSSRC std/_format.d) 12 */ 13 module undead.doformat; 14 15 //debug=format; // uncomment to turn on debugging printf's 16 17 import core.vararg; 18 import std.exception; 19 import std.meta; 20 import std.range.primitives; 21 import std.traits; 22 import std.format; 23 24 version(CRuntime_DigitalMars) 25 { 26 version = DigitalMarsC; 27 } 28 29 version (DigitalMarsC) 30 { 31 // This is DMC's internal floating point formatting function 32 extern (C) 33 { 34 extern shared char* function(int c, int flags, int precision, 35 in real* pdval, 36 char* buf, size_t* psl, int width) __pfloatfmt; 37 } 38 } 39 40 /********************************************************************** 41 * Signals a mismatch between a format and its corresponding argument. 42 */ 43 class FormatException : Exception 44 { 45 @safe pure nothrow 46 this() 47 { 48 super("format error"); 49 } 50 51 @safe pure nothrow 52 this(string msg, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) 53 { 54 super(msg, fn, ln, next); 55 } 56 } 57 58 59 // Legacy implementation 60 61 enum Mangle : char 62 { 63 Tvoid = 'v', 64 Tbool = 'b', 65 Tbyte = 'g', 66 Tubyte = 'h', 67 Tshort = 's', 68 Tushort = 't', 69 Tint = 'i', 70 Tuint = 'k', 71 Tlong = 'l', 72 Tulong = 'm', 73 Tfloat = 'f', 74 Tdouble = 'd', 75 Treal = 'e', 76 77 Tchar = 'a', 78 Twchar = 'u', 79 Tdchar = 'w', 80 81 Tarray = 'A', 82 Tsarray = 'G', 83 Taarray = 'H', 84 Tpointer = 'P', 85 Tfunction = 'F', 86 Tident = 'I', 87 Tclass = 'C', 88 Tstruct = 'S', 89 Tenum = 'E', 90 Ttypedef = 'T', 91 Tdelegate = 'D', 92 93 Tconst = 'x', 94 Timmutable = 'y', 95 } 96 97 // return the TypeInfo for a primitive type and null otherwise. This 98 // is required since for arrays of ints we only have the mangled char 99 // to work from. If arrays always subclassed TypeInfo_Array this 100 // routine could go away. 101 private TypeInfo primitiveTypeInfo(Mangle m) 102 { 103 // BUG: should fix this in static this() to avoid double checked locking bug 104 __gshared TypeInfo[Mangle] dic; 105 if (!dic.length) 106 { 107 dic = [ 108 Mangle.Tvoid : typeid(void), 109 Mangle.Tbool : typeid(bool), 110 Mangle.Tbyte : typeid(byte), 111 Mangle.Tubyte : typeid(ubyte), 112 Mangle.Tshort : typeid(short), 113 Mangle.Tushort : typeid(ushort), 114 Mangle.Tint : typeid(int), 115 Mangle.Tuint : typeid(uint), 116 Mangle.Tlong : typeid(long), 117 Mangle.Tulong : typeid(ulong), 118 Mangle.Tfloat : typeid(float), 119 Mangle.Tdouble : typeid(double), 120 Mangle.Treal : typeid(real), 121 Mangle.Tchar : typeid(char), 122 Mangle.Twchar : typeid(wchar), 123 Mangle.Tdchar : typeid(dchar) 124 ]; 125 } 126 auto p = m in dic; 127 return p ? *p : null; 128 } 129 130 // This stuff has been removed from the docs and is planned for deprecation. 131 /* 132 * Interprets variadic argument list pointed to by argptr whose types 133 * are given by arguments[], formats them according to embedded format 134 * strings in the variadic argument list, and sends the resulting 135 * characters to putc. 136 * 137 * The variadic arguments are consumed in order. Each is formatted 138 * into a sequence of chars, using the default format specification 139 * for its type, and the characters are sequentially passed to putc. 140 * If a $(D char[]), $(D wchar[]), or $(D dchar[]) argument is 141 * encountered, it is interpreted as a format string. As many 142 * arguments as specified in the format string are consumed and 143 * formatted according to the format specifications in that string and 144 * passed to putc. If there are too few remaining arguments, a 145 * $(D FormatException) is thrown. If there are more remaining arguments than 146 * needed by the format specification, the default processing of 147 * arguments resumes until they are all consumed. 148 * 149 * Params: 150 * putc = Output is sent do this delegate, character by character. 151 * arguments = Array of $(D TypeInfo)s, one for each argument to be formatted. 152 * argptr = Points to variadic argument list. 153 * 154 * Throws: 155 * Mismatched arguments and formats result in a $(D FormatException) being thrown. 156 * 157 * Format_String: 158 * <a name="format-string">$(I Format strings)</a> 159 * consist of characters interspersed with 160 * $(I format specifications). Characters are simply copied 161 * to the output (such as putc) after any necessary conversion 162 * to the corresponding UTF-8 sequence. 163 * 164 * A $(I format specification) starts with a '%' character, 165 * and has the following grammar: 166 167 $(CONSOLE 168 $(I FormatSpecification): 169 $(B '%%') 170 $(B '%') $(I Flags) $(I Width) $(I Precision) $(I FormatChar) 171 172 $(I Flags): 173 $(I empty) 174 $(B '-') $(I Flags) 175 $(B '+') $(I Flags) 176 $(B '#') $(I Flags) 177 $(B '0') $(I Flags) 178 $(B ' ') $(I Flags) 179 180 $(I Width): 181 $(I empty) 182 $(I Integer) 183 $(B '*') 184 185 $(I Precision): 186 $(I empty) 187 $(B '.') 188 $(B '.') $(I Integer) 189 $(B '.*') 190 191 $(I Integer): 192 $(I Digit) 193 $(I Digit) $(I Integer) 194 195 $(I Digit): 196 $(B '0') 197 $(B '1') 198 $(B '2') 199 $(B '3') 200 $(B '4') 201 $(B '5') 202 $(B '6') 203 $(B '7') 204 $(B '8') 205 $(B '9') 206 207 $(I FormatChar): 208 $(B 's') 209 $(B 'b') 210 $(B 'd') 211 $(B 'o') 212 $(B 'x') 213 $(B 'X') 214 $(B 'e') 215 $(B 'E') 216 $(B 'f') 217 $(B 'F') 218 $(B 'g') 219 $(B 'G') 220 $(B 'a') 221 $(B 'A') 222 ) 223 $(DL 224 $(DT $(I Flags)) 225 $(DL 226 $(DT $(B '-')) 227 $(DD 228 Left justify the result in the field. 229 It overrides any $(B 0) flag.) 230 231 $(DT $(B '+')) 232 $(DD Prefix positive numbers in a signed conversion with a $(B +). 233 It overrides any $(I space) flag.) 234 235 $(DT $(B '#')) 236 $(DD Use alternative formatting: 237 $(DL 238 $(DT For $(B 'o'):) 239 $(DD Add to precision as necessary so that the first digit 240 of the octal formatting is a '0', even if both the argument 241 and the $(I Precision) are zero.) 242 $(DT For $(B 'x') ($(B 'X')):) 243 $(DD If non-zero, prefix result with $(B 0x) ($(B 0X)).) 244 $(DT For floating point formatting:) 245 $(DD Always insert the decimal point.) 246 $(DT For $(B 'g') ($(B 'G')):) 247 $(DD Do not elide trailing zeros.) 248 )) 249 250 $(DT $(B '0')) 251 $(DD For integer and floating point formatting when not nan or 252 infinity, use leading zeros 253 to pad rather than spaces. 254 Ignore if there's a $(I Precision).) 255 256 $(DT $(B ' ')) 257 $(DD Prefix positive numbers in a signed conversion with a space.) 258 ) 259 260 $(DT $(I Width)) 261 $(DD 262 Specifies the minimum field width. 263 If the width is a $(B *), the next argument, which must be 264 of type $(B int), is taken as the width. 265 If the width is negative, it is as if the $(B -) was given 266 as a $(I Flags) character.) 267 268 $(DT $(I Precision)) 269 $(DD Gives the precision for numeric conversions. 270 If the precision is a $(B *), the next argument, which must be 271 of type $(B int), is taken as the precision. If it is negative, 272 it is as if there was no $(I Precision).) 273 274 $(DT $(I FormatChar)) 275 $(DD 276 $(DL 277 $(DT $(B 's')) 278 $(DD The corresponding argument is formatted in a manner consistent 279 with its type: 280 $(DL 281 $(DT $(B bool)) 282 $(DD The result is <tt>'true'</tt> or <tt>'false'</tt>.) 283 $(DT integral types) 284 $(DD The $(B %d) format is used.) 285 $(DT floating point types) 286 $(DD The $(B %g) format is used.) 287 $(DT string types) 288 $(DD The result is the string converted to UTF-8.) 289 A $(I Precision) specifies the maximum number of characters 290 to use in the result. 291 $(DT classes derived from $(B Object)) 292 $(DD The result is the string returned from the class instance's 293 $(B .toString()) method. 294 A $(I Precision) specifies the maximum number of characters 295 to use in the result.) 296 $(DT non-string static and dynamic arrays) 297 $(DD The result is [s<sub>0</sub>, s<sub>1</sub>, ...] 298 where s<sub>k</sub> is the kth element 299 formatted with the default format.) 300 )) 301 302 $(DT $(B 'b','d','o','x','X')) 303 $(DD The corresponding argument must be an integral type 304 and is formatted as an integer. If the argument is a signed type 305 and the $(I FormatChar) is $(B d) it is converted to 306 a signed string of characters, otherwise it is treated as 307 unsigned. An argument of type $(B bool) is formatted as '1' 308 or '0'. The base used is binary for $(B b), octal for $(B o), 309 decimal 310 for $(B d), and hexadecimal for $(B x) or $(B X). 311 $(B x) formats using lower case letters, $(B X) uppercase. 312 If there are fewer resulting digits than the $(I Precision), 313 leading zeros are used as necessary. 314 If the $(I Precision) is 0 and the number is 0, no digits 315 result.) 316 317 $(DT $(B 'e','E')) 318 $(DD A floating point number is formatted as one digit before 319 the decimal point, $(I Precision) digits after, the $(I FormatChar), 320 ±, followed by at least a two digit exponent: $(I d.dddddd)e$(I ±dd). 321 If there is no $(I Precision), six 322 digits are generated after the decimal point. 323 If the $(I Precision) is 0, no decimal point is generated.) 324 325 $(DT $(B 'f','F')) 326 $(DD A floating point number is formatted in decimal notation. 327 The $(I Precision) specifies the number of digits generated 328 after the decimal point. It defaults to six. At least one digit 329 is generated before the decimal point. If the $(I Precision) 330 is zero, no decimal point is generated.) 331 332 $(DT $(B 'g','G')) 333 $(DD A floating point number is formatted in either $(B e) or 334 $(B f) format for $(B g); $(B E) or $(B F) format for 335 $(B G). 336 The $(B f) format is used if the exponent for an $(B e) format 337 is greater than -5 and less than the $(I Precision). 338 The $(I Precision) specifies the number of significant 339 digits, and defaults to six. 340 Trailing zeros are elided after the decimal point, if the fractional 341 part is zero then no decimal point is generated.) 342 343 $(DT $(B 'a','A')) 344 $(DD A floating point number is formatted in hexadecimal 345 exponential notation 0x$(I h.hhhhhh)p$(I ±d). 346 There is one hexadecimal digit before the decimal point, and as 347 many after as specified by the $(I Precision). 348 If the $(I Precision) is zero, no decimal point is generated. 349 If there is no $(I Precision), as many hexadecimal digits as 350 necessary to exactly represent the mantissa are generated. 351 The exponent is written in as few digits as possible, 352 but at least one, is in decimal, and represents a power of 2 as in 353 $(I h.hhhhhh)*2<sup>$(I ±d)</sup>. 354 The exponent for zero is zero. 355 The hexadecimal digits, x and p are in upper case if the 356 $(I FormatChar) is upper case.) 357 ) 358 359 Floating point NaN's are formatted as $(B nan) if the 360 $(I FormatChar) is lower case, or $(B NAN) if upper. 361 Floating point infinities are formatted as $(B inf) or 362 $(B infinity) if the 363 $(I FormatChar) is lower case, or $(B INF) or $(B INFINITY) if upper. 364 )) 365 366 Example: 367 368 ------------------------- 369 import core.stdc.stdio; 370 import std.format; 371 372 void myPrint(...) 373 { 374 void putc(dchar c) 375 { 376 fputc(c, stdout); 377 } 378 379 std.format.doFormat(&putc, _arguments, _argptr); 380 } 381 382 void main() 383 { 384 int x = 27; 385 386 // prints 'The answer is 27:6' 387 myPrint("The answer is %s:", x, 6); 388 } 389 ------------------------ 390 */ 391 void doFormat()(scope void delegate(dchar) putc, TypeInfo[] arguments, va_list ap) 392 { 393 import std.utf : encode, toUCSindex, isValidDchar, UTFException, toUTF8; 394 import core.stdc.string : strlen; 395 import core.stdc.stdlib : alloca, malloc, realloc, free; 396 import core.stdc.stdio : snprintf; 397 398 size_t bufLength = 1024; 399 void* argBuffer = malloc(bufLength); 400 scope(exit) free(argBuffer); 401 402 size_t bufUsed = 0; 403 foreach (ti; arguments) 404 { 405 // Ensure the required alignment 406 bufUsed += ti.talign - 1; 407 bufUsed -= (cast(size_t)argBuffer + bufUsed) & (ti.talign - 1); 408 auto pos = bufUsed; 409 // Align to next word boundary 410 bufUsed += ti.tsize + size_t.sizeof - 1; 411 bufUsed -= (cast(size_t)argBuffer + bufUsed) & (size_t.sizeof - 1); 412 // Resize buffer if necessary 413 while (bufUsed > bufLength) 414 { 415 bufLength *= 2; 416 argBuffer = realloc(argBuffer, bufLength); 417 } 418 // Copy argument into buffer 419 va_arg(ap, ti, argBuffer + pos); 420 } 421 422 auto argptr = argBuffer; 423 void* skipArg(TypeInfo ti) 424 { 425 // Ensure the required alignment 426 argptr += ti.talign - 1; 427 argptr -= cast(size_t)argptr & (ti.talign - 1); 428 auto p = argptr; 429 // Align to next word boundary 430 argptr += ti.tsize + size_t.sizeof - 1; 431 argptr -= cast(size_t)argptr & (size_t.sizeof - 1); 432 return p; 433 } 434 auto getArg(T)() 435 { 436 return *cast(T*)skipArg(typeid(T)); 437 } 438 439 TypeInfo ti; 440 Mangle m; 441 uint flags; 442 int field_width; 443 int precision; 444 445 enum : uint 446 { 447 FLdash = 1, 448 FLplus = 2, 449 FLspace = 4, 450 FLhash = 8, 451 FLlngdbl = 0x20, 452 FL0pad = 0x40, 453 FLprecision = 0x80, 454 } 455 456 static TypeInfo skipCI(TypeInfo valti) 457 { 458 for (;;) 459 { 460 if (typeid(valti).name.length == 18 && 461 typeid(valti).name[9..18] == "Invariant") 462 valti = (cast(TypeInfo_Invariant)valti).base; 463 else if (typeid(valti).name.length == 14 && 464 typeid(valti).name[9..14] == "Const") 465 valti = (cast(TypeInfo_Const)valti).base; 466 else 467 break; 468 } 469 470 return valti; 471 } 472 473 void formatArg(char fc) 474 { 475 bool vbit; 476 ulong vnumber; 477 char vchar; 478 dchar vdchar; 479 Object vobject; 480 real vreal; 481 Mangle m2; 482 int signed = 0; 483 uint base = 10; 484 int uc; 485 char[ulong.sizeof * 8] tmpbuf; // long enough to print long in binary 486 const(char)* prefix = ""; 487 string s; 488 489 void putstr(const char[] s) 490 { 491 //printf("putstr: s = %.*s, flags = x%x\n", s.length, s.ptr, flags); 492 ptrdiff_t padding = field_width - 493 (strlen(prefix) + toUCSindex(s, s.length)); 494 ptrdiff_t prepad = 0; 495 ptrdiff_t postpad = 0; 496 if (padding > 0) 497 { 498 if (flags & FLdash) 499 postpad = padding; 500 else 501 prepad = padding; 502 } 503 504 if (flags & FL0pad) 505 { 506 while (*prefix) 507 putc(*prefix++); 508 while (prepad--) 509 putc('0'); 510 } 511 else 512 { 513 while (prepad--) 514 putc(' '); 515 while (*prefix) 516 putc(*prefix++); 517 } 518 519 foreach (dchar c; s) 520 putc(c); 521 522 while (postpad--) 523 putc(' '); 524 } 525 526 void putreal(real v) 527 { 528 //printf("putreal %Lg\n", vreal); 529 530 switch (fc) 531 { 532 case 's': 533 fc = 'g'; 534 break; 535 536 case 'f', 'F', 'e', 'E', 'g', 'G', 'a', 'A': 537 break; 538 539 default: 540 //printf("fc = '%c'\n", fc); 541 Lerror: 542 throw new FormatException("incompatible format character for floating point type"); 543 } 544 version (DigitalMarsC) 545 { 546 uint sl; 547 char[] fbuf = tmpbuf; 548 if (!(flags & FLprecision)) 549 precision = 6; 550 while (1) 551 { 552 sl = fbuf.length; 553 prefix = (*__pfloatfmt)(fc, flags | FLlngdbl, 554 precision, &v, cast(char*)fbuf, &sl, field_width); 555 if (sl != -1) 556 break; 557 sl = fbuf.length * 2; 558 fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl]; 559 } 560 putstr(fbuf[0 .. sl]); 561 } 562 else 563 { 564 ptrdiff_t sl; 565 char[] fbuf = tmpbuf; 566 char[12] format; 567 format[0] = '%'; 568 int i = 1; 569 if (flags & FLdash) 570 format[i++] = '-'; 571 if (flags & FLplus) 572 format[i++] = '+'; 573 if (flags & FLspace) 574 format[i++] = ' '; 575 if (flags & FLhash) 576 format[i++] = '#'; 577 if (flags & FL0pad) 578 format[i++] = '0'; 579 format[i + 0] = '*'; 580 format[i + 1] = '.'; 581 format[i + 2] = '*'; 582 format[i + 3] = 'L'; 583 format[i + 4] = fc; 584 format[i + 5] = 0; 585 if (!(flags & FLprecision)) 586 precision = -1; 587 while (1) 588 { 589 sl = fbuf.length; 590 int n; 591 version (CRuntime_Microsoft) 592 { 593 import std.math : isNaN, isInfinity; 594 if (isNaN(v)) // snprintf writes 1.#QNAN 595 n = snprintf(fbuf.ptr, sl, "nan"); 596 else if (isInfinity(v)) // snprintf writes 1.#INF 597 n = snprintf(fbuf.ptr, sl, v < 0 ? "-inf" : "inf"); 598 else 599 n = snprintf(fbuf.ptr, sl, format.ptr, field_width, 600 precision, cast(double)v); 601 } 602 else 603 n = snprintf(fbuf.ptr, sl, format.ptr, field_width, 604 precision, v); 605 //printf("format = '%s', n = %d\n", cast(char*)format, n); 606 if (n >= 0 && n < sl) 607 { sl = n; 608 break; 609 } 610 if (n < 0) 611 sl = sl * 2; 612 else 613 sl = n + 1; 614 fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl]; 615 } 616 putstr(fbuf[0 .. sl]); 617 } 618 return; 619 } 620 621 static Mangle getMan(TypeInfo ti) 622 { 623 auto m = cast(Mangle)typeid(ti).name[9]; 624 if (typeid(ti).name.length == 20 && 625 typeid(ti).name[9..20] == "StaticArray") 626 m = cast(Mangle)'G'; 627 return m; 628 } 629 630 /* p = pointer to the first element in the array 631 * len = number of elements in the array 632 * valti = type of the elements 633 */ 634 void putArray(void* p, size_t len, TypeInfo valti) 635 { 636 //printf("\nputArray(len = %u), tsize = %u\n", len, valti.tsize); 637 putc('['); 638 valti = skipCI(valti); 639 size_t tsize = valti.tsize; 640 auto argptrSave = argptr; 641 auto tiSave = ti; 642 auto mSave = m; 643 ti = valti; 644 //printf("\n%.*s\n", typeid(valti).name.length, typeid(valti).name.ptr); 645 m = getMan(valti); 646 while (len--) 647 { 648 //doFormat(putc, (&valti)[0 .. 1], p); 649 argptr = p; 650 formatArg('s'); 651 p += tsize; 652 if (len > 0) putc(','); 653 } 654 m = mSave; 655 ti = tiSave; 656 argptr = argptrSave; 657 putc(']'); 658 } 659 660 void putAArray(ubyte[long] vaa, TypeInfo valti, TypeInfo keyti) 661 { 662 putc('['); 663 bool comma=false; 664 auto argptrSave = argptr; 665 auto tiSave = ti; 666 auto mSave = m; 667 valti = skipCI(valti); 668 keyti = skipCI(keyti); 669 foreach (ref fakevalue; vaa) 670 { 671 if (comma) putc(','); 672 comma = true; 673 void *pkey = &fakevalue; 674 version (D_LP64) 675 pkey -= (long.sizeof + 15) & ~(15); 676 else 677 pkey -= (long.sizeof + size_t.sizeof - 1) & ~(size_t.sizeof - 1); 678 679 // the key comes before the value 680 auto keysize = keyti.tsize; 681 version (D_LP64) 682 auto keysizet = (keysize + 15) & ~(15); 683 else 684 auto keysizet = (keysize + size_t.sizeof - 1) & ~(size_t.sizeof - 1); 685 686 void* pvalue = pkey + keysizet; 687 688 //doFormat(putc, (&keyti)[0..1], pkey); 689 m = getMan(keyti); 690 argptr = pkey; 691 692 ti = keyti; 693 formatArg('s'); 694 695 putc(':'); 696 //doFormat(putc, (&valti)[0..1], pvalue); 697 m = getMan(valti); 698 argptr = pvalue; 699 700 ti = valti; 701 formatArg('s'); 702 } 703 m = mSave; 704 ti = tiSave; 705 argptr = argptrSave; 706 putc(']'); 707 } 708 709 //printf("formatArg(fc = '%c', m = '%c')\n", fc, m); 710 int mi; 711 switch (m) 712 { 713 case Mangle.Tbool: 714 vbit = getArg!(bool)(); 715 if (fc != 's') 716 { vnumber = vbit; 717 goto Lnumber; 718 } 719 putstr(vbit ? "true" : "false"); 720 return; 721 722 case Mangle.Tchar: 723 vchar = getArg!(char)(); 724 if (fc != 's') 725 { vnumber = vchar; 726 goto Lnumber; 727 } 728 L2: 729 putstr((&vchar)[0 .. 1]); 730 return; 731 732 case Mangle.Twchar: 733 vdchar = getArg!(wchar)(); 734 goto L1; 735 736 case Mangle.Tdchar: 737 vdchar = getArg!(dchar)(); 738 L1: 739 if (fc != 's') 740 { vnumber = vdchar; 741 goto Lnumber; 742 } 743 if (vdchar <= 0x7F) 744 { vchar = cast(char)vdchar; 745 goto L2; 746 } 747 else 748 { if (!isValidDchar(vdchar)) 749 throw new UTFException("invalid dchar in format"); 750 char[4] vbuf; 751 putstr(vbuf[0 .. encode(vbuf, vdchar)]); 752 } 753 return; 754 755 case Mangle.Tbyte: 756 signed = 1; 757 vnumber = getArg!(byte)(); 758 goto Lnumber; 759 760 case Mangle.Tubyte: 761 vnumber = getArg!(ubyte)(); 762 goto Lnumber; 763 764 case Mangle.Tshort: 765 signed = 1; 766 vnumber = getArg!(short)(); 767 goto Lnumber; 768 769 case Mangle.Tushort: 770 vnumber = getArg!(ushort)(); 771 goto Lnumber; 772 773 case Mangle.Tint: 774 signed = 1; 775 vnumber = getArg!(int)(); 776 goto Lnumber; 777 778 case Mangle.Tuint: 779 Luint: 780 vnumber = getArg!(uint)(); 781 goto Lnumber; 782 783 case Mangle.Tlong: 784 signed = 1; 785 vnumber = cast(ulong)getArg!(long)(); 786 goto Lnumber; 787 788 case Mangle.Tulong: 789 Lulong: 790 vnumber = getArg!(ulong)(); 791 goto Lnumber; 792 793 case Mangle.Tclass: 794 vobject = getArg!(Object)(); 795 if (vobject is null) 796 s = "null"; 797 else 798 s = vobject.toString(); 799 goto Lputstr; 800 801 case Mangle.Tpointer: 802 vnumber = cast(ulong)getArg!(void*)(); 803 if (fc != 'x') uc = 1; 804 flags |= FL0pad; 805 if (!(flags & FLprecision)) 806 { flags |= FLprecision; 807 precision = (void*).sizeof; 808 } 809 base = 16; 810 goto Lnumber; 811 812 case Mangle.Tfloat: 813 if (fc == 'x' || fc == 'X') 814 goto Luint; 815 vreal = getArg!(float)(); 816 goto Lreal; 817 818 case Mangle.Tdouble: 819 if (fc == 'x' || fc == 'X') 820 goto Lulong; 821 vreal = getArg!(double)(); 822 goto Lreal; 823 824 case Mangle.Treal: 825 vreal = getArg!(real)(); 826 goto Lreal; 827 828 case Mangle.Tsarray: 829 putArray(argptr, (cast(TypeInfo_StaticArray)ti).len, (cast(TypeInfo_StaticArray)ti).next); 830 return; 831 832 case Mangle.Tarray: 833 mi = 10; 834 if (typeid(ti).name.length == 14 && 835 typeid(ti).name[9..14] == "Array") 836 { // array of non-primitive types 837 TypeInfo tn = (cast(TypeInfo_Array)ti).next; 838 tn = skipCI(tn); 839 switch (cast(Mangle)typeid(tn).name[9]) 840 { 841 case Mangle.Tchar: goto LarrayChar; 842 case Mangle.Twchar: goto LarrayWchar; 843 case Mangle.Tdchar: goto LarrayDchar; 844 default: 845 break; 846 } 847 void[] va = getArg!(void[])(); 848 putArray(va.ptr, va.length, tn); 849 return; 850 } 851 if (typeid(ti).name.length == 25 && 852 typeid(ti).name[9..25] == "AssociativeArray") 853 { // associative array 854 ubyte[long] vaa = getArg!(ubyte[long])(); 855 putAArray(vaa, 856 (cast(TypeInfo_AssociativeArray)ti).next, 857 (cast(TypeInfo_AssociativeArray)ti).key); 858 return; 859 } 860 861 while (1) 862 { 863 m2 = cast(Mangle)typeid(ti).name[mi]; 864 switch (m2) 865 { 866 case Mangle.Tchar: 867 LarrayChar: 868 s = getArg!(string)(); 869 goto Lputstr; 870 871 case Mangle.Twchar: 872 LarrayWchar: 873 wchar[] sw = getArg!(wchar[])(); 874 s = toUTF8(sw); 875 goto Lputstr; 876 877 case Mangle.Tdchar: 878 LarrayDchar: 879 s = toUTF8(getArg!(dstring)()); 880 Lputstr: 881 if (fc != 's') 882 throw new FormatException("string"); 883 if (flags & FLprecision && precision < s.length) 884 s = s[0 .. precision]; 885 putstr(s); 886 break; 887 888 case Mangle.Tconst: 889 case Mangle.Timmutable: 890 mi++; 891 continue; 892 893 default: 894 TypeInfo ti2 = primitiveTypeInfo(m2); 895 if (!ti2) 896 goto Lerror; 897 void[] va = getArg!(void[])(); 898 putArray(va.ptr, va.length, ti2); 899 } 900 return; 901 } 902 assert(0); 903 904 case Mangle.Tenum: 905 ti = (cast(TypeInfo_Enum)ti).base; 906 m = cast(Mangle)typeid(ti).name[9]; 907 formatArg(fc); 908 return; 909 910 case Mangle.Tstruct: 911 { TypeInfo_Struct tis = cast(TypeInfo_Struct)ti; 912 if (tis.xtoString is null) 913 throw new FormatException("Can't convert " ~ tis.toString() 914 ~ " to string: \"string toString()\" not defined"); 915 s = tis.xtoString(skipArg(tis)); 916 goto Lputstr; 917 } 918 919 default: 920 goto Lerror; 921 } 922 923 Lnumber: 924 switch (fc) 925 { 926 case 's': 927 case 'd': 928 if (signed) 929 { if (cast(long)vnumber < 0) 930 { prefix = "-"; 931 vnumber = -vnumber; 932 } 933 else if (flags & FLplus) 934 prefix = "+"; 935 else if (flags & FLspace) 936 prefix = " "; 937 } 938 break; 939 940 case 'b': 941 signed = 0; 942 base = 2; 943 break; 944 945 case 'o': 946 signed = 0; 947 base = 8; 948 break; 949 950 case 'X': 951 uc = 1; 952 if (flags & FLhash && vnumber) 953 prefix = "0X"; 954 signed = 0; 955 base = 16; 956 break; 957 958 case 'x': 959 if (flags & FLhash && vnumber) 960 prefix = "0x"; 961 signed = 0; 962 base = 16; 963 break; 964 965 default: 966 goto Lerror; 967 } 968 969 if (!signed) 970 { 971 switch (m) 972 { 973 case Mangle.Tbyte: 974 vnumber &= 0xFF; 975 break; 976 977 case Mangle.Tshort: 978 vnumber &= 0xFFFF; 979 break; 980 981 case Mangle.Tint: 982 vnumber &= 0xFFFFFFFF; 983 break; 984 985 default: 986 break; 987 } 988 } 989 990 if (flags & FLprecision && fc != 'p') 991 flags &= ~FL0pad; 992 993 if (vnumber < base) 994 { 995 if (vnumber == 0 && precision == 0 && flags & FLprecision && 996 !(fc == 'o' && flags & FLhash)) 997 { 998 putstr(null); 999 return; 1000 } 1001 if (precision == 0 || !(flags & FLprecision)) 1002 { vchar = cast(char)('0' + vnumber); 1003 if (vnumber < 10) 1004 vchar = cast(char)('0' + vnumber); 1005 else 1006 vchar = cast(char)((uc ? 'A' - 10 : 'a' - 10) + vnumber); 1007 goto L2; 1008 } 1009 } 1010 1011 { 1012 ptrdiff_t n = tmpbuf.length; 1013 char c; 1014 int hexoffset = uc ? ('A' - ('9' + 1)) : ('a' - ('9' + 1)); 1015 1016 while (vnumber) 1017 { 1018 c = cast(char)((vnumber % base) + '0'); 1019 if (c > '9') 1020 c += hexoffset; 1021 vnumber /= base; 1022 tmpbuf[--n] = c; 1023 } 1024 if (tmpbuf.length - n < precision && precision < tmpbuf.length) 1025 { 1026 ptrdiff_t m = tmpbuf.length - precision; 1027 tmpbuf[m .. n] = '0'; 1028 n = m; 1029 } 1030 else if (flags & FLhash && fc == 'o') 1031 prefix = "0"; 1032 putstr(tmpbuf[n .. tmpbuf.length]); 1033 return; 1034 } 1035 1036 Lreal: 1037 putreal(vreal); 1038 return; 1039 1040 Lerror: 1041 throw new FormatException("formatArg"); 1042 } 1043 1044 for (int j = 0; j < arguments.length; ) 1045 { 1046 ti = arguments[j++]; 1047 //printf("arg[%d]: '%.*s' %d\n", j, typeid(ti).name.length, typeid(ti).name.ptr, typeid(ti).name.length); 1048 //ti.print(); 1049 1050 flags = 0; 1051 precision = 0; 1052 field_width = 0; 1053 1054 ti = skipCI(ti); 1055 int mi = 9; 1056 do 1057 { 1058 if (typeid(ti).name.length <= mi) 1059 goto Lerror; 1060 m = cast(Mangle)typeid(ti).name[mi++]; 1061 } while (m == Mangle.Tconst || m == Mangle.Timmutable); 1062 1063 if (m == Mangle.Tarray) 1064 { 1065 if (typeid(ti).name.length == 14 && 1066 typeid(ti).name[9..14] == "Array") 1067 { 1068 TypeInfo tn = (cast(TypeInfo_Array)ti).next; 1069 tn = skipCI(tn); 1070 switch (cast(Mangle)typeid(tn).name[9]) 1071 { 1072 case Mangle.Tchar: 1073 case Mangle.Twchar: 1074 case Mangle.Tdchar: 1075 ti = tn; 1076 mi = 9; 1077 break; 1078 default: 1079 break; 1080 } 1081 } 1082 L1: 1083 Mangle m2 = cast(Mangle)typeid(ti).name[mi]; 1084 string fmt; // format string 1085 wstring wfmt; 1086 dstring dfmt; 1087 1088 /* For performance reasons, this code takes advantage of the 1089 * fact that most format strings will be ASCII, and that the 1090 * format specifiers are always ASCII. This means we only need 1091 * to deal with UTF in a couple of isolated spots. 1092 */ 1093 1094 switch (m2) 1095 { 1096 case Mangle.Tchar: 1097 fmt = getArg!(string)(); 1098 break; 1099 1100 case Mangle.Twchar: 1101 wfmt = getArg!(wstring)(); 1102 fmt = toUTF8(wfmt); 1103 break; 1104 1105 case Mangle.Tdchar: 1106 dfmt = getArg!(dstring)(); 1107 fmt = toUTF8(dfmt); 1108 break; 1109 1110 case Mangle.Tconst: 1111 case Mangle.Timmutable: 1112 mi++; 1113 goto L1; 1114 1115 default: 1116 formatArg('s'); 1117 continue; 1118 } 1119 1120 for (size_t i = 0; i < fmt.length; ) 1121 { dchar c = fmt[i++]; 1122 1123 dchar getFmtChar() 1124 { // Valid format specifier characters will never be UTF 1125 if (i == fmt.length) 1126 throw new FormatException("invalid specifier"); 1127 return fmt[i++]; 1128 } 1129 1130 int getFmtInt() 1131 { int n; 1132 1133 while (1) 1134 { 1135 n = n * 10 + (c - '0'); 1136 if (n < 0) // overflow 1137 throw new FormatException("int overflow"); 1138 c = getFmtChar(); 1139 if (c < '0' || c > '9') 1140 break; 1141 } 1142 return n; 1143 } 1144 1145 int getFmtStar() 1146 { Mangle m; 1147 TypeInfo ti; 1148 1149 if (j == arguments.length) 1150 throw new FormatException("too few arguments"); 1151 ti = arguments[j++]; 1152 m = cast(Mangle)typeid(ti).name[9]; 1153 if (m != Mangle.Tint) 1154 throw new FormatException("int argument expected"); 1155 return getArg!(int)(); 1156 } 1157 1158 if (c != '%') 1159 { 1160 if (c > 0x7F) // if UTF sequence 1161 { 1162 i--; // back up and decode UTF sequence 1163 import std.utf : decode; 1164 c = decode(fmt, i); 1165 } 1166 Lputc: 1167 putc(c); 1168 continue; 1169 } 1170 1171 // Get flags {-+ #} 1172 flags = 0; 1173 while (1) 1174 { 1175 c = getFmtChar(); 1176 switch (c) 1177 { 1178 case '-': flags |= FLdash; continue; 1179 case '+': flags |= FLplus; continue; 1180 case ' ': flags |= FLspace; continue; 1181 case '#': flags |= FLhash; continue; 1182 case '0': flags |= FL0pad; continue; 1183 1184 case '%': if (flags == 0) 1185 goto Lputc; 1186 break; 1187 1188 default: break; 1189 } 1190 break; 1191 } 1192 1193 // Get field width 1194 field_width = 0; 1195 if (c == '*') 1196 { 1197 field_width = getFmtStar(); 1198 if (field_width < 0) 1199 { flags |= FLdash; 1200 field_width = -field_width; 1201 } 1202 1203 c = getFmtChar(); 1204 } 1205 else if (c >= '0' && c <= '9') 1206 field_width = getFmtInt(); 1207 1208 if (flags & FLplus) 1209 flags &= ~FLspace; 1210 if (flags & FLdash) 1211 flags &= ~FL0pad; 1212 1213 // Get precision 1214 precision = 0; 1215 if (c == '.') 1216 { flags |= FLprecision; 1217 //flags &= ~FL0pad; 1218 1219 c = getFmtChar(); 1220 if (c == '*') 1221 { 1222 precision = getFmtStar(); 1223 if (precision < 0) 1224 { precision = 0; 1225 flags &= ~FLprecision; 1226 } 1227 1228 c = getFmtChar(); 1229 } 1230 else if (c >= '0' && c <= '9') 1231 precision = getFmtInt(); 1232 } 1233 1234 if (j == arguments.length) 1235 goto Lerror; 1236 ti = arguments[j++]; 1237 ti = skipCI(ti); 1238 mi = 9; 1239 do 1240 { 1241 m = cast(Mangle)typeid(ti).name[mi++]; 1242 } while (m == Mangle.Tconst || m == Mangle.Timmutable); 1243 1244 if (c > 0x7F) // if UTF sequence 1245 goto Lerror; // format specifiers can't be UTF 1246 formatArg(cast(char)c); 1247 } 1248 } 1249 else 1250 { 1251 formatArg('s'); 1252 } 1253 } 1254 return; 1255 1256 Lerror: 1257 throw new FormatException(); 1258 } 1259 1260 1261 private bool needToSwapEndianess(Char)(ref FormatSpec!Char f) 1262 { 1263 import std.system : endian, Endian; 1264 1265 return endian == Endian.littleEndian && f.flPlus 1266 || endian == Endian.bigEndian && f.flDash; 1267 } 1268 1269 unittest 1270 { 1271 string res; 1272 void putc(dchar c) 1273 { 1274 res ~= c; 1275 } 1276 1277 void myPrint(...) 1278 { 1279 undead.doformat.doFormat(&putc, _arguments, _argptr); 1280 } 1281 1282 myPrint("The answer is %s:", 27, 6); 1283 assert(res == "The answer is 27:6"); 1284 }