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 version (GNU) 394 { 395 static assert(false, "GNU D compiler does not support doFormat"); 396 } 397 398 import std.utf : encode, toUCSindex, isValidDchar, UTFException, toUTF8; 399 import core.stdc.string : strlen; 400 import core.stdc.stdlib : alloca, malloc, realloc, free; 401 import core.stdc.stdio : snprintf; 402 403 size_t bufLength = 1024; 404 void* argBuffer = malloc(bufLength); 405 scope(exit) free(argBuffer); 406 407 size_t bufUsed = 0; 408 foreach (ti; arguments) 409 { 410 // Ensure the required alignment 411 bufUsed += ti.talign - 1; 412 bufUsed -= (cast(size_t)argBuffer + bufUsed) & (ti.talign - 1); 413 auto pos = bufUsed; 414 // Align to next word boundary 415 bufUsed += ti.tsize + size_t.sizeof - 1; 416 bufUsed -= (cast(size_t)argBuffer + bufUsed) & (size_t.sizeof - 1); 417 // Resize buffer if necessary 418 while (bufUsed > bufLength) 419 { 420 bufLength *= 2; 421 argBuffer = realloc(argBuffer, bufLength); 422 } 423 // Copy argument into buffer 424 va_arg(ap, ti, argBuffer + pos); 425 } 426 427 auto argptr = argBuffer; 428 void* skipArg(TypeInfo ti) 429 { 430 // Ensure the required alignment 431 argptr += ti.talign - 1; 432 argptr -= cast(size_t)argptr & (ti.talign - 1); 433 auto p = argptr; 434 // Align to next word boundary 435 argptr += ti.tsize + size_t.sizeof - 1; 436 argptr -= cast(size_t)argptr & (size_t.sizeof - 1); 437 return p; 438 } 439 auto getArg(T)() 440 { 441 return *cast(T*)skipArg(typeid(T)); 442 } 443 444 TypeInfo ti; 445 Mangle m; 446 uint flags; 447 int field_width; 448 int precision; 449 450 enum : uint 451 { 452 FLdash = 1, 453 FLplus = 2, 454 FLspace = 4, 455 FLhash = 8, 456 FLlngdbl = 0x20, 457 FL0pad = 0x40, 458 FLprecision = 0x80, 459 } 460 461 static TypeInfo skipCI(TypeInfo valti) 462 { 463 for (;;) 464 { 465 if (typeid(valti).name.length == 18 && 466 typeid(valti).name[9..18] == "Invariant") 467 valti = (cast(TypeInfo_Invariant)valti).base; 468 else if (typeid(valti).name.length == 14 && 469 typeid(valti).name[9..14] == "Const") 470 valti = (cast(TypeInfo_Const)valti).base; 471 else 472 break; 473 } 474 475 return valti; 476 } 477 478 void formatArg(char fc) 479 { 480 bool vbit; 481 ulong vnumber; 482 char vchar; 483 dchar vdchar; 484 Object vobject; 485 real vreal; 486 Mangle m2; 487 int signed = 0; 488 uint base = 10; 489 int uc; 490 char[ulong.sizeof * 8] tmpbuf; // long enough to print long in binary 491 const(char)* prefix = ""; 492 string s; 493 494 void putstr(const char[] s) 495 { 496 //printf("putstr: s = %.*s, flags = x%x\n", s.length, s.ptr, flags); 497 ptrdiff_t padding = field_width - 498 (strlen(prefix) + toUCSindex(s, s.length)); 499 ptrdiff_t prepad = 0; 500 ptrdiff_t postpad = 0; 501 if (padding > 0) 502 { 503 if (flags & FLdash) 504 postpad = padding; 505 else 506 prepad = padding; 507 } 508 509 if (flags & FL0pad) 510 { 511 while (*prefix) 512 putc(*prefix++); 513 while (prepad--) 514 putc('0'); 515 } 516 else 517 { 518 while (prepad--) 519 putc(' '); 520 while (*prefix) 521 putc(*prefix++); 522 } 523 524 foreach (dchar c; s) 525 putc(c); 526 527 while (postpad--) 528 putc(' '); 529 } 530 531 void putreal(real v) 532 { 533 //printf("putreal %Lg\n", vreal); 534 535 switch (fc) 536 { 537 case 's': 538 fc = 'g'; 539 break; 540 541 case 'f', 'F', 'e', 'E', 'g', 'G', 'a', 'A': 542 break; 543 544 default: 545 //printf("fc = '%c'\n", fc); 546 Lerror: 547 throw new FormatException("incompatible format character for floating point type"); 548 } 549 version (DigitalMarsC) 550 { 551 uint sl; 552 char[] fbuf = tmpbuf; 553 if (!(flags & FLprecision)) 554 precision = 6; 555 while (1) 556 { 557 sl = fbuf.length; 558 prefix = (*__pfloatfmt)(fc, flags | FLlngdbl, 559 precision, &v, cast(char*)fbuf, &sl, field_width); 560 if (sl != -1) 561 break; 562 sl = fbuf.length * 2; 563 fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl]; 564 } 565 putstr(fbuf[0 .. sl]); 566 } 567 else 568 { 569 ptrdiff_t sl; 570 char[] fbuf = tmpbuf; 571 char[12] format; 572 format[0] = '%'; 573 int i = 1; 574 if (flags & FLdash) 575 format[i++] = '-'; 576 if (flags & FLplus) 577 format[i++] = '+'; 578 if (flags & FLspace) 579 format[i++] = ' '; 580 if (flags & FLhash) 581 format[i++] = '#'; 582 if (flags & FL0pad) 583 format[i++] = '0'; 584 format[i + 0] = '*'; 585 format[i + 1] = '.'; 586 format[i + 2] = '*'; 587 format[i + 3] = 'L'; 588 format[i + 4] = fc; 589 format[i + 5] = 0; 590 if (!(flags & FLprecision)) 591 precision = -1; 592 while (1) 593 { 594 sl = fbuf.length; 595 int n; 596 version (CRuntime_Microsoft) 597 { 598 import std.math : isNaN, isInfinity; 599 if (isNaN(v)) // snprintf writes 1.#QNAN 600 n = snprintf(fbuf.ptr, sl, "nan"); 601 else if (isInfinity(v)) // snprintf writes 1.#INF 602 n = snprintf(fbuf.ptr, sl, v < 0 ? "-inf" : "inf"); 603 else 604 n = snprintf(fbuf.ptr, sl, format.ptr, field_width, 605 precision, cast(double)v); 606 } 607 else 608 n = snprintf(fbuf.ptr, sl, format.ptr, field_width, 609 precision, v); 610 //printf("format = '%s', n = %d\n", cast(char*)format, n); 611 if (n >= 0 && n < sl) 612 { sl = n; 613 break; 614 } 615 if (n < 0) 616 sl = sl * 2; 617 else 618 sl = n + 1; 619 fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl]; 620 } 621 putstr(fbuf[0 .. sl]); 622 } 623 return; 624 } 625 626 static Mangle getMan(TypeInfo ti) 627 { 628 auto m = cast(Mangle)typeid(ti).name[9]; 629 if (typeid(ti).name.length == 20 && 630 typeid(ti).name[9..20] == "StaticArray") 631 m = cast(Mangle)'G'; 632 return m; 633 } 634 635 /* p = pointer to the first element in the array 636 * len = number of elements in the array 637 * valti = type of the elements 638 */ 639 void putArray(void* p, size_t len, TypeInfo valti) 640 { 641 //printf("\nputArray(len = %u), tsize = %u\n", len, valti.tsize); 642 putc('['); 643 valti = skipCI(valti); 644 size_t tsize = valti.tsize; 645 auto argptrSave = argptr; 646 auto tiSave = ti; 647 auto mSave = m; 648 ti = valti; 649 //printf("\n%.*s\n", typeid(valti).name.length, typeid(valti).name.ptr); 650 m = getMan(valti); 651 while (len--) 652 { 653 //doFormat(putc, (&valti)[0 .. 1], p); 654 argptr = p; 655 formatArg('s'); 656 p += tsize; 657 if (len > 0) putc(','); 658 } 659 m = mSave; 660 ti = tiSave; 661 argptr = argptrSave; 662 putc(']'); 663 } 664 665 void putAArray(ubyte[long] vaa, TypeInfo valti, TypeInfo keyti) 666 { 667 putc('['); 668 bool comma=false; 669 auto argptrSave = argptr; 670 auto tiSave = ti; 671 auto mSave = m; 672 valti = skipCI(valti); 673 keyti = skipCI(keyti); 674 foreach (ref fakevalue; vaa) 675 { 676 if (comma) putc(','); 677 comma = true; 678 void *pkey = &fakevalue; 679 version (D_LP64) 680 pkey -= (long.sizeof + 15) & ~(15); 681 else 682 pkey -= (long.sizeof + size_t.sizeof - 1) & ~(size_t.sizeof - 1); 683 684 // the key comes before the value 685 auto keysize = keyti.tsize; 686 version (D_LP64) 687 auto keysizet = (keysize + 15) & ~(15); 688 else 689 auto keysizet = (keysize + size_t.sizeof - 1) & ~(size_t.sizeof - 1); 690 691 void* pvalue = pkey + keysizet; 692 693 //doFormat(putc, (&keyti)[0..1], pkey); 694 m = getMan(keyti); 695 argptr = pkey; 696 697 ti = keyti; 698 formatArg('s'); 699 700 putc(':'); 701 //doFormat(putc, (&valti)[0..1], pvalue); 702 m = getMan(valti); 703 argptr = pvalue; 704 705 ti = valti; 706 formatArg('s'); 707 } 708 m = mSave; 709 ti = tiSave; 710 argptr = argptrSave; 711 putc(']'); 712 } 713 714 //printf("formatArg(fc = '%c', m = '%c')\n", fc, m); 715 int mi; 716 switch (m) 717 { 718 case Mangle.Tbool: 719 vbit = getArg!(bool)(); 720 if (fc != 's') 721 { vnumber = vbit; 722 goto Lnumber; 723 } 724 putstr(vbit ? "true" : "false"); 725 return; 726 727 case Mangle.Tchar: 728 vchar = getArg!(char)(); 729 if (fc != 's') 730 { vnumber = vchar; 731 goto Lnumber; 732 } 733 L2: 734 putstr((&vchar)[0 .. 1]); 735 return; 736 737 case Mangle.Twchar: 738 vdchar = getArg!(wchar)(); 739 goto L1; 740 741 case Mangle.Tdchar: 742 vdchar = getArg!(dchar)(); 743 L1: 744 if (fc != 's') 745 { vnumber = vdchar; 746 goto Lnumber; 747 } 748 if (vdchar <= 0x7F) 749 { vchar = cast(char)vdchar; 750 goto L2; 751 } 752 else 753 { if (!isValidDchar(vdchar)) 754 throw new UTFException("invalid dchar in format"); 755 char[4] vbuf; 756 putstr(vbuf[0 .. encode(vbuf, vdchar)]); 757 } 758 return; 759 760 case Mangle.Tbyte: 761 signed = 1; 762 vnumber = getArg!(byte)(); 763 goto Lnumber; 764 765 case Mangle.Tubyte: 766 vnumber = getArg!(ubyte)(); 767 goto Lnumber; 768 769 case Mangle.Tshort: 770 signed = 1; 771 vnumber = getArg!(short)(); 772 goto Lnumber; 773 774 case Mangle.Tushort: 775 vnumber = getArg!(ushort)(); 776 goto Lnumber; 777 778 case Mangle.Tint: 779 signed = 1; 780 vnumber = getArg!(int)(); 781 goto Lnumber; 782 783 case Mangle.Tuint: 784 Luint: 785 vnumber = getArg!(uint)(); 786 goto Lnumber; 787 788 case Mangle.Tlong: 789 signed = 1; 790 vnumber = cast(ulong)getArg!(long)(); 791 goto Lnumber; 792 793 case Mangle.Tulong: 794 Lulong: 795 vnumber = getArg!(ulong)(); 796 goto Lnumber; 797 798 case Mangle.Tclass: 799 vobject = getArg!(Object)(); 800 if (vobject is null) 801 s = "null"; 802 else 803 s = vobject.toString(); 804 goto Lputstr; 805 806 case Mangle.Tpointer: 807 vnumber = cast(ulong)getArg!(void*)(); 808 if (fc != 'x') uc = 1; 809 flags |= FL0pad; 810 if (!(flags & FLprecision)) 811 { flags |= FLprecision; 812 precision = (void*).sizeof; 813 } 814 base = 16; 815 goto Lnumber; 816 817 case Mangle.Tfloat: 818 if (fc == 'x' || fc == 'X') 819 goto Luint; 820 vreal = getArg!(float)(); 821 goto Lreal; 822 823 case Mangle.Tdouble: 824 if (fc == 'x' || fc == 'X') 825 goto Lulong; 826 vreal = getArg!(double)(); 827 goto Lreal; 828 829 case Mangle.Treal: 830 vreal = getArg!(real)(); 831 goto Lreal; 832 833 case Mangle.Tsarray: 834 putArray(argptr, (cast(TypeInfo_StaticArray)ti).len, (cast(TypeInfo_StaticArray)ti).next); 835 return; 836 837 case Mangle.Tarray: 838 mi = 10; 839 if (typeid(ti).name.length == 14 && 840 typeid(ti).name[9..14] == "Array") 841 { // array of non-primitive types 842 TypeInfo tn = (cast(TypeInfo_Array)ti).next; 843 tn = skipCI(tn); 844 switch (cast(Mangle)typeid(tn).name[9]) 845 { 846 case Mangle.Tchar: goto LarrayChar; 847 case Mangle.Twchar: goto LarrayWchar; 848 case Mangle.Tdchar: goto LarrayDchar; 849 default: 850 break; 851 } 852 void[] va = getArg!(void[])(); 853 putArray(va.ptr, va.length, tn); 854 return; 855 } 856 if (typeid(ti).name.length == 25 && 857 typeid(ti).name[9..25] == "AssociativeArray") 858 { // associative array 859 ubyte[long] vaa = getArg!(ubyte[long])(); 860 putAArray(vaa, 861 (cast(TypeInfo_AssociativeArray)ti).next, 862 (cast(TypeInfo_AssociativeArray)ti).key); 863 return; 864 } 865 866 while (1) 867 { 868 m2 = cast(Mangle)typeid(ti).name[mi]; 869 switch (m2) 870 { 871 case Mangle.Tchar: 872 LarrayChar: 873 s = getArg!(string)(); 874 goto Lputstr; 875 876 case Mangle.Twchar: 877 LarrayWchar: 878 wchar[] sw = getArg!(wchar[])(); 879 s = toUTF8(sw); 880 goto Lputstr; 881 882 case Mangle.Tdchar: 883 LarrayDchar: 884 s = toUTF8(getArg!(dstring)()); 885 Lputstr: 886 if (fc != 's') 887 throw new FormatException("string"); 888 if (flags & FLprecision && precision < s.length) 889 s = s[0 .. precision]; 890 putstr(s); 891 break; 892 893 case Mangle.Tconst: 894 case Mangle.Timmutable: 895 mi++; 896 continue; 897 898 default: 899 TypeInfo ti2 = primitiveTypeInfo(m2); 900 if (!ti2) 901 goto Lerror; 902 void[] va = getArg!(void[])(); 903 putArray(va.ptr, va.length, ti2); 904 } 905 return; 906 } 907 assert(0); 908 909 case Mangle.Tenum: 910 ti = (cast(TypeInfo_Enum)ti).base; 911 m = cast(Mangle)typeid(ti).name[9]; 912 formatArg(fc); 913 return; 914 915 case Mangle.Tstruct: 916 { TypeInfo_Struct tis = cast(TypeInfo_Struct)ti; 917 if (tis.xtoString is null) 918 throw new FormatException("Can't convert " ~ tis.toString() 919 ~ " to string: \"string toString()\" not defined"); 920 s = tis.xtoString(skipArg(tis)); 921 goto Lputstr; 922 } 923 924 default: 925 goto Lerror; 926 } 927 928 Lnumber: 929 switch (fc) 930 { 931 case 's': 932 case 'd': 933 if (signed) 934 { if (cast(long)vnumber < 0) 935 { prefix = "-"; 936 vnumber = -vnumber; 937 } 938 else if (flags & FLplus) 939 prefix = "+"; 940 else if (flags & FLspace) 941 prefix = " "; 942 } 943 break; 944 945 case 'b': 946 signed = 0; 947 base = 2; 948 break; 949 950 case 'o': 951 signed = 0; 952 base = 8; 953 break; 954 955 case 'X': 956 uc = 1; 957 if (flags & FLhash && vnumber) 958 prefix = "0X"; 959 signed = 0; 960 base = 16; 961 break; 962 963 case 'x': 964 if (flags & FLhash && vnumber) 965 prefix = "0x"; 966 signed = 0; 967 base = 16; 968 break; 969 970 default: 971 goto Lerror; 972 } 973 974 if (!signed) 975 { 976 switch (m) 977 { 978 case Mangle.Tbyte: 979 vnumber &= 0xFF; 980 break; 981 982 case Mangle.Tshort: 983 vnumber &= 0xFFFF; 984 break; 985 986 case Mangle.Tint: 987 vnumber &= 0xFFFFFFFF; 988 break; 989 990 default: 991 break; 992 } 993 } 994 995 if (flags & FLprecision && fc != 'p') 996 flags &= ~FL0pad; 997 998 if (vnumber < base) 999 { 1000 if (vnumber == 0 && precision == 0 && flags & FLprecision && 1001 !(fc == 'o' && flags & FLhash)) 1002 { 1003 putstr(null); 1004 return; 1005 } 1006 if (precision == 0 || !(flags & FLprecision)) 1007 { vchar = cast(char)('0' + vnumber); 1008 if (vnumber < 10) 1009 vchar = cast(char)('0' + vnumber); 1010 else 1011 vchar = cast(char)((uc ? 'A' - 10 : 'a' - 10) + vnumber); 1012 goto L2; 1013 } 1014 } 1015 1016 { 1017 ptrdiff_t n = tmpbuf.length; 1018 char c; 1019 int hexoffset = uc ? ('A' - ('9' + 1)) : ('a' - ('9' + 1)); 1020 1021 while (vnumber) 1022 { 1023 c = cast(char)((vnumber % base) + '0'); 1024 if (c > '9') 1025 c += hexoffset; 1026 vnumber /= base; 1027 tmpbuf[--n] = c; 1028 } 1029 if (tmpbuf.length - n < precision && precision < tmpbuf.length) 1030 { 1031 ptrdiff_t m = tmpbuf.length - precision; 1032 tmpbuf[m .. n] = '0'; 1033 n = m; 1034 } 1035 else if (flags & FLhash && fc == 'o') 1036 prefix = "0"; 1037 putstr(tmpbuf[n .. tmpbuf.length]); 1038 return; 1039 } 1040 1041 Lreal: 1042 putreal(vreal); 1043 return; 1044 1045 Lerror: 1046 throw new FormatException("formatArg"); 1047 } 1048 1049 for (int j = 0; j < arguments.length; ) 1050 { 1051 ti = arguments[j++]; 1052 //printf("arg[%d]: '%.*s' %d\n", j, typeid(ti).name.length, typeid(ti).name.ptr, typeid(ti).name.length); 1053 //ti.print(); 1054 1055 flags = 0; 1056 precision = 0; 1057 field_width = 0; 1058 1059 ti = skipCI(ti); 1060 int mi = 9; 1061 do 1062 { 1063 if (typeid(ti).name.length <= mi) 1064 goto Lerror; 1065 m = cast(Mangle)typeid(ti).name[mi++]; 1066 } while (m == Mangle.Tconst || m == Mangle.Timmutable); 1067 1068 if (m == Mangle.Tarray) 1069 { 1070 if (typeid(ti).name.length == 14 && 1071 typeid(ti).name[9..14] == "Array") 1072 { 1073 TypeInfo tn = (cast(TypeInfo_Array)ti).next; 1074 tn = skipCI(tn); 1075 switch (cast(Mangle)typeid(tn).name[9]) 1076 { 1077 case Mangle.Tchar: 1078 case Mangle.Twchar: 1079 case Mangle.Tdchar: 1080 ti = tn; 1081 mi = 9; 1082 break; 1083 default: 1084 break; 1085 } 1086 } 1087 L1: 1088 Mangle m2 = cast(Mangle)typeid(ti).name[mi]; 1089 string fmt; // format string 1090 wstring wfmt; 1091 dstring dfmt; 1092 1093 /* For performance reasons, this code takes advantage of the 1094 * fact that most format strings will be ASCII, and that the 1095 * format specifiers are always ASCII. This means we only need 1096 * to deal with UTF in a couple of isolated spots. 1097 */ 1098 1099 switch (m2) 1100 { 1101 case Mangle.Tchar: 1102 fmt = getArg!(string)(); 1103 break; 1104 1105 case Mangle.Twchar: 1106 wfmt = getArg!(wstring)(); 1107 fmt = toUTF8(wfmt); 1108 break; 1109 1110 case Mangle.Tdchar: 1111 dfmt = getArg!(dstring)(); 1112 fmt = toUTF8(dfmt); 1113 break; 1114 1115 case Mangle.Tconst: 1116 case Mangle.Timmutable: 1117 mi++; 1118 goto L1; 1119 1120 default: 1121 formatArg('s'); 1122 continue; 1123 } 1124 1125 for (size_t i = 0; i < fmt.length; ) 1126 { dchar c = fmt[i++]; 1127 1128 dchar getFmtChar() 1129 { // Valid format specifier characters will never be UTF 1130 if (i == fmt.length) 1131 throw new FormatException("invalid specifier"); 1132 return fmt[i++]; 1133 } 1134 1135 int getFmtInt() 1136 { int n; 1137 1138 while (1) 1139 { 1140 n = n * 10 + (c - '0'); 1141 if (n < 0) // overflow 1142 throw new FormatException("int overflow"); 1143 c = getFmtChar(); 1144 if (c < '0' || c > '9') 1145 break; 1146 } 1147 return n; 1148 } 1149 1150 int getFmtStar() 1151 { Mangle m; 1152 TypeInfo ti; 1153 1154 if (j == arguments.length) 1155 throw new FormatException("too few arguments"); 1156 ti = arguments[j++]; 1157 m = cast(Mangle)typeid(ti).name[9]; 1158 if (m != Mangle.Tint) 1159 throw new FormatException("int argument expected"); 1160 return getArg!(int)(); 1161 } 1162 1163 if (c != '%') 1164 { 1165 if (c > 0x7F) // if UTF sequence 1166 { 1167 i--; // back up and decode UTF sequence 1168 import std.utf : decode; 1169 c = decode(fmt, i); 1170 } 1171 Lputc: 1172 putc(c); 1173 continue; 1174 } 1175 1176 // Get flags {-+ #} 1177 flags = 0; 1178 while (1) 1179 { 1180 c = getFmtChar(); 1181 switch (c) 1182 { 1183 case '-': flags |= FLdash; continue; 1184 case '+': flags |= FLplus; continue; 1185 case ' ': flags |= FLspace; continue; 1186 case '#': flags |= FLhash; continue; 1187 case '0': flags |= FL0pad; continue; 1188 1189 case '%': if (flags == 0) 1190 goto Lputc; 1191 break; 1192 1193 default: break; 1194 } 1195 break; 1196 } 1197 1198 // Get field width 1199 field_width = 0; 1200 if (c == '*') 1201 { 1202 field_width = getFmtStar(); 1203 if (field_width < 0) 1204 { flags |= FLdash; 1205 field_width = -field_width; 1206 } 1207 1208 c = getFmtChar(); 1209 } 1210 else if (c >= '0' && c <= '9') 1211 field_width = getFmtInt(); 1212 1213 if (flags & FLplus) 1214 flags &= ~FLspace; 1215 if (flags & FLdash) 1216 flags &= ~FL0pad; 1217 1218 // Get precision 1219 precision = 0; 1220 if (c == '.') 1221 { flags |= FLprecision; 1222 //flags &= ~FL0pad; 1223 1224 c = getFmtChar(); 1225 if (c == '*') 1226 { 1227 precision = getFmtStar(); 1228 if (precision < 0) 1229 { precision = 0; 1230 flags &= ~FLprecision; 1231 } 1232 1233 c = getFmtChar(); 1234 } 1235 else if (c >= '0' && c <= '9') 1236 precision = getFmtInt(); 1237 } 1238 1239 if (j == arguments.length) 1240 goto Lerror; 1241 ti = arguments[j++]; 1242 ti = skipCI(ti); 1243 mi = 9; 1244 do 1245 { 1246 m = cast(Mangle)typeid(ti).name[mi++]; 1247 } while (m == Mangle.Tconst || m == Mangle.Timmutable); 1248 1249 if (c > 0x7F) // if UTF sequence 1250 goto Lerror; // format specifiers can't be UTF 1251 formatArg(cast(char)c); 1252 } 1253 } 1254 else 1255 { 1256 formatArg('s'); 1257 } 1258 } 1259 return; 1260 1261 Lerror: 1262 throw new FormatException(); 1263 } 1264 1265 1266 private bool needToSwapEndianess(Char)(ref FormatSpec!Char f) 1267 { 1268 import std.system : endian, Endian; 1269 1270 return endian == Endian.littleEndian && f.flPlus 1271 || endian == Endian.bigEndian && f.flDash; 1272 } 1273 1274 version(GNU) {} else 1275 unittest 1276 { 1277 string res; 1278 void putc(dchar c) 1279 { 1280 res ~= c; 1281 } 1282 1283 void myPrint(...) 1284 { 1285 undead.doformat.doFormat(&putc, _arguments, _argptr); 1286 } 1287 1288 myPrint("The answer is %s:", 27, 6); 1289 assert(res == "The answer is 27:6"); 1290 }