mirror of
https://github.com/dlang/phobos.git
synced 2025-04-28 14:10:30 +03:00
1184 lines
27 KiB
D
1184 lines
27 KiB
D
/**
|
|
* This module implements the workhorse functionality for string and I/O formatting.
|
|
* It's comparable to C99's vsprintf().
|
|
*
|
|
* Macros:
|
|
* WIKI = StdFormat
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2004-2005 by Digital Mars, www.digitalmars.com
|
|
* Written by Walter Bright
|
|
*
|
|
* This software is provided 'as-is', without any express or implied
|
|
* warranty. In no event will the authors be held liable for any damages
|
|
* arising from the use of this software.
|
|
*
|
|
* Permission is granted to anyone to use this software for any purpose,
|
|
* including commercial applications, and to alter it and redistribute it
|
|
* freely, subject to the following restrictions:
|
|
*
|
|
* o The origin of this software must not be misrepresented; you must not
|
|
* claim that you wrote the original software. If you use this software
|
|
* in a product, an acknowledgment in the product documentation would be
|
|
* appreciated but is not required.
|
|
* o Altered source versions must be plainly marked as such, and must not
|
|
* be misrepresented as being the original software.
|
|
* o This notice may not be removed or altered from any source
|
|
* distribution.
|
|
*/
|
|
|
|
module std.format;
|
|
|
|
//debug=format; // uncomment to turn on debugging printf's
|
|
|
|
import std.stdarg; // caller will need va_list
|
|
|
|
private import std.utf;
|
|
private import std.c.stdlib;
|
|
private import std.string;
|
|
|
|
alias bool bit;
|
|
|
|
version (Windows)
|
|
{
|
|
version (DigitalMars)
|
|
{
|
|
version = DigitalMarsC;
|
|
}
|
|
}
|
|
|
|
version (DigitalMarsC)
|
|
{
|
|
// This is DMC's internal floating point formatting function
|
|
extern (C) char* function(int c, int flags, int precision, real* pdval,
|
|
char* buf, int* psl, int width) __pfloatfmt;
|
|
}
|
|
else
|
|
{
|
|
// Use C99 snprintf
|
|
extern (C) int snprintf(char* s, size_t n, char* format, ...);
|
|
}
|
|
|
|
/**********************************************************************
|
|
* Signals a mismatch between a format and its corresponding argument.
|
|
*/
|
|
class FormatError : Error
|
|
{
|
|
private:
|
|
|
|
this()
|
|
{
|
|
super("std.format");
|
|
}
|
|
|
|
this(char[] msg)
|
|
{
|
|
super("std.format " ~ msg);
|
|
}
|
|
}
|
|
|
|
|
|
enum Mangle : char
|
|
{
|
|
Tvoid = 'v',
|
|
Tbit = 'b',
|
|
Tbool = 'x',
|
|
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',
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
TypeInfo ti;
|
|
switch (m)
|
|
{
|
|
case Mangle.Tvoid:
|
|
ti = typeid(void);break;
|
|
case Mangle.Tbit:
|
|
ti = typeid(bit);break;
|
|
case Mangle.Tbool:
|
|
ti = typeid(bool);break;
|
|
case Mangle.Tbyte:
|
|
ti = typeid(byte);break;
|
|
case Mangle.Tubyte:
|
|
ti = typeid(ubyte);break;
|
|
case Mangle.Tshort:
|
|
ti = typeid(short);break;
|
|
case Mangle.Tushort:
|
|
ti = typeid(ushort);break;
|
|
case Mangle.Tint:
|
|
ti = typeid(int);break;
|
|
case Mangle.Tuint:
|
|
ti = typeid(uint);break;
|
|
case Mangle.Tlong:
|
|
ti = typeid(long);break;
|
|
case Mangle.Tulong:
|
|
ti = typeid(ulong);break;
|
|
case Mangle.Tfloat:
|
|
ti = typeid(float);break;
|
|
case Mangle.Tdouble:
|
|
ti = typeid(double);break;
|
|
case Mangle.Treal:
|
|
ti = typeid(real);break;
|
|
case Mangle.Tifloat:
|
|
ti = typeid(ifloat);break;
|
|
case Mangle.Tidouble:
|
|
ti = typeid(idouble);break;
|
|
case Mangle.Tireal:
|
|
ti = typeid(ireal);break;
|
|
case Mangle.Tcfloat:
|
|
ti = typeid(cfloat);break;
|
|
case Mangle.Tcdouble:
|
|
ti = typeid(cdouble);break;
|
|
case Mangle.Tcreal:
|
|
ti = typeid(creal);break;
|
|
case Mangle.Tchar:
|
|
ti = typeid(char);break;
|
|
case Mangle.Twchar:
|
|
ti = typeid(wchar);break;
|
|
case Mangle.Tdchar:
|
|
ti = typeid(dchar);
|
|
default:
|
|
ti = null;
|
|
}
|
|
return ti;
|
|
}
|
|
|
|
/************************************
|
|
* 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 char[], wchar[], or 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 FormatError 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 TypeInfo's, one for each argument to be formatted.
|
|
* argptr = Points to variadic argument list.
|
|
*
|
|
* Throws:
|
|
* Mismatched arguments and formats result in a FormatError 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:
|
|
|
|
<pre>
|
|
$(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')
|
|
</pre>
|
|
<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.
|
|
</dl>
|
|
|
|
<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.
|
|
</dl>
|
|
|
|
<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.
|
|
</dl>
|
|
|
|
<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),
|
|
±, followed by at least a two digit exponent: $(I d.dddddd)e$(I ±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 one.
|
|
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 ±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 ±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.
|
|
</dl>
|
|
|
|
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.
|
|
</dl>
|
|
|
|
Example:
|
|
|
|
-------------------------
|
|
import std.c.stdio;
|
|
import std.format;
|
|
|
|
void formattedPrint(...)
|
|
{
|
|
void putc(char c)
|
|
{
|
|
fputc(c, stdout);
|
|
}
|
|
|
|
std.format.doFormat(&putc, _arguments, _argptr);
|
|
}
|
|
|
|
...
|
|
|
|
int x = 27;
|
|
// prints 'The answer is 27:6'
|
|
formattedPrint("The answer is %s:", x, 6);
|
|
------------------------
|
|
*/
|
|
|
|
void doFormat(void delegate(dchar) putc, TypeInfo[] arguments, va_list argptr)
|
|
{ int j;
|
|
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,
|
|
}
|
|
|
|
|
|
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
|
|
char* prefix = "";
|
|
char[] s;
|
|
|
|
void putstr(char[] s)
|
|
{
|
|
//printf("flags = x%x\n", flags);
|
|
int prepad = 0;
|
|
int postpad = 0;
|
|
int padding = field_width - (strlen(prefix) + s.length);
|
|
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 FormatError("floating");
|
|
}
|
|
version (DigitalMarsC)
|
|
{
|
|
int 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
|
|
{
|
|
int 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)
|
|
{ int n;
|
|
|
|
sl = fbuf.length;
|
|
n = snprintf(fbuf, sl, format, 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;
|
|
}
|
|
|
|
void putArray(void* p, size_t len, TypeInfo ti) {
|
|
putc('[');
|
|
size_t tsize = ti.tsize();
|
|
while (len--) {
|
|
doFormat(putc, (&ti)[0 .. 1], p);
|
|
p += tsize;
|
|
if (len > 0) putc(',');
|
|
}
|
|
putc(']');
|
|
}
|
|
|
|
//printf("formatArg(fc = '%c', m = '%c')\n", fc, m);
|
|
switch (m)
|
|
{
|
|
case Mangle.Tbit:
|
|
case Mangle.Tbool:
|
|
vbit = va_arg!(bool)(argptr);
|
|
if (fc != 's')
|
|
{ vnumber = vbit;
|
|
goto Lnumber;
|
|
}
|
|
putstr(vbit ? "true" : "false");
|
|
return;
|
|
|
|
|
|
case Mangle.Tchar:
|
|
vchar = va_arg!(char)(argptr);
|
|
if (fc != 's')
|
|
{ vnumber = vchar;
|
|
goto Lnumber;
|
|
}
|
|
L2:
|
|
putstr((&vchar)[0 .. 1]);
|
|
return;
|
|
|
|
case Mangle.Twchar:
|
|
vdchar = va_arg!(wchar)(argptr);
|
|
goto L1;
|
|
|
|
case Mangle.Tdchar:
|
|
vdchar = va_arg!(dchar)(argptr);
|
|
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", 0);
|
|
char[4] vbuf;
|
|
putstr(toUTF8(vbuf, vdchar));
|
|
}
|
|
return;
|
|
|
|
|
|
case Mangle.Tbyte:
|
|
signed = 1;
|
|
vnumber = va_arg!(byte)(argptr);
|
|
goto Lnumber;
|
|
|
|
case Mangle.Tubyte:
|
|
vnumber = va_arg!(ubyte)(argptr);
|
|
goto Lnumber;
|
|
|
|
case Mangle.Tshort:
|
|
signed = 1;
|
|
vnumber = va_arg!(short)(argptr);
|
|
goto Lnumber;
|
|
|
|
case Mangle.Tushort:
|
|
vnumber = va_arg!(ushort)(argptr);
|
|
goto Lnumber;
|
|
|
|
case Mangle.Tint:
|
|
signed = 1;
|
|
vnumber = va_arg!(int)(argptr);
|
|
goto Lnumber;
|
|
|
|
case Mangle.Tuint:
|
|
Luint:
|
|
vnumber = va_arg!(uint)(argptr);
|
|
goto Lnumber;
|
|
|
|
case Mangle.Tlong:
|
|
signed = 1;
|
|
vnumber = va_arg!(long)(argptr);
|
|
goto Lnumber;
|
|
|
|
case Mangle.Tulong:
|
|
Lulong:
|
|
vnumber = va_arg!(ulong)(argptr);
|
|
goto Lnumber;
|
|
|
|
case Mangle.Tclass:
|
|
vobject = va_arg!(Object)(argptr);
|
|
s = vobject.toString();
|
|
goto Lputstr;
|
|
|
|
case Mangle.Tpointer:
|
|
vnumber = cast(ulong)va_arg!(void*)(argptr);
|
|
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 = va_arg!(float)(argptr);
|
|
goto Lreal;
|
|
|
|
case Mangle.Tdouble:
|
|
case Mangle.Tidouble:
|
|
if (fc == 'x' || fc == 'X')
|
|
goto Lulong;
|
|
vreal = va_arg!(double)(argptr);
|
|
goto Lreal;
|
|
|
|
case Mangle.Treal:
|
|
case Mangle.Tireal:
|
|
vreal = va_arg!(real)(argptr);
|
|
goto Lreal;
|
|
|
|
|
|
case Mangle.Tcfloat:
|
|
vcreal = va_arg!(cfloat)(argptr);
|
|
goto Lcomplex;
|
|
|
|
case Mangle.Tcdouble:
|
|
vcreal = va_arg!(cdouble)(argptr);
|
|
goto Lcomplex;
|
|
|
|
case Mangle.Tcreal:
|
|
vcreal = va_arg!(creal)(argptr);
|
|
goto Lcomplex;
|
|
|
|
case Mangle.Tarray:
|
|
if (ti.classinfo.name.length == 14 &&
|
|
ti.classinfo.name[9..14] == "Array")
|
|
{ // array of non-primitive types
|
|
void[] va = va_arg!(void[])(argptr);
|
|
putArray(va.ptr, va.length, (cast(TypeInfo_Array)ti).next);
|
|
return;
|
|
}
|
|
m2 = cast(Mangle)ti.classinfo.name[10];
|
|
switch (m2)
|
|
{
|
|
case Mangle.Tchar:
|
|
s = va_arg!(char[])(argptr);
|
|
goto Lputstr;
|
|
|
|
case Mangle.Twchar:
|
|
wchar[] sw = va_arg!(wchar[])(argptr);
|
|
s = toUTF8(sw);
|
|
goto Lputstr;
|
|
|
|
case Mangle.Tdchar:
|
|
dchar[] sd = va_arg!(dchar[])(argptr);
|
|
s = toUTF8(sd);
|
|
Lputstr:
|
|
if (fc != 's')
|
|
throw new FormatError("string");
|
|
if (flags & FLprecision && precision < s.length)
|
|
s = s[0 .. precision];
|
|
putstr(s);
|
|
break;
|
|
|
|
default:
|
|
TypeInfo ti2 = primitiveTypeInfo(m2);
|
|
if (!ti2)
|
|
goto Lerror;
|
|
void[] va = va_arg!(void[])(argptr);
|
|
putArray(va.ptr, va.length, ti2);
|
|
}
|
|
return;
|
|
|
|
case Mangle.Ttypedef:
|
|
ti = (cast(TypeInfo_Typedef)ti).base;
|
|
m = cast(Mangle)ti.classinfo.name[9];
|
|
formatArg(fc);
|
|
return;
|
|
|
|
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 (flags & FLprecision && fc != 'p')
|
|
flags &= ~FL0pad;
|
|
|
|
if (vnumber < 10)
|
|
{
|
|
if (vnumber == 0 && precision == 0 && flags & FLprecision &&
|
|
!(fc == 'o' && flags & FLhash))
|
|
{
|
|
putstr(null);
|
|
return;
|
|
}
|
|
if (vnumber < base)
|
|
{ vchar = '0' + vnumber;
|
|
goto L2;
|
|
}
|
|
}
|
|
|
|
int n = tmpbuf.length;
|
|
char c;
|
|
int hexoffset = uc ? ('A' - ('9' + 1)) : ('a' - ('9' + 1));
|
|
|
|
while (vnumber)
|
|
{
|
|
c = (vnumber % base) + '0';
|
|
if (c > '9')
|
|
c += hexoffset;
|
|
vnumber /= base;
|
|
tmpbuf[--n] = c;
|
|
}
|
|
if (tmpbuf.length - n < precision && precision < tmpbuf.length)
|
|
{
|
|
int 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);
|
|
putc('+');
|
|
putreal(vcreal.im);
|
|
putc('i');
|
|
return;
|
|
|
|
Lerror:
|
|
throw new FormatError("formatArg");
|
|
}
|
|
|
|
|
|
for (j = 0; j < arguments.length; )
|
|
{ ti = arguments[j++];
|
|
//printf("test1: '%.*s' %d\n", ti.classinfo.name, ti.classinfo.name.length);
|
|
//ti.print();
|
|
if (ti.classinfo.name.length < 10)
|
|
goto Lerror;
|
|
m = cast(Mangle)ti.classinfo.name[9];
|
|
|
|
if (m == Mangle.Tarray)
|
|
{
|
|
Mangle m2 = cast(Mangle)ti.classinfo.name[10];
|
|
char[] fmt; // format string
|
|
wchar[] wfmt;
|
|
dchar[] 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 = va_arg!(char[])(argptr);
|
|
break;
|
|
|
|
case Mangle.Twchar:
|
|
wfmt = va_arg!(wchar[])(argptr);
|
|
fmt = toUTF8(wfmt);
|
|
break;
|
|
|
|
case Mangle.Tdchar:
|
|
dfmt = va_arg!(dchar[])(argptr);
|
|
fmt = toUTF8(dfmt);
|
|
break;
|
|
|
|
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 FormatError("invalid specifier");
|
|
return fmt[i++];
|
|
}
|
|
|
|
int getFmtInt()
|
|
{ int n;
|
|
|
|
while (1)
|
|
{
|
|
n = n * 10 + (c - '0');
|
|
if (n < 0) // overflow
|
|
throw new FormatError("int overflow");
|
|
c = getFmtChar();
|
|
if (c < '0' || c > '9')
|
|
break;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
int getFmtStar()
|
|
{ Mangle m;
|
|
TypeInfo ti;
|
|
|
|
if (j == arguments.length)
|
|
throw new FormatError("too few arguments");
|
|
ti = arguments[j++];
|
|
m = cast(Mangle)ti.classinfo.name[9];
|
|
if (m != Mangle.Tint)
|
|
throw new FormatError("int argument expected");
|
|
return va_arg!(int)(argptr);
|
|
}
|
|
|
|
if (c != '%')
|
|
{
|
|
if (c > 0x7F) // if UTF sequence
|
|
{
|
|
i--; // back up and decode UTF sequence
|
|
c = std.utf.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;
|
|
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++];
|
|
m = cast(Mangle)ti.classinfo.name[9];
|
|
|
|
if (c > 0x7F) // if UTF sequence
|
|
goto Lerror; // format specifiers can't be UTF
|
|
formatArg(cast(char)c);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
field_width = 0;
|
|
flags = 0;
|
|
precision = 0;
|
|
formatArg('s');
|
|
}
|
|
}
|
|
return;
|
|
|
|
Lerror:
|
|
throw new FormatError();
|
|
}
|
|
|
|
/* ======================== Unit Tests ====================================== */
|
|
|
|
unittest
|
|
{
|
|
int i;
|
|
char[] s;
|
|
|
|
debug(format) printf("std.format.format.unittest\n");
|
|
|
|
s = std.string.format("hello world! %s %s ", true, 57, 1_000_000_000, 'x', " foo");
|
|
assert(s == "hello world! true 57 1000000000x foo");
|
|
|
|
s = std.string.format(1.67, " %A ", -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
|
|
assert(s == "1.67 -0X1.47AE147AE147BP+0 nan");
|
|
|
|
s = std.string.format("%x %X", 0x1234AF, 0xAFAFAFAF);
|
|
assert(s == "1234af AFAFAFAF");
|
|
|
|
s = std.string.format("%b %o", 0x1234AF, 0xAFAFAFAF);
|
|
assert(s == "100100011010010101111 25753727657");
|
|
|
|
s = std.string.format("%d %s", 0x1234AF, 0xAFAFAFAF);
|
|
assert(s == "1193135 2947526575");
|
|
|
|
s = std.string.format("%s", 1.2 + 3.4i);
|
|
assert(s == "1.2+3.4i");
|
|
|
|
s = std.string.format("%x %X", 1.32, 6.78f);
|
|
assert(s == "3ff51eb851eb851f 40D8F5C3");
|
|
|
|
s = std.string.format("%#06.*f",2,12.345);
|
|
assert(s == "012.35");
|
|
|
|
s = std.string.format("%#0*.*f",6,2,12.345);
|
|
assert(s == "012.35");
|
|
|
|
s = std.string.format("%7.4g:", 12.678);
|
|
assert(s == " 12.68:");
|
|
|
|
s = std.string.format("%04f|%05d|%#05x|%#5x",-4.,-10,1,1);
|
|
assert(s == "-4.000000|-0010|0x001| 0x1");
|
|
|
|
i = -10;
|
|
s = std.string.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 = std.string.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 = std.string.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 = std.string.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 = std.string.format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i);
|
|
assert(s == "10| 10|010|10|10.0000");
|
|
|
|
s = std.string.format("%.0d", 0);
|
|
assert(s == "");
|
|
|
|
s = std.string.format("%.g", .34);
|
|
assert(s == "0.3");
|
|
|
|
s = std.string.format("%.0g", .34);
|
|
assert(s == "0.3");
|
|
|
|
s = std.string.format("%.2g", .34);
|
|
assert(s == "0.34");
|
|
|
|
s = std.string.format("%0.0008f", 1e-08);
|
|
assert(s == "0.00000001");
|
|
|
|
s = std.string.format("%0.0008f", 1e-05);
|
|
assert(s == "0.00001000");
|
|
|
|
s = "helloworld";
|
|
char[] r;
|
|
r = std.string.format("%.2s", s[0..5]);
|
|
assert(r == "he");
|
|
r = std.string.format("%.20s", s[0..5]);
|
|
assert(r == "hello");
|
|
r = std.string.format("%8s", s[0..5]);
|
|
assert(r == " hello");
|
|
|
|
int[] arr = new int[4];
|
|
arr[0] = 100;
|
|
arr[1] = -999;
|
|
arr[3] = 0;
|
|
r = std.string.format(arr);
|
|
assert(r == "[100,-999,0,0]");
|
|
r = std.string.format("%s",arr);
|
|
assert(r == "[100,-999,0,0]");
|
|
|
|
char[][] arr2 = new char[][4];
|
|
arr2[0] = "hello";
|
|
arr2[1] = "world";
|
|
arr2[3] = "foo";
|
|
r = std.string.format(arr2);
|
|
assert(r == "[hello,world,,foo]");
|
|
|
|
}
|
|
|