aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/undead/doformat.d
diff options
context:
space:
mode:
authorRalph Amissah <ralph@amissah.com>2016-08-22 10:31:34 -0400
committerRalph Amissah <ralph@amissah.com>2019-04-04 14:53:41 -0400
commit39b78293cbab8ce9df020a6754d3aae6624dab71 (patch)
tree319fb68bbea334377dde13644247153ff20315d7 /src/undead/doformat.d
parentstep 0.6.3 includes fixes (diff)
sdlang uses lexer.d which uses stream.d which phobos is to drop 2016-10
* stream & its depends: src/undead/stream.d src/undead/internal/file.d src/undead/doformat.d * https://github.com/DigitalMars/undeaD/blob/master/src/undead/
Diffstat (limited to 'src/undead/doformat.d')
-rw-r--r--src/undead/doformat.d1620
1 files changed, 1620 insertions, 0 deletions
diff --git a/src/undead/doformat.d b/src/undead/doformat.d
new file mode 100644
index 0000000..4fc0daf
--- /dev/null
+++ b/src/undead/doformat.d
@@ -0,0 +1,1620 @@
+// Written in the D programming language.
+
+/**
+ Copyright: Copyright Digital Mars 2000-2013.
+
+ License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+ Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com,
+ Andrei Alexandrescu), and Kenji Hara
+
+ Source: $(PHOBOSSRC std/_format.d)
+ */
+module undead.doformat;
+
+//debug=format; // uncomment to turn on debugging printf's
+
+import core.vararg;
+import std.exception;
+import std.meta;
+import std.range.primitives;
+import std.traits;
+import std.format;
+
+version(CRuntime_DigitalMars)
+{
+ version = DigitalMarsC;
+}
+
+version (DigitalMarsC)
+{
+ // This is DMC's internal floating point formatting function
+ extern (C)
+ {
+ extern shared char* function(int c, int flags, int precision,
+ in real* pdval,
+ char* buf, size_t* psl, int width) __pfloatfmt;
+ }
+}
+
+/**********************************************************************
+ * Signals a mismatch between a format and its corresponding argument.
+ */
+class FormatException : Exception
+{
+ @safe pure nothrow
+ this()
+ {
+ super("format error");
+ }
+
+ @safe pure nothrow
+ this(string msg, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null)
+ {
+ super(msg, fn, ln, next);
+ }
+}
+
+
+// Legacy implementation
+
+enum Mangle : char
+{
+ Tvoid = 'v',
+ Tbool = 'b',
+ Tbyte = 'g',
+ Tubyte = 'h',
+ Tshort = 's',
+ Tushort = 't',
+ Tint = 'i',
+ Tuint = 'k',
+ Tlong = 'l',
+ Tulong = 'm',
+ Tfloat = 'f',
+ Tdouble = 'd',
+ Treal = 'e',
+
+ Tifloat = 'o',
+ Tidouble = 'p',
+ Tireal = 'j',
+ Tcfloat = 'q',
+ Tcdouble = 'r',
+ Tcreal = 'c',
+
+ Tchar = 'a',
+ Twchar = 'u',
+ Tdchar = 'w',
+
+ Tarray = 'A',
+ Tsarray = 'G',
+ Taarray = 'H',
+ Tpointer = 'P',
+ Tfunction = 'F',
+ Tident = 'I',
+ Tclass = 'C',
+ Tstruct = 'S',
+ Tenum = 'E',
+ Ttypedef = 'T',
+ Tdelegate = 'D',
+
+ Tconst = 'x',
+ Timmutable = 'y',
+}
+
+// return the TypeInfo for a primitive type and null otherwise. This
+// is required since for arrays of ints we only have the mangled char
+// to work from. If arrays always subclassed TypeInfo_Array this
+// routine could go away.
+private TypeInfo primitiveTypeInfo(Mangle m)
+{
+ // BUG: should fix this in static this() to avoid double checked locking bug
+ __gshared TypeInfo[Mangle] dic;
+ if (!dic.length)
+ {
+ dic = [
+ Mangle.Tvoid : typeid(void),
+ Mangle.Tbool : typeid(bool),
+ Mangle.Tbyte : typeid(byte),
+ Mangle.Tubyte : typeid(ubyte),
+ Mangle.Tshort : typeid(short),
+ Mangle.Tushort : typeid(ushort),
+ Mangle.Tint : typeid(int),
+ Mangle.Tuint : typeid(uint),
+ Mangle.Tlong : typeid(long),
+ Mangle.Tulong : typeid(ulong),
+ Mangle.Tfloat : typeid(float),
+ Mangle.Tdouble : typeid(double),
+ Mangle.Treal : typeid(real),
+ Mangle.Tifloat : typeid(ifloat),
+ Mangle.Tidouble : typeid(idouble),
+ Mangle.Tireal : typeid(ireal),
+ Mangle.Tcfloat : typeid(cfloat),
+ Mangle.Tcdouble : typeid(cdouble),
+ Mangle.Tcreal : typeid(creal),
+ Mangle.Tchar : typeid(char),
+ Mangle.Twchar : typeid(wchar),
+ Mangle.Tdchar : typeid(dchar)
+ ];
+ }
+ auto p = m in dic;
+ return p ? *p : null;
+}
+
+// This stuff has been removed from the docs and is planned for deprecation.
+/*
+ * Interprets variadic argument list pointed to by argptr whose types
+ * are given by arguments[], formats them according to embedded format
+ * strings in the variadic argument list, and sends the resulting
+ * characters to putc.
+ *
+ * The variadic arguments are consumed in order. Each is formatted
+ * into a sequence of chars, using the default format specification
+ * for its type, and the characters are sequentially passed to putc.
+ * If a $(D char[]), $(D wchar[]), or $(D dchar[]) argument is
+ * encountered, it is interpreted as a format string. As many
+ * arguments as specified in the format string are consumed and
+ * formatted according to the format specifications in that string and
+ * passed to putc. If there are too few remaining arguments, a
+ * $(D FormatException) is thrown. If there are more remaining arguments than
+ * needed by the format specification, the default processing of
+ * arguments resumes until they are all consumed.
+ *
+ * Params:
+ * putc = Output is sent do this delegate, character by character.
+ * arguments = Array of $(D TypeInfo)s, one for each argument to be formatted.
+ * argptr = Points to variadic argument list.
+ *
+ * Throws:
+ * Mismatched arguments and formats result in a $(D FormatException) being thrown.
+ *
+ * Format_String:
+ * <a name="format-string">$(I Format strings)</a>
+ * consist of characters interspersed with
+ * $(I format specifications). Characters are simply copied
+ * to the output (such as putc) after any necessary conversion
+ * to the corresponding UTF-8 sequence.
+ *
+ * A $(I format specification) starts with a '%' character,
+ * and has the following grammar:
+
+$(CONSOLE
+$(I FormatSpecification):
+ $(B '%%')
+ $(B '%') $(I Flags) $(I Width) $(I Precision) $(I FormatChar)
+
+$(I Flags):
+ $(I empty)
+ $(B '-') $(I Flags)
+ $(B '+') $(I Flags)
+ $(B '#') $(I Flags)
+ $(B '0') $(I Flags)
+ $(B ' ') $(I Flags)
+
+$(I Width):
+ $(I empty)
+ $(I Integer)
+ $(B '*')
+
+$(I Precision):
+ $(I empty)
+ $(B '.')
+ $(B '.') $(I Integer)
+ $(B '.*')
+
+$(I Integer):
+ $(I Digit)
+ $(I Digit) $(I Integer)
+
+$(I Digit):
+ $(B '0')
+ $(B '1')
+ $(B '2')
+ $(B '3')
+ $(B '4')
+ $(B '5')
+ $(B '6')
+ $(B '7')
+ $(B '8')
+ $(B '9')
+
+$(I FormatChar):
+ $(B 's')
+ $(B 'b')
+ $(B 'd')
+ $(B 'o')
+ $(B 'x')
+ $(B 'X')
+ $(B 'e')
+ $(B 'E')
+ $(B 'f')
+ $(B 'F')
+ $(B 'g')
+ $(B 'G')
+ $(B 'a')
+ $(B 'A')
+)
+ $(DL
+ $(DT $(I Flags))
+ $(DL
+ $(DT $(B '-'))
+ $(DD
+ Left justify the result in the field.
+ It overrides any $(B 0) flag.)
+
+ $(DT $(B '+'))
+ $(DD Prefix positive numbers in a signed conversion with a $(B +).
+ It overrides any $(I space) flag.)
+
+ $(DT $(B '#'))
+ $(DD Use alternative formatting:
+ $(DL
+ $(DT For $(B 'o'):)
+ $(DD Add to precision as necessary so that the first digit
+ of the octal formatting is a '0', even if both the argument
+ and the $(I Precision) are zero.)
+ $(DT For $(B 'x') ($(B 'X')):)
+ $(DD If non-zero, prefix result with $(B 0x) ($(B 0X)).)
+ $(DT For floating point formatting:)
+ $(DD Always insert the decimal point.)
+ $(DT For $(B 'g') ($(B 'G')):)
+ $(DD Do not elide trailing zeros.)
+ ))
+
+ $(DT $(B '0'))
+ $(DD For integer and floating point formatting when not nan or
+ infinity, use leading zeros
+ to pad rather than spaces.
+ Ignore if there's a $(I Precision).)
+
+ $(DT $(B ' '))
+ $(DD Prefix positive numbers in a signed conversion with a space.)
+ )
+
+ $(DT $(I Width))
+ $(DD
+ Specifies the minimum field width.
+ If the width is a $(B *), the next argument, which must be
+ of type $(B int), is taken as the width.
+ If the width is negative, it is as if the $(B -) was given
+ as a $(I Flags) character.)
+
+ $(DT $(I Precision))
+ $(DD Gives the precision for numeric conversions.
+ If the precision is a $(B *), the next argument, which must be
+ of type $(B int), is taken as the precision. If it is negative,
+ it is as if there was no $(I Precision).)
+
+ $(DT $(I FormatChar))
+ $(DD
+ $(DL
+ $(DT $(B 's'))
+ $(DD The corresponding argument is formatted in a manner consistent
+ with its type:
+ $(DL
+ $(DT $(B bool))
+ $(DD The result is <tt>'true'</tt> or <tt>'false'</tt>.)
+ $(DT integral types)
+ $(DD The $(B %d) format is used.)
+ $(DT floating point types)
+ $(DD The $(B %g) format is used.)
+ $(DT string types)
+ $(DD The result is the string converted to UTF-8.)
+ A $(I Precision) specifies the maximum number of characters
+ to use in the result.
+ $(DT classes derived from $(B Object))
+ $(DD The result is the string returned from the class instance's
+ $(B .toString()) method.
+ A $(I Precision) specifies the maximum number of characters
+ to use in the result.)
+ $(DT non-string static and dynamic arrays)
+ $(DD The result is [s<sub>0</sub>, s<sub>1</sub>, ...]
+ where s<sub>k</sub> is the kth element
+ formatted with the default format.)
+ ))
+
+ $(DT $(B 'b','d','o','x','X'))
+ $(DD The corresponding argument must be an integral type
+ and is formatted as an integer. If the argument is a signed type
+ and the $(I FormatChar) is $(B d) it is converted to
+ a signed string of characters, otherwise it is treated as
+ unsigned. An argument of type $(B bool) is formatted as '1'
+ or '0'. The base used is binary for $(B b), octal for $(B o),
+ decimal
+ for $(B d), and hexadecimal for $(B x) or $(B X).
+ $(B x) formats using lower case letters, $(B X) uppercase.
+ If there are fewer resulting digits than the $(I Precision),
+ leading zeros are used as necessary.
+ If the $(I Precision) is 0 and the number is 0, no digits
+ result.)
+
+ $(DT $(B 'e','E'))
+ $(DD A floating point number is formatted as one digit before
+ the decimal point, $(I Precision) digits after, the $(I FormatChar),
+ &plusmn;, followed by at least a two digit exponent: $(I d.dddddd)e$(I &plusmn;dd).
+ If there is no $(I Precision), six
+ digits are generated after the decimal point.
+ If the $(I Precision) is 0, no decimal point is generated.)
+
+ $(DT $(B 'f','F'))
+ $(DD A floating point number is formatted in decimal notation.
+ The $(I Precision) specifies the number of digits generated
+ after the decimal point. It defaults to six. At least one digit
+ is generated before the decimal point. If the $(I Precision)
+ is zero, no decimal point is generated.)
+
+ $(DT $(B 'g','G'))
+ $(DD A floating point number is formatted in either $(B e) or
+ $(B f) format for $(B g); $(B E) or $(B F) format for
+ $(B G).
+ The $(B f) format is used if the exponent for an $(B e) format
+ is greater than -5 and less than the $(I Precision).
+ The $(I Precision) specifies the number of significant
+ digits, and defaults to six.
+ Trailing zeros are elided after the decimal point, if the fractional
+ part is zero then no decimal point is generated.)
+
+ $(DT $(B 'a','A'))
+ $(DD A floating point number is formatted in hexadecimal
+ exponential notation 0x$(I h.hhhhhh)p$(I &plusmn;d).
+ There is one hexadecimal digit before the decimal point, and as
+ many after as specified by the $(I Precision).
+ If the $(I Precision) is zero, no decimal point is generated.
+ If there is no $(I Precision), as many hexadecimal digits as
+ necessary to exactly represent the mantissa are generated.
+ The exponent is written in as few digits as possible,
+ but at least one, is in decimal, and represents a power of 2 as in
+ $(I h.hhhhhh)*2<sup>$(I &plusmn;d)</sup>.
+ The exponent for zero is zero.
+ The hexadecimal digits, x and p are in upper case if the
+ $(I FormatChar) is upper case.)
+ )
+
+ Floating point NaN's are formatted as $(B nan) if the
+ $(I FormatChar) is lower case, or $(B NAN) if upper.
+ Floating point infinities are formatted as $(B inf) or
+ $(B infinity) if the
+ $(I FormatChar) is lower case, or $(B INF) or $(B INFINITY) if upper.
+ ))
+
+Example:
+
+-------------------------
+import core.stdc.stdio;
+import std.format;
+
+void myPrint(...)
+{
+ void putc(dchar c)
+ {
+ fputc(c, stdout);
+ }
+
+ std.format.doFormat(&putc, _arguments, _argptr);
+}
+
+void main()
+{
+ int x = 27;
+
+ // prints 'The answer is 27:6'
+ myPrint("The answer is %s:", x, 6);
+}
+------------------------
+ */
+void doFormat()(scope void delegate(dchar) putc, TypeInfo[] arguments, va_list ap)
+{
+ import std.utf : toUCSindex, isValidDchar, UTFException, toUTF8;
+ import core.stdc.string : strlen;
+ import core.stdc.stdlib : alloca, malloc, realloc, free;
+ import core.stdc.stdio : snprintf;
+
+ size_t bufLength = 1024;
+ void* argBuffer = malloc(bufLength);
+ scope(exit) free(argBuffer);
+
+ size_t bufUsed = 0;
+ foreach (ti; arguments)
+ {
+ // Ensure the required alignment
+ bufUsed += ti.talign - 1;
+ bufUsed -= (cast(size_t)argBuffer + bufUsed) & (ti.talign - 1);
+ auto pos = bufUsed;
+ // Align to next word boundary
+ bufUsed += ti.tsize + size_t.sizeof - 1;
+ bufUsed -= (cast(size_t)argBuffer + bufUsed) & (size_t.sizeof - 1);
+ // Resize buffer if necessary
+ while (bufUsed > bufLength)
+ {
+ bufLength *= 2;
+ argBuffer = realloc(argBuffer, bufLength);
+ }
+ // Copy argument into buffer
+ va_arg(ap, ti, argBuffer + pos);
+ }
+
+ auto argptr = argBuffer;
+ void* skipArg(TypeInfo ti)
+ {
+ // Ensure the required alignment
+ argptr += ti.talign - 1;
+ argptr -= cast(size_t)argptr & (ti.talign - 1);
+ auto p = argptr;
+ // Align to next word boundary
+ argptr += ti.tsize + size_t.sizeof - 1;
+ argptr -= cast(size_t)argptr & (size_t.sizeof - 1);
+ return p;
+ }
+ auto getArg(T)()
+ {
+ return *cast(T*)skipArg(typeid(T));
+ }
+
+ TypeInfo ti;
+ Mangle m;
+ uint flags;
+ int field_width;
+ int precision;
+
+ enum : uint
+ {
+ FLdash = 1,
+ FLplus = 2,
+ FLspace = 4,
+ FLhash = 8,
+ FLlngdbl = 0x20,
+ FL0pad = 0x40,
+ FLprecision = 0x80,
+ }
+
+ static TypeInfo skipCI(TypeInfo valti)
+ {
+ for (;;)
+ {
+ if (typeid(valti).name.length == 18 &&
+ typeid(valti).name[9..18] == "Invariant")
+ valti = (cast(TypeInfo_Invariant)valti).base;
+ else if (typeid(valti).name.length == 14 &&
+ typeid(valti).name[9..14] == "Const")
+ valti = (cast(TypeInfo_Const)valti).base;
+ else
+ break;
+ }
+
+ return valti;
+ }
+
+ void formatArg(char fc)
+ {
+ bool vbit;
+ ulong vnumber;
+ char vchar;
+ dchar vdchar;
+ Object vobject;
+ real vreal;
+ creal vcreal;
+ Mangle m2;
+ int signed = 0;
+ uint base = 10;
+ int uc;
+ char[ulong.sizeof * 8] tmpbuf; // long enough to print long in binary
+ const(char)* prefix = "";
+ string s;
+
+ void putstr(const char[] s)
+ {
+ //printf("putstr: s = %.*s, flags = x%x\n", s.length, s.ptr, flags);
+ ptrdiff_t padding = field_width -
+ (strlen(prefix) + toUCSindex(s, s.length));
+ ptrdiff_t prepad = 0;
+ ptrdiff_t postpad = 0;
+ if (padding > 0)
+ {
+ if (flags & FLdash)
+ postpad = padding;
+ else
+ prepad = padding;
+ }
+
+ if (flags & FL0pad)
+ {
+ while (*prefix)
+ putc(*prefix++);
+ while (prepad--)
+ putc('0');
+ }
+ else
+ {
+ while (prepad--)
+ putc(' ');
+ while (*prefix)
+ putc(*prefix++);
+ }
+
+ foreach (dchar c; s)
+ putc(c);
+
+ while (postpad--)
+ putc(' ');
+ }
+
+ void putreal(real v)
+ {
+ //printf("putreal %Lg\n", vreal);
+
+ switch (fc)
+ {
+ case 's':
+ fc = 'g';
+ break;
+
+ case 'f', 'F', 'e', 'E', 'g', 'G', 'a', 'A':
+ break;
+
+ default:
+ //printf("fc = '%c'\n", fc);
+ Lerror:
+ throw new FormatException("incompatible format character for floating point type");
+ }
+ version (DigitalMarsC)
+ {
+ uint sl;
+ char[] fbuf = tmpbuf;
+ if (!(flags & FLprecision))
+ precision = 6;
+ while (1)
+ {
+ sl = fbuf.length;
+ prefix = (*__pfloatfmt)(fc, flags | FLlngdbl,
+ precision, &v, cast(char*)fbuf, &sl, field_width);
+ if (sl != -1)
+ break;
+ sl = fbuf.length * 2;
+ fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl];
+ }
+ putstr(fbuf[0 .. sl]);
+ }
+ else
+ {
+ ptrdiff_t sl;
+ char[] fbuf = tmpbuf;
+ char[12] format;
+ format[0] = '%';
+ int i = 1;
+ if (flags & FLdash)
+ format[i++] = '-';
+ if (flags & FLplus)
+ format[i++] = '+';
+ if (flags & FLspace)
+ format[i++] = ' ';
+ if (flags & FLhash)
+ format[i++] = '#';
+ if (flags & FL0pad)
+ format[i++] = '0';
+ format[i + 0] = '*';
+ format[i + 1] = '.';
+ format[i + 2] = '*';
+ format[i + 3] = 'L';
+ format[i + 4] = fc;
+ format[i + 5] = 0;
+ if (!(flags & FLprecision))
+ precision = -1;
+ while (1)
+ {
+ sl = fbuf.length;
+ int n;
+ version (CRuntime_Microsoft)
+ {
+ import std.math : isNaN, isInfinity;
+ if (isNaN(v)) // snprintf writes 1.#QNAN
+ n = snprintf(fbuf.ptr, sl, "nan");
+ else if (isInfinity(v)) // snprintf writes 1.#INF
+ n = snprintf(fbuf.ptr, sl, v < 0 ? "-inf" : "inf");
+ else
+ n = snprintf(fbuf.ptr, sl, format.ptr, field_width,
+ precision, cast(double)v);
+ }
+ else
+ n = snprintf(fbuf.ptr, sl, format.ptr, field_width,
+ precision, v);
+ //printf("format = '%s', n = %d\n", cast(char*)format, n);
+ if (n >= 0 && n < sl)
+ { sl = n;
+ break;
+ }
+ if (n < 0)
+ sl = sl * 2;
+ else
+ sl = n + 1;
+ fbuf = (cast(char*)alloca(sl * char.sizeof))[0 .. sl];
+ }
+ putstr(fbuf[0 .. sl]);
+ }
+ return;
+ }
+
+ static Mangle getMan(TypeInfo ti)
+ {
+ auto m = cast(Mangle)typeid(ti).name[9];
+ if (typeid(ti).name.length == 20 &&
+ typeid(ti).name[9..20] == "StaticArray")
+ m = cast(Mangle)'G';
+ return m;
+ }
+
+ /* p = pointer to the first element in the array
+ * len = number of elements in the array
+ * valti = type of the elements
+ */
+ void putArray(void* p, size_t len, TypeInfo valti)
+ {
+ //printf("\nputArray(len = %u), tsize = %u\n", len, valti.tsize);
+ putc('[');
+ valti = skipCI(valti);
+ size_t tsize = valti.tsize;
+ auto argptrSave = argptr;
+ auto tiSave = ti;
+ auto mSave = m;
+ ti = valti;
+ //printf("\n%.*s\n", typeid(valti).name.length, typeid(valti).name.ptr);
+ m = getMan(valti);
+ while (len--)
+ {
+ //doFormat(putc, (&valti)[0 .. 1], p);
+ argptr = p;
+ formatArg('s');
+ p += tsize;
+ if (len > 0) putc(',');
+ }
+ m = mSave;
+ ti = tiSave;
+ argptr = argptrSave;
+ putc(']');
+ }
+
+ void putAArray(ubyte[long] vaa, TypeInfo valti, TypeInfo keyti)
+ {
+ putc('[');
+ bool comma=false;
+ auto argptrSave = argptr;
+ auto tiSave = ti;
+ auto mSave = m;
+ valti = skipCI(valti);
+ keyti = skipCI(keyti);
+ foreach (ref fakevalue; vaa)
+ {
+ if (comma) putc(',');
+ comma = true;
+ void *pkey = &fakevalue;
+ version (D_LP64)
+ pkey -= (long.sizeof + 15) & ~(15);
+ else
+ pkey -= (long.sizeof + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
+
+ // the key comes before the value
+ auto keysize = keyti.tsize;
+ version (D_LP64)
+ auto keysizet = (keysize + 15) & ~(15);
+ else
+ auto keysizet = (keysize + size_t.sizeof - 1) & ~(size_t.sizeof - 1);
+
+ void* pvalue = pkey + keysizet;
+
+ //doFormat(putc, (&keyti)[0..1], pkey);
+ m = getMan(keyti);
+ argptr = pkey;
+
+ ti = keyti;
+ formatArg('s');
+
+ putc(':');
+ //doFormat(putc, (&valti)[0..1], pvalue);
+ m = getMan(valti);
+ argptr = pvalue;
+
+ ti = valti;
+ formatArg('s');
+ }
+ m = mSave;
+ ti = tiSave;
+ argptr = argptrSave;
+ putc(']');
+ }
+
+ //printf("formatArg(fc = '%c', m = '%c')\n", fc, m);
+ int mi;
+ switch (m)
+ {
+ case Mangle.Tbool:
+ vbit = getArg!(bool)();
+ if (fc != 's')
+ { vnumber = vbit;
+ goto Lnumber;
+ }
+ putstr(vbit ? "true" : "false");
+ return;
+
+ case Mangle.Tchar:
+ vchar = getArg!(char)();
+ if (fc != 's')
+ { vnumber = vchar;
+ goto Lnumber;
+ }
+ L2:
+ putstr((&vchar)[0 .. 1]);
+ return;
+
+ case Mangle.Twchar:
+ vdchar = getArg!(wchar)();
+ goto L1;
+
+ case Mangle.Tdchar:
+ vdchar = getArg!(dchar)();
+ L1:
+ if (fc != 's')
+ { vnumber = vdchar;
+ goto Lnumber;
+ }
+ if (vdchar <= 0x7F)
+ { vchar = cast(char)vdchar;
+ goto L2;
+ }
+ else
+ { if (!isValidDchar(vdchar))
+ throw new UTFException("invalid dchar in format");
+ char[4] vbuf;
+ putstr(toUTF8(vbuf, vdchar));
+ }
+ return;
+
+ case Mangle.Tbyte:
+ signed = 1;
+ vnumber = getArg!(byte)();
+ goto Lnumber;
+
+ case Mangle.Tubyte:
+ vnumber = getArg!(ubyte)();
+ goto Lnumber;
+
+ case Mangle.Tshort:
+ signed = 1;
+ vnumber = getArg!(short)();
+ goto Lnumber;
+
+ case Mangle.Tushort:
+ vnumber = getArg!(ushort)();
+ goto Lnumber;
+
+ case Mangle.Tint:
+ signed = 1;
+ vnumber = getArg!(int)();
+ goto Lnumber;
+
+ case Mangle.Tuint:
+ Luint:
+ vnumber = getArg!(uint)();
+ goto Lnumber;
+
+ case Mangle.Tlong:
+ signed = 1;
+ vnumber = cast(ulong)getArg!(long)();
+ goto Lnumber;
+
+ case Mangle.Tulong:
+ Lulong:
+ vnumber = getArg!(ulong)();
+ goto Lnumber;
+
+ case Mangle.Tclass:
+ vobject = getArg!(Object)();
+ if (vobject is null)
+ s = "null";
+ else
+ s = vobject.toString();
+ goto Lputstr;
+
+ case Mangle.Tpointer:
+ vnumber = cast(ulong)getArg!(void*)();
+ if (fc != 'x') uc = 1;
+ flags |= FL0pad;
+ if (!(flags & FLprecision))
+ { flags |= FLprecision;
+ precision = (void*).sizeof;
+ }
+ base = 16;
+ goto Lnumber;
+
+ case Mangle.Tfloat:
+ case Mangle.Tifloat:
+ if (fc == 'x' || fc == 'X')
+ goto Luint;
+ vreal = getArg!(float)();
+ goto Lreal;
+
+ case Mangle.Tdouble:
+ case Mangle.Tidouble:
+ if (fc == 'x' || fc == 'X')
+ goto Lulong;
+ vreal = getArg!(double)();
+ goto Lreal;
+
+ case Mangle.Treal:
+ case Mangle.Tireal:
+ vreal = getArg!(real)();
+ goto Lreal;
+
+ case Mangle.Tcfloat:
+ vcreal = getArg!(cfloat)();
+ goto Lcomplex;
+
+ case Mangle.Tcdouble:
+ vcreal = getArg!(cdouble)();
+ goto Lcomplex;
+
+ case Mangle.Tcreal:
+ vcreal = getArg!(creal)();
+ goto Lcomplex;
+
+ case Mangle.Tsarray:
+ putArray(argptr, (cast(TypeInfo_StaticArray)ti).len, (cast(TypeInfo_StaticArray)ti).next);
+ return;
+
+ case Mangle.Tarray:
+ mi = 10;
+ if (typeid(ti).name.length == 14 &&
+ typeid(ti).name[9..14] == "Array")
+ { // array of non-primitive types
+ TypeInfo tn = (cast(TypeInfo_Array)ti).next;
+ tn = skipCI(tn);
+ switch (cast(Mangle)typeid(tn).name[9])
+ {
+ case Mangle.Tchar: goto LarrayChar;
+ case Mangle.Twchar: goto LarrayWchar;
+ case Mangle.Tdchar: goto LarrayDchar;
+ default:
+ break;
+ }
+ void[] va = getArg!(void[])();
+ putArray(va.ptr, va.length, tn);
+ return;
+ }
+ if (typeid(ti).name.length == 25 &&
+ typeid(ti).name[9..25] == "AssociativeArray")
+ { // associative array
+ ubyte[long] vaa = getArg!(ubyte[long])();
+ putAArray(vaa,
+ (cast(TypeInfo_AssociativeArray)ti).next,
+ (cast(TypeInfo_AssociativeArray)ti).key);
+ return;
+ }
+
+ while (1)
+ {
+ m2 = cast(Mangle)typeid(ti).name[mi];
+ switch (m2)
+ {
+ case Mangle.Tchar:
+ LarrayChar:
+ s = getArg!(string)();
+ goto Lputstr;
+
+ case Mangle.Twchar:
+ LarrayWchar:
+ wchar[] sw = getArg!(wchar[])();
+ s = toUTF8(sw);
+ goto Lputstr;
+
+ case Mangle.Tdchar:
+ LarrayDchar:
+ s = toUTF8(getArg!(dstring)());
+ Lputstr:
+ if (fc != 's')
+ throw new FormatException("string");
+ if (flags & FLprecision && precision < s.length)
+ s = s[0 .. precision];
+ putstr(s);
+ break;
+
+ case Mangle.Tconst:
+ case Mangle.Timmutable:
+ mi++;
+ continue;
+
+ default:
+ TypeInfo ti2 = primitiveTypeInfo(m2);
+ if (!ti2)
+ goto Lerror;
+ void[] va = getArg!(void[])();
+ putArray(va.ptr, va.length, ti2);
+ }
+ return;
+ }
+ assert(0);
+
+ case Mangle.Ttypedef:
+ ti = (cast(TypeInfo_Typedef)ti).base;
+ m = cast(Mangle)typeid(ti).name[9];
+ formatArg(fc);
+ return;
+
+ case Mangle.Tenum:
+ ti = (cast(TypeInfo_Enum)ti).base;
+ m = cast(Mangle)typeid(ti).name[9];
+ formatArg(fc);
+ return;
+
+ case Mangle.Tstruct:
+ { TypeInfo_Struct tis = cast(TypeInfo_Struct)ti;
+ if (tis.xtoString is null)
+ throw new FormatException("Can't convert " ~ tis.toString()
+ ~ " to string: \"string toString()\" not defined");
+ s = tis.xtoString(skipArg(tis));
+ goto Lputstr;
+ }
+
+ default:
+ goto Lerror;
+ }
+
+ Lnumber:
+ switch (fc)
+ {
+ case 's':
+ case 'd':
+ if (signed)
+ { if (cast(long)vnumber < 0)
+ { prefix = "-";
+ vnumber = -vnumber;
+ }
+ else if (flags & FLplus)
+ prefix = "+";
+ else if (flags & FLspace)
+ prefix = " ";
+ }
+ break;
+
+ case 'b':
+ signed = 0;
+ base = 2;
+ break;
+
+ case 'o':
+ signed = 0;
+ base = 8;
+ break;
+
+ case 'X':
+ uc = 1;
+ if (flags & FLhash && vnumber)
+ prefix = "0X";
+ signed = 0;
+ base = 16;
+ break;
+
+ case 'x':
+ if (flags & FLhash && vnumber)
+ prefix = "0x";
+ signed = 0;
+ base = 16;
+ break;
+
+ default:
+ goto Lerror;
+ }
+
+ if (!signed)
+ {
+ switch (m)
+ {
+ case Mangle.Tbyte:
+ vnumber &= 0xFF;
+ break;
+
+ case Mangle.Tshort:
+ vnumber &= 0xFFFF;
+ break;
+
+ case Mangle.Tint:
+ vnumber &= 0xFFFFFFFF;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (flags & FLprecision && fc != 'p')
+ flags &= ~FL0pad;
+
+ if (vnumber < base)
+ {
+ if (vnumber == 0 && precision == 0 && flags & FLprecision &&
+ !(fc == 'o' && flags & FLhash))
+ {
+ putstr(null);
+ return;
+ }
+ if (precision == 0 || !(flags & FLprecision))
+ { vchar = cast(char)('0' + vnumber);
+ if (vnumber < 10)
+ vchar = cast(char)('0' + vnumber);
+ else
+ vchar = cast(char)((uc ? 'A' - 10 : 'a' - 10) + vnumber);
+ goto L2;
+ }
+ }
+
+ {
+ ptrdiff_t n = tmpbuf.length;
+ char c;
+ int hexoffset = uc ? ('A' - ('9' + 1)) : ('a' - ('9' + 1));
+
+ while (vnumber)
+ {
+ c = cast(char)((vnumber % base) + '0');
+ if (c > '9')
+ c += hexoffset;
+ vnumber /= base;
+ tmpbuf[--n] = c;
+ }
+ if (tmpbuf.length - n < precision && precision < tmpbuf.length)
+ {
+ ptrdiff_t m = tmpbuf.length - precision;
+ tmpbuf[m .. n] = '0';
+ n = m;
+ }
+ else if (flags & FLhash && fc == 'o')
+ prefix = "0";
+ putstr(tmpbuf[n .. tmpbuf.length]);
+ return;
+ }
+
+ Lreal:
+ putreal(vreal);
+ return;
+
+ Lcomplex:
+ putreal(vcreal.re);
+ if (vcreal.im >= 0)
+ {
+ putc('+');
+ }
+ putreal(vcreal.im);
+ putc('i');
+ return;
+
+ Lerror:
+ throw new FormatException("formatArg");
+ }
+
+ for (int j = 0; j < arguments.length; )
+ {
+ ti = arguments[j++];
+ //printf("arg[%d]: '%.*s' %d\n", j, typeid(ti).name.length, typeid(ti).name.ptr, typeid(ti).name.length);
+ //ti.print();
+
+ flags = 0;
+ precision = 0;
+ field_width = 0;
+
+ ti = skipCI(ti);
+ int mi = 9;
+ do
+ {
+ if (typeid(ti).name.length <= mi)
+ goto Lerror;
+ m = cast(Mangle)typeid(ti).name[mi++];
+ } while (m == Mangle.Tconst || m == Mangle.Timmutable);
+
+ if (m == Mangle.Tarray)
+ {
+ if (typeid(ti).name.length == 14 &&
+ typeid(ti).name[9..14] == "Array")
+ {
+ TypeInfo tn = (cast(TypeInfo_Array)ti).next;
+ tn = skipCI(tn);
+ switch (cast(Mangle)typeid(tn).name[9])
+ {
+ case Mangle.Tchar:
+ case Mangle.Twchar:
+ case Mangle.Tdchar:
+ ti = tn;
+ mi = 9;
+ break;
+ default:
+ break;
+ }
+ }
+ L1:
+ Mangle m2 = cast(Mangle)typeid(ti).name[mi];
+ string fmt; // format string
+ wstring wfmt;
+ dstring dfmt;
+
+ /* For performance reasons, this code takes advantage of the
+ * fact that most format strings will be ASCII, and that the
+ * format specifiers are always ASCII. This means we only need
+ * to deal with UTF in a couple of isolated spots.
+ */
+
+ switch (m2)
+ {
+ case Mangle.Tchar:
+ fmt = getArg!(string)();
+ break;
+
+ case Mangle.Twchar:
+ wfmt = getArg!(wstring)();
+ fmt = toUTF8(wfmt);
+ break;
+
+ case Mangle.Tdchar:
+ dfmt = getArg!(dstring)();
+ fmt = toUTF8(dfmt);
+ break;
+
+ case Mangle.Tconst:
+ case Mangle.Timmutable:
+ mi++;
+ goto L1;
+
+ default:
+ formatArg('s');
+ continue;
+ }
+
+ for (size_t i = 0; i < fmt.length; )
+ { dchar c = fmt[i++];
+
+ dchar getFmtChar()
+ { // Valid format specifier characters will never be UTF
+ if (i == fmt.length)
+ throw new FormatException("invalid specifier");
+ return fmt[i++];
+ }
+
+ int getFmtInt()
+ { int n;
+
+ while (1)
+ {
+ n = n * 10 + (c - '0');
+ if (n < 0) // overflow
+ throw new FormatException("int overflow");
+ c = getFmtChar();
+ if (c < '0' || c > '9')
+ break;
+ }
+ return n;
+ }
+
+ int getFmtStar()
+ { Mangle m;
+ TypeInfo ti;
+
+ if (j == arguments.length)
+ throw new FormatException("too few arguments");
+ ti = arguments[j++];
+ m = cast(Mangle)typeid(ti).name[9];
+ if (m != Mangle.Tint)
+ throw new FormatException("int argument expected");
+ return getArg!(int)();
+ }
+
+ if (c != '%')
+ {
+ if (c > 0x7F) // if UTF sequence
+ {
+ i--; // back up and decode UTF sequence
+ import std.utf : decode;
+ c = decode(fmt, i);
+ }
+ Lputc:
+ putc(c);
+ continue;
+ }
+
+ // Get flags {-+ #}
+ flags = 0;
+ while (1)
+ {
+ c = getFmtChar();
+ switch (c)
+ {
+ case '-': flags |= FLdash; continue;
+ case '+': flags |= FLplus; continue;
+ case ' ': flags |= FLspace; continue;
+ case '#': flags |= FLhash; continue;
+ case '0': flags |= FL0pad; continue;
+
+ case '%': if (flags == 0)
+ goto Lputc;
+ break;
+
+ default: break;
+ }
+ break;
+ }
+
+ // Get field width
+ field_width = 0;
+ if (c == '*')
+ {
+ field_width = getFmtStar();
+ if (field_width < 0)
+ { flags |= FLdash;
+ field_width = -field_width;
+ }
+
+ c = getFmtChar();
+ }
+ else if (c >= '0' && c <= '9')
+ field_width = getFmtInt();
+
+ if (flags & FLplus)
+ flags &= ~FLspace;
+ if (flags & FLdash)
+ flags &= ~FL0pad;
+
+ // Get precision
+ precision = 0;
+ if (c == '.')
+ { flags |= FLprecision;
+ //flags &= ~FL0pad;
+
+ c = getFmtChar();
+ if (c == '*')
+ {
+ precision = getFmtStar();
+ if (precision < 0)
+ { precision = 0;
+ flags &= ~FLprecision;
+ }
+
+ c = getFmtChar();
+ }
+ else if (c >= '0' && c <= '9')
+ precision = getFmtInt();
+ }
+
+ if (j == arguments.length)
+ goto Lerror;
+ ti = arguments[j++];
+ ti = skipCI(ti);
+ mi = 9;
+ do
+ {
+ m = cast(Mangle)typeid(ti).name[mi++];
+ } while (m == Mangle.Tconst || m == Mangle.Timmutable);
+
+ if (c > 0x7F) // if UTF sequence
+ goto Lerror; // format specifiers can't be UTF
+ formatArg(cast(char)c);
+ }
+ }
+ else
+ {
+ formatArg('s');
+ }
+ }
+ return;
+
+ Lerror:
+ throw new FormatException();
+}
+
+
+private bool needToSwapEndianess(Char)(ref FormatSpec!Char f)
+{
+ import std.system : endian, Endian;
+
+ return endian == Endian.littleEndian && f.flPlus
+ || endian == Endian.bigEndian && f.flDash;
+}
+
+/* ======================== Unit Tests ====================================== */
+
+unittest
+{
+ import std.conv : octal;
+
+ int i;
+ string s;
+
+ debug(format) printf("std.format.format.unittest\n");
+
+ s = format("hello world! %s %s %s%s%s", true, 57, 1_000_000_000, 'x', " foo");
+ assert(s == "hello world! true 57 1000000000x foo");
+
+ s = format("%s %A %s", 1.67, -1.28, float.nan);
+ /* The host C library is used to format floats.
+ * C99 doesn't specify what the hex digit before the decimal point
+ * is for %A.
+ */
+ //version (linux)
+ // assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan");
+ //else version (OSX)
+ // assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan", s);
+ //else
+ version (MinGW)
+ assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan", s);
+ else version (CRuntime_Microsoft)
+ assert(s == "1.67 -0X1.47AE14P+0 nan"
+ || s == "1.67 -0X1.47AE147AE147BP+0 nan", s); // MSVCRT 14+ (VS 2015)
+ else
+ assert(s == "1.67 -0X1.47AE147AE147BP+0 nan", s);
+
+ s = format("%x %X", 0x1234AF, 0xAFAFAFAF);
+ assert(s == "1234af AFAFAFAF");
+
+ s = format("%b %o", 0x1234AF, 0xAFAFAFAF);
+ assert(s == "100100011010010101111 25753727657");
+
+ s = format("%d %s", 0x1234AF, 0xAFAFAFAF);
+ assert(s == "1193135 2947526575");
+
+ //version(X86_64)
+ //{
+ // pragma(msg, "several format tests disabled on x86_64 due to bug 5625");
+ //}
+ //else
+ //{
+ s = format("%s", 1.2 + 3.4i);
+ assert(s == "1.2+3.4i", s);
+
+ //s = format("%x %X", 1.32, 6.78f);
+ //assert(s == "3ff51eb851eb851f 40D8F5C3");
+
+ //}
+
+ s = format("%#06.*f",2,12.345);
+ assert(s == "012.35");
+
+ s = format("%#0*.*f",6,2,12.345);
+ assert(s == "012.35");
+
+ s = format("%7.4g:", 12.678);
+ assert(s == " 12.68:");
+
+ s = format("%7.4g:", 12.678L);
+ assert(s == " 12.68:");
+
+ s = format("%04f|%05d|%#05x|%#5x",-4.0,-10,1,1);
+ assert(s == "-4.000000|-0010|0x001| 0x1");
+
+ i = -10;
+ s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+ assert(s == "-10|-10|-10|-10|-10.0000");
+
+ i = -5;
+ s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+ assert(s == "-5| -5|-05|-5|-5.0000");
+
+ i = 0;
+ s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+ assert(s == "0| 0|000|0|0.0000");
+
+ i = 5;
+ s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+ assert(s == "5| 5|005|5|5.0000");
+
+ i = 10;
+ s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
+ assert(s == "10| 10|010|10|10.0000");
+
+ s = format("%.0d", 0);
+ assert(s == "");
+
+ s = format("%.g", .34);
+ assert(s == "0.3");
+
+ s = format("%.0g", .34);
+ assert(s == "0.3");
+
+ s = format("%.2g", .34);
+ assert(s == "0.34");
+
+ s = format("%0.0008f", 1e-08);
+ assert(s == "0.00000001");
+
+ s = format("%0.0008f", 1e-05);
+ assert(s == "0.00001000");
+
+ s = "helloworld";
+ string r;
+ r = format("%.2s", s[0..5]);
+ assert(r == "he");
+ r = format("%.20s", s[0..5]);
+ assert(r == "hello");
+ r = format("%8s", s[0..5]);
+ assert(r == " hello");
+
+ byte[] arrbyte = new byte[4];
+ arrbyte[0] = 100;
+ arrbyte[1] = -99;
+ arrbyte[3] = 0;
+ r = format("%s", arrbyte);
+ assert(r == "[100, -99, 0, 0]");
+
+ ubyte[] arrubyte = new ubyte[4];
+ arrubyte[0] = 100;
+ arrubyte[1] = 200;
+ arrubyte[3] = 0;
+ r = format("%s", arrubyte);
+ assert(r == "[100, 200, 0, 0]");
+
+ short[] arrshort = new short[4];
+ arrshort[0] = 100;
+ arrshort[1] = -999;
+ arrshort[3] = 0;
+ r = format("%s", arrshort);
+ assert(r == "[100, -999, 0, 0]");
+
+ ushort[] arrushort = new ushort[4];
+ arrushort[0] = 100;
+ arrushort[1] = 20_000;
+ arrushort[3] = 0;
+ r = format("%s", arrushort);
+ assert(r == "[100, 20000, 0, 0]");
+
+ int[] arrint = new int[4];
+ arrint[0] = 100;
+ arrint[1] = -999;
+ arrint[3] = 0;
+ r = format("%s", arrint);
+ assert(r == "[100, -999, 0, 0]");
+
+ long[] arrlong = new long[4];
+ arrlong[0] = 100;
+ arrlong[1] = -999;
+ arrlong[3] = 0;
+ r = format("%s", arrlong);
+ assert(r == "[100, -999, 0, 0]");
+
+ ulong[] arrulong = new ulong[4];
+ arrulong[0] = 100;
+ arrulong[1] = 999;
+ arrulong[3] = 0;
+ r = format("%s", arrulong);
+ assert(r == "[100, 999, 0, 0]");
+
+ string[] arr2 = new string[4];
+ arr2[0] = "hello";
+ arr2[1] = "world";
+ arr2[3] = "foo";
+ r = format("%s", arr2);
+ assert(r == `["hello", "world", "", "foo"]`);
+
+ r = format("%.8d", 7);
+ assert(r == "00000007");
+ r = format("%.8x", 10);
+ assert(r == "0000000a");
+
+ r = format("%-3d", 7);
+ assert(r == "7 ");
+
+ r = format("%*d", -3, 7);
+ assert(r == "7 ");
+
+ r = format("%.*d", -3, 7);
+ assert(r == "7");
+
+ r = format("abc"c);
+ assert(r == "abc");
+
+ //format() returns the same type as inputted.
+ wstring wr;
+ wr = format("def"w);
+ assert(wr == "def"w);
+
+ dstring dr;
+ dr = format("ghi"d);
+ assert(dr == "ghi"d);
+
+ void* p = cast(void*)0xDEADBEEF;
+ r = format("%s", p);
+ assert(r == "DEADBEEF");
+
+ r = format("%#x", 0xabcd);
+ assert(r == "0xabcd");
+ r = format("%#X", 0xABCD);
+ assert(r == "0XABCD");
+
+ r = format("%#o", octal!12345);
+ assert(r == "012345");
+ r = format("%o", 9);
+ assert(r == "11");
+ r = format("%#o", 0); // issue 15663
+ assert(r == "0");
+
+ r = format("%+d", 123);
+ assert(r == "+123");
+ r = format("%+d", -123);
+ assert(r == "-123");
+ r = format("% d", 123);
+ assert(r == " 123");
+ r = format("% d", -123);
+ assert(r == "-123");
+
+ r = format("%%");
+ assert(r == "%");
+
+ r = format("%d", true);
+ assert(r == "1");
+ r = format("%d", false);
+ assert(r == "0");
+
+ r = format("%d", 'a');
+ assert(r == "97");
+ wchar wc = 'a';
+ r = format("%d", wc);
+ assert(r == "97");
+ dchar dc = 'a';
+ r = format("%d", dc);
+ assert(r == "97");
+
+ byte b = byte.max;
+ r = format("%x", b);
+ assert(r == "7f");
+ r = format("%x", ++b);
+ assert(r == "80");
+ r = format("%x", ++b);
+ assert(r == "81");
+
+ short sh = short.max;
+ r = format("%x", sh);
+ assert(r == "7fff");
+ r = format("%x", ++sh);
+ assert(r == "8000");
+ r = format("%x", ++sh);
+ assert(r == "8001");
+
+ i = int.max;
+ r = format("%x", i);
+ assert(r == "7fffffff");
+ r = format("%x", ++i);
+ assert(r == "80000000");
+ r = format("%x", ++i);
+ assert(r == "80000001");
+
+ r = format("%x", 10);
+ assert(r == "a");
+ r = format("%X", 10);
+ assert(r == "A");
+ r = format("%x", 15);
+ assert(r == "f");
+ r = format("%X", 15);
+ assert(r == "F");
+
+ Object c = null;
+ r = format("%s", c);
+ assert(r == "null");
+
+ enum TestEnum
+ {
+ Value1, Value2
+ }
+ r = format("%s", TestEnum.Value2);
+ assert(r == "Value2");
+
+ immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]);
+ r = format("%s", aa.values);
+ assert(r == `["hello", "betty"]` || r == `["betty", "hello"]`);
+ r = format("%s", aa);
+ assert(r == `[3:"hello", 4:"betty"]` || r == `[4:"betty", 3:"hello"]`);
+
+ static const dchar[] ds = ['a','b'];
+ for (int j = 0; j < ds.length; ++j)
+ {
+ r = format(" %d", ds[j]);
+ if (j == 0)
+ assert(r == " 97");
+ else
+ assert(r == " 98");
+ }
+
+ r = format(">%14d<, %s", 15, [1,2,3]);
+ assert(r == "> 15<, [1, 2, 3]");
+
+ assert(format("%8s", "bar") == " bar");
+ assert(format("%8s", "b\u00e9ll\u00f4") == " b\u00e9ll\u00f4");
+}